首页 > 其他分享 >学习笔记-JVM

学习笔记-JVM

时间:2023-05-25 11:34:17浏览次数:48  
标签:java String 笔记 学习 内存 JVM Integer 方法 加载

JVM的位置

JVM是运行在操作系统上的虚拟机,存在于JRE当中

JVM的类型

  • HotSpot

    • Sun公司

    • 用的基本都是这个

  • JRockit

    • BEA
  • J9VM

    • IBM

JVM的体系结构

JVM8.png

体系000.png

体系001.png

本地方法接口JNI

  • JNI的作用

    • 拓展java的使用,融合不同的编程语言为java所用

      • 最初是C/C++
    • 因为最初java诞生的时候,市面上全是C/C++,java要想立足,必须有能调用C/C++的方法

      • 于是在内存中设置了本地方法栈,专门用来登记native方法

      • 然后由JNI去调用本地方法库

  • 凡是带了native关键字的方法

    • 说明java的作用范围达不到了

    • 会进入本地方法栈

    • 执行引擎会调用本地方法接口JNI

    • 去调用底层c语言的库

  • 常见的本地方法

    • 线程

    • 打印机

    • 管理系统

  • 现在除了通过JNI,也有其他方法去调用其他语言的方法,比如说Socket

类加载器

ClassLoader

用于加载类

分类

  • 虚拟机自带的加载器

    • java调用不到这个类

    • 是用C/C++写的

  • 启动类(根)加载器

    • 加载java核心类库
  • 扩展类加载器

    • 加载ext目录中的jar包
  • 应用程序加载器

    • 加载当前classpath下的所有类

除此之外,用户也能自定义类加载器,用来加载指定路径的class类

双亲委派机制

  • 类加载器收到类加载的请求

  • 将这个请求向上委托给父类加载器去完成

    • 一直向上委托

    • 直到启动类加载器

  • 当前加载器检查是否能够加载当前这个类

    • 能加载就结束,使用当前的加载器

    • 否则通知子加载器进行加载

  • 重复步骤3

  • 若没有任何类加载器可以加载

    • Class Not Found

双亲委派机制的作用:

  • 沙箱隔离机制,安全,防止Java的核心API类被篡改

    • 恶意代码无法通过同名类的方法获得高级权限
  • 避免重复加载

类加载的过程

  • 验证

  • 准备

  • 解析

  • 初始化

程序计数器

  • 可以看作

    • 当前线程所执行的字节码的行号指示器

    • 指向下一个将要执行的指令代码的地址

      • 如果是Java方法,记录的是虚拟机字节码指令的地址

      • 如果是native方法,记录的是Undefined

    • 由执行引擎来读取下一条指令

  • 更确切地说

    • 一个线程的执行

    • 是通过字节码解释器改变当前线程的计数器的值

    • 来获取下一条需要执行的字节码指令

  • 在物理上是通过寄存器来使用的

  • 不存在OOM

  • 线程运行需要的内存空间

  • 虚拟机栈为java方法服务

  • 本地方法栈为native方法服务

  • 两者在作用上是非常相似的

  • 下面主要描述虚拟机栈

栈中存储的是什么

  • 栈帧(stack frame)是栈的元素

    • 每个方法在执行时都会创建一个栈帧
  • 栈帧主要包含四个部分

    • 局部变量表(local variable)

    • 操作数栈(operand stack)

    • 动态连接(dynamic linking)

    • 方法出口

栈000.png

局部变量表

  • 用于存储数据

  • 存储的类型有两种

    • 基本数据类型的局部变量

      • 包括方法参数
    • 对象的引用

      • 但是不存储对象的内容
  • 所需的内存空间在编译期间完成分配

    • 方法运行期间不会改变局部变量表的大小
  • 变量槽(Variable Slot)

    • 局部变量表的容量的最小单位

    • 一个slot最大32位

      • 对于64位的数据类型(long和double)会分配两个连续的slot
    • java通过索引定位的方法使用局部变量表

      • 从0开始

      • 一个Slot占1位

      • 非static方法第0个槽存储方法所属对象实例的引用

      • 栈001.png

    • slot复用

      • 为了节省栈帧空间,slot是可以复用的

      • 如果某个变量失效了

        • 即超出了某个变量的作用域
      • 那么这个变量的slot就会交给其他变量使用

      • 副作用(这一段存疑):

        • 会影响系统的垃圾收集行为

        • 当某个变量失效后,因为它的slot可能还会交给其他变量复用,所以它占用的slot就不会被回收

  • 线程安全

    • 当局部变量表中的引用逃离了线程的范围

    • 也就是当一个引用可以被另一个线程拿到的时候

    • 就变成线程不安全的了

操作数栈

  • 一个栈

  • 元素可以是任意的java数据类型

  • 主要作用

    • 用于算数运算

    • 用于参数传递

  • 栈帧中用于计算的临时数据存储区

  • 举例

    • public class OperandStack{
      
          public static int add(int a, int b){
              int c = a + b;
              return c;
          }
      
          public static void main(String[] args){
              add(100, 98);
          }
      }
      
    • 栈002.png

    • 栈003.jpg

动态连接

指向运行时常量池中该栈帧所属方法引用

返回地址

  • 存放调用该方法的pc寄存器的值

  • 正常退出时会使用

  • 异常退出时会通过异常表来确认

可能出现的异常

  • StackOverflowError

    • 栈溢出错误

    • 如果一个线程在计算时所需的栈大小>配置允许最大的栈大小

    • 那么jvm将抛出该错误

  • OutOfMemoryError

    • 内存不足

    • 栈进行动态扩展时如果无法申请到足够的内存

    • 会抛出该错误

设置栈参数

  • -Xss

    • 设置栈大小

    • 通常几百K

jstack命令

  • jstack是JVM自带的JAVA栈追踪工具

  • 它用于打印出给定的java进程ID、core file、远程调试Java栈信息

  • 常用命令:

    • jstack [option] pid

      • 打印某个进程的堆栈信息
    • 选项

      • -F强制输出

      • -m显示本地方法的堆栈

      • -l显示锁信息

  • 使用案例

    • 查看进程死锁情况

    • 查看高cpu占用情况

      • 还需要用到top命令

被所有线程共享

主要存储

  • new关键字创建的对象实例

    • 数组
  • 静态变量

  • string池(1.8之后)

GC就是在堆上收集对象所占用的内存空间

堆的空间结构

堆000.png

  • 新创建的对象会存储在生成区

  • 年轻代内存满之后,会触发Minor CG,清理年轻代内存

  • 长期存活的对象和大对象会存储在老年代

  • 当老年代内存满之后,会触发Full CG,清理全部内存

    • 如果清理后仍然无法存储进新的对象

    • 会抛出OutOfMemoryError

堆内存诊断

  • jps工具
    • 查看当前系统中有哪些java进程
  • jmap工具
    • 查看堆内存占用情况
    • jmap -heap pid
    • jmap -dump:format=b,live,file=1.bin pid
      • 将堆内存占用情况转储
      • format=b:以二进制的形式
      • live:抓取之前调用一次垃圾回收
      • file=1.bin:将文件导出为1.bin
  • jconsole工具
    • 图形界面的,多功能的监测工具
  • jvisualvm
    • 可视化虚拟机
  • 案例:调用垃圾回收后,占用的内存依然非常大
    • 使用jvisualvm
    • 查看对象个数
    • 使用堆转储dump

方法区

被所有线程共享

主要存储

  • 类信息

    • 版本

    • 字段

    • 方法

    • 接口

  • 运行时的常量池

    • 字面量

      • final修饰的常量

      • 基本数据类型的值

      • 字符串(1.8之前)

    • 符号引用

      • 类和接口的全类型

      • 方法名和描述符

      • 字段名和描述符

    • 当类被加载时,.class中的常量池会被放进运行时常量池中

永久区

JDK1.7及之前,方法区的具体实现是PermSpace永久区

MetaSpace

JDK1.8后,使用MetaSpace元空间替代PermSpace

元空间不在JVM中,而是使用本地内存

有两个参数:

  • MetaSpaceSize

    • 初始化元空间大小

    • 控制发生GC的阈值

  • MaxMetaSpaceSize

    • 限制元空间大小上限

    • 防止异常占用过多的物理内存

使用常量池的优点

  • 避免了频繁的创建和销毁对象而影响系统性能

  • 实现了对象的共享

Integer常量池

public void TestIntegerCache()
{
    public static void main(String[] args)
    {

        Integer i1 = new Integer(66);
        Integer i2 = new Integer(66);
        Integer i3 = 66;
        Integer i4 = 66;
        Integer i5 = 150;
        Integer i6 = 150;
        System.out.println(i1 == i2);//false
        System.out.println(i3 == i4);//true
        System.out.println(i5 == i6);//false
    }

}
  • 为什么i1 == i2false

    • 因为是new了两个新对象,两个新对象的地址不一样
  • 为什么i3 == i4true

    • Integer i3 = 66时,其实进行了一步装箱操作

    • 通过Integer.valueOf()66装箱成Integer

    • public static Integer valueOf(int i) {
              if (i >= IntegerCache.low && i <= IntegerCache.high)
                  return IntegerCache.cache[i + (-IntegerCache.low)];
              return new Integer(i);
      }
      
    • IntegerCacheInteger的静态内部类

      • 它通过static{}静态代码块,将-128~127的值全部缓存在了一个Integer数组中
  • 为什么i5 == i6false

    • 因为150超出了缓存的范围

    • 重新new了一个对象

      public static void main(String[] args){
      Integer i1 = new Integer(4);
      Integer i2 = new Integer(6);
      Integer i3 = new Integer(10);
      System.out.print(i3 == i1+i2);//true
      }

  • 为什么i3 == i1 + i2true

    • 因为对象在进行+运算时是会进行拆箱的

    • 拆箱成int再进行数值比较

String常量池

在1.6之后在堆中,在1.6及之前在永久代中

目的是为了减少字符串对内存的占用,提高效率

String是由final修饰的类,不可被继承

  • String str = new String("abcd");

    • 每次都会创建一个新对象
  • String str = "abcd"

    • 先在栈上创建一个引用

    • 然后去String常量池找是否有"abcd"

      • 若有,直接让引用指向它

      • 没有,向常量池添加一个"abcd",再指向它

字符串+连接问题

String a = "a1";   
String b = "a" + 1;   
System.out.println((a == b)); //result = true  

String a = "atrue";   
String b = "a" + "true";   
System.out.println((a == b)); //result = true 

String a = "a3.4";   
String b = "a" + 3.4;   
System.out.println((a == b)); //result = true 

JVM在编译时就会优化成+号连接后的值

字符串引用+连接问题

public static void main(String[] args){
       String str1 = "a";
       String str2 = "ab";
       String str3 = str1 + "b";
       System.out.print(str2 == str3);//false
    }

因为是变量,在编译时无法确定结果

JVM会将+连接优化成StringBuilder的append方法

反编译后的内容

public class TestDemo
{

    public TestDemo()
    {
    }

    public static void main(String args[])
    {
        String s = "a";
        String s1 = "ab";
        String s2 = (new StringBuilder()).append(s).append("b").toString();
        System.out.print(s1 = s2);
    }
}

但要注意的是,用final修饰过的字符串引用,会被视为常量,而非变量

intern()

s.intern()

  • 将字符串对象尝试放入串池中

    • 如果有,则不放入

    • 如果没有,则放入

      • 但是在1.6版本时

      • 会复制一份,然后把复制品放入串池中

  • 返回串池中的对象

调优

一些参数

  • -XX:+PrinStringTableStatistic

    • 打印串池的统计信息
  • -XX:+PrintGCDetails -verbose:gc

    • 打印GC信息
  • -XX:StringTableSize=<数值>

    • 调整StringTable底层hash表的长度

调优思路

  • 调整桶个数

    • 使用-XX:StringTableSize=<数值>

      • 因为StringTable底层是一个hashtable

      • 所以我们可以通过调整长度来减少发生碰撞的次数

      • 从而减少链表的长度

      • 最终提高速度

  • 考虑将字符串对象是否入池

    • 使用intern()方法

    • 将字符串入池

.class文件中的内容

反编译指令javap -v <.class文件>

  • 类的基本信息

    • 更改时间

    • MD5

    • 类全名

    • 版本信息

    • 父类信息

    • 接口信息

  • 常量池

    • 一张表

    • 虚拟机根据这张常量表找到要执行的

      • 类型、方法名、参数类型、字面量等信息
  • 类的方法定义

    • 构造方法

    • 成员方法

直接内存

  • Direct Memory

    • 常见于NIO操作时,用于数据缓冲区

    • 分配回收成本较高,但读写性能高

    • 不受JVM内存回收管理

    • 会出现内存溢出OOM

  • Unsafe

    • 在底层是通过Unsafe对象分配的空间

    • 于是也需要手动调用Unsafe对象的freeMemory方法释放空间

    • ByteBuffer的实现类内部,使用了Cleaner(虚引用)来检测ByteBuffer对象

    • 一旦ByteBuffer对象被垃圾回收

    • 就会由ReferenceHandler线程通过Cleaner的clean方法调用freeMemory来释放直接内存

  • 禁用显示垃圾回收对直接内存的影响

    • -XX:+DisableExplicitGC禁止显示的垃圾回收

    • 无效掉System.gc()

    • 所以使用直接内存的时候,应该手动使用Unsafe对象

直接内存000.png

  • 原始的IO操作

  • 由于java不能直接访问系统内存

  • 所以数据在被读入到系统缓冲区后,

  • 要再读进java缓冲区

  • 然后才能访问

直接内存001.png

  • 直接内存

  • 存在于系统内存中

  • 但是java和系统都能够访问

标签:java,String,笔记,学习,内存,JVM,Integer,方法,加载
From: https://www.cnblogs.com/Andl-Liu/p/17430650.html

相关文章

  • Spring MVC学习笔记
    1、基本概念SpringMVC是Spring中的一个很重要的模块,主要赋予Spring快速构建MVC架构的Web程序的能力。MVC是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。SpringMVC下我们一般把后端项目分为Servic......
  • 首个机器学习实时特征平台测试基准论文被 VLDB 2023 录取
    国际顶级数据库学术会议VLDB2023将于2023年8月份在加拿大温哥华举办。近日,由清华大学、新加坡国立大学、以及OpenMLDB社区联合完成的科研成果-业界第一个严谨的机器学习实时特征平台测试基准,被大会录取并且受邀在现场报告。论文题目为:FEBench:ABenchmarkforReal-Ti......
  • 利用gpt学习笔记
    如果您想要将t.sample_type_code的前两个字符与td.template_code进行匹配,可以使用LEFT()函数来提取子字符串,并将其作为连接条件。以下是修改后的查询语句:SELECT*FROMt_sample_type_templatetLEFTJOINt_template_datatdONLEFT(t.sample_type_code,2)=LEFT(td.......
  • Spring学习笔记
    1、基本概念Spring:开源的轻量级的java开发框架,目的是提高开发人员的开发效率以及系统的可维护性。核心功能是支持IOC(控制反转)和AOP(面向切面编程)可以很方便地对数据库进行访问、可以很方便地集成第三方组件(电子邮件,任务,调度,缓存等等)、对单元测试支持比较好、支持RESTfulJava......
  • MyBatis体系笔记(未完结)
    MyBatis什么是MyBatisMyBatis是优秀的持久层框架MyBatis使用XML将SQL与程序解耦,便于维护MyBatis学习简单,执行高效,是JDBC的延伸1.MyBatis开发流程引入MyBatis依赖创建核心配置文件创建实体(Entity)类创建Mapper映射文件初始化SessionFactory......
  • 走进Linux世界,学习Linux系统的必备指南
    随着计算机技术的不断发展,Linux操作系统已成为IT行业中备受关注的操作系统之一。Linux以其安全性、稳定性和开放性,受到了广泛的认可和欢迎。学习Linux系统对于IT行业的从业者来说是非常重要的。但是,对于初学者来说,学习Linux可能会感到有些困难。 所以,我今天我打算给初学者们答......
  • constexpr学习
    constexptr和常量表达式常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式。编译过程中得到计算结果。字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定。(注意!!!)对于这条语......
  • Zookeeper学习大纲
    ZooKeeper学习大纲ZooKeeper应用及原理解析Zookeeper介绍什么是ZookeeperZookeeper的应用场景搭建Zookeeper服务器zoo.cfg配置文件说明Zookeeper服务器的操作命令Zookeeper内部的数据模型zk是如何保存数据zk中的znode是什么样的结构zk中节点znode的类型zk的数......
  • 这可能是最全面的Java学习路线了
    大家好,我是大彬~我本科学的不是计算机,大四开始自学Java,并且拿到了几个互联网中大厂的offer。在学习Java这方面还是比较有经验的,下面我来分享下我整理的Java自学路线。在这里也提醒学弟学妹们,要尽早确定以后的方向,读研还是工作,找工作的话,也要尽快确定工作岗位,想转行的,需要花更多......
  • YOLOv5s训练学习记录 - test
    armorFinder_ROS2YOLOv5s训练学习记录:训练、获取模型YOLOv5初级使用教程下载yolov5git上下载Yolov5并调通测试代码数据集准备与文件夹结构这里我使用的是之前提供的装甲板数据集'RM_train_data'这个和原本提供的'RM_train_data'不太一样,需要改成下边的样子├─images│......