当前位置: 代码迷 >> 综合 >> JVM性能调优-01|磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型(JDK8+HotSpot)
  详细解决方案

JVM性能调优-01|磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型(JDK8+HotSpot)

热度:19   发布时间:2023-12-12 11:28:49.0

一、磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型(JDK8+HotSpot)

JVM 整体组成可分为以下四个部分:
在这里插入图片描述
运行时数据区为最主要部分

  1. 类加载器(ClassLoader)
  2. 运行时数据区(Runtime Data Area)
  3. 执行引擎(Execution Engine)
  4. 本地库接口(Native Interface)

二、类加载器

  1. 类加载器子系统负责从文件或者网络中加载Class文件,Class文件在文件打开的开头有特定的文件表示(CAFEBB)。
  2. 类加载器子系统分为三个部分,加载阶段,链接阶段,初始化阶段。
  3. 类加载器只负责文件的加载,具体代码是否可以运行,是执行引擎Execution Engine决定的。

一.加载阶段

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
  3. 在内存中生成一个代表这个类的Java.lang.Class对象,作为方法去这个类是各种数据的访问入口。

二.链接阶段

1、验证

  • 验证的目的主要确认lcss文件中的字节流包含信息符合当前虚拟机的要求,保证被加载类的争取性,不会危害到虚拟机自身的安全。
  • 主要包含四种验证方式,文件格式验证,元数据验证,字节码验证,符号引用验证。

2、准备(准备类,但是并未赋予真正的值)

  • 为类变量分配内存,并且设置该类变量的默认初始值。
  • 这里不包含用final修饰的static,因为final在编译的时候就已经分配了,在准备阶段会显示的进行初始化。
  • 这里也不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量则是会随着对象一起分配到java堆里

3、解析

  • 将常量池内的符号引用转换为直接引用的过程
  • 符号引用就是一组符号用来描述所引用的目标。符号引用的字面量形式明确定义在《java虚拟机规范》的class文件格式中。直接引用就是直接指向目标的指针、想读偏移量或者一个间接定位到目标的句柄。

三.初始化阶段(对链接阶段准备的对象进行赋值)

  • 初始化阶段就是执行类构造器方法(clinit)的过程
  • 此方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并而来的
  • 如果该类有父类,会优先加载父类的初始化构造器方法,之后才会执行子类的。

四.类加载器的分类

1、引导类加载器(Bootstrap ClassLoader)

2、自定义类加载器(User-Defined ClassLoader)

三、运行时数据区(Runtime Data Area,重点)

一.方法区(Method Area,线程共享,元空间)

方法区用于存储已经被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。

方法去与Java堆一样,是各个线程共享的内存区域

方法区的大小和堆一样,可以选择固定的大小或者扩展

方法去的大小决定了系统可以保存多少各类,如果系统定义了太多的类,会导致方法去内存溢出。

查看JVM启动参数的值

java -XX:+PrintFlagsInitial 该命令可以查看所有JVM参数启动的初始值

可以通过以下的几个参数对Metaspace进行控制:

-XX:MetaspaceSize=N #这个参数是初始化的Metaspace大小,该值越大触发Metaspace GC的时机就越晚,默认值大概是20M。-XX:MaxMetaspaceSize=N # 限制Metaspace增长的上限,防止因为某些情况导致Metaspace无限的使用本地内存,影响到其他程序,默认值大概是4G-XX:MinMetaspaceFreeRatio=N #当进行过Metaspace GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小,默认值为40-XX:MaxMetasaceFreeRatio=N #当进行过Metaspace GC之后, 会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间,默认值为70-XX:MaxMetaspaceExpansion=N # Metaspace增长时的最大幅度,默认值为5452592B(大约为5MB)。-XX:MinMetaspaceExpansion=N #Metaspace增长时的最小幅度,默认值为340784B(大约330KB为)。

1、类型信息

2、域信息

3、方法信息

4、

二.堆空间(Java Heap,线程共享)

Java 堆是内存空间占据的最大一块区域了,Java 堆是用来存放对象实例及数组,也就是说我们代码中通过 new 关键字 new 出来的对象都存放在这里。所以这里也就成为了垃圾回收器的主要活动营地了,于是它就有了一个别名叫做 GC 堆,并且单个 JVM 进程有且仅有一个 Java 堆。
PS:根据Java虚拟机的发展,JIT编译器的发展和逃逸分析技术的逐渐成熟,现在存在着new出来的对象不存在于堆中的可能性。比如创建在方法内的对象,未在方法外调用,会被优化为创建在虚拟机栈中,而不是堆中

1、堆的结构

在这里插入图片描述

在这里插入图片描述

JVM 提供了参数 -Xmn 来设置年轻代内存的大小,但没有提供参数设置老年代的大小。但其实老年代的大小就等于堆大小减去年轻代大小。

-Xmn :设置年轻代堆的大小

2、年轻代(新生代)

1.Eden空间

2.From空间(survivor0)

3.To空间(survivor1)

3、老年代

4、堆空间中的GC过程

在这里插入图片描述

  1. 绝大多数的新New出来的对象都放在了Eden区域(伊甸园,新生地)
  2. Eden区域快满的时候进行一次清理(Minor GC,轻量级垃圾回收),不被引用的对象直接被清理掉,还在引用但是年龄较大的,挪到S0区域。
  3. Eden第二次快满的时候,清理操作,清除掉Eden中的未被引用的对象和S0中未被引用的对象,并将这些对象都挪到S1区域。(保证S0或者S1必然有一个为空,用来存储下一次接收的对象)
  4. Eden和S(0或者1)区域都快满的时候,会将新生代(Eden区域+S区域)挪到老年代的区域,
  5. 循环执行,直到老年代(Old)区域及新生代区域都快满的时候,会对内存区域进行一次大清洗(FullGC),为之后对象的创建,腾地方。

清理Eden区和Survivor区叫Minor GC;清理Old区叫Major GC;清理整个堆空间—包括年轻代和老年代叫Full GC。

内存不是越大越好,盲目增大堆内存可能会让吞吐量不增反减,堆内存大了,每次gc扫描对象也就越多也越需要花费时间,反而会适得其反。合理设置堆内存大小,根据实际业务调整,不宜过大,也不宜过小。

三.栈空间(Java Virtual Machine Stacks Area,线程隔离)

栈是运行时的单位,队是存储的单位。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据,堆解决的是数据存储问题,即数据在哪里放,怎么放。

1、Java虚拟机栈是什么?

Java虚拟机栈早期称之为Java栈,每个线程在创建的时候,都会创建一个虚拟机栈,其内部保存着一个个的栈帧,对应着每一次的Java方法调用。

2、虚拟机栈的生命周期

生命周期与线程一致

3、栈的结构

在这里插入图片描述

1.局部变量表(Local Variable Table)

局部变量表(Local Variables)是一组变量值的存储空间,用于存放方法参数和方法内部定义的局部变量。在Java程序被编译为Class文件时,就在方法的Code属性的max_locals数据项中确定了方法所需的分配的局部变量表的最大容量。

2.操作数栈(Operand Stack)

操作数栈(Operand Stack)也常被成为操作栈,是一个后入先出栈,用于保存计算过程中的中间结果,同时作为计算过程中变量临时的存储空间。其最大深度在编译时就被写到了Code属性的max_stacks中。

3.动态链接(Dynamic Linking)

4.返回地址(Return Address)

5.栈帧数据(Stack Data)

3、作用

主管Java程序的运行,保存方法的局部变量,部分结果,并参与方法的调用和返回。

4、特点

5、虚拟机栈大小的调整

四.PC寄存器(程序计数器,Program Counter Register,线程隔离)

程序计数器是一块很小的内存空间,主要用来记录各个线程执行的字节码的地址,例如,分支、循环、跳转、异常、线程恢复等都依赖于计数器。由于 Java 是多线程语言,当执行的线程数量超过 CPU 核数时,线程之间会根据时间片轮询争夺 CPU 资源。如果一个线程的时间片用完了,或者是其它原因导致这个线程的 CPU 资源被提前抢夺,那么这个退出的线程就需要单独的一个程序计数器,来记录下一条运行的指令。

  • PC寄存器是Java虚拟机中唯一的没有内存溢出的区域。
  • PC寄存器是一块很小的内存空间,大小几乎可以忽略不计
  • PC寄存器的线程是私有的,每个线程都有自己的PC寄存器,其生命周期同线程的生命周期完全一致
  • 在执行Native方法(调用C语言方法)的时候,PC寄存器为未指定值(undefned)

1、案例

在这里插入图片描述
红圈内称之为指令地址(偏移地址),由PC寄存器进行记录,指的是该线程下一步的操作是什么。

2、PC寄存器常见问题:为什么需要PC寄存器?

CPU处理着多个线程,在不同的线程之间来回切换,切换之后就需要知道接下来从哪里继续执行,PC寄存器中的数据就是告诉CPU下一个指令执行的是哪个。

五.本地方法栈(Native Method Stack,线程隔离)

四、执行引擎(Execution Engine)

五、本地库接口

  相关解决方案