JVM内存模型深度剖析

174 阅读3分钟

JVM内存模型尝试剖析

JVM

JVM只是JDK中的一部分,主要负责将Java指令转为对应计算机的指定,是Java语言实现跨平台特性的基础。

一个Java类是如何实现跨平台的

以.java为后缀的符合java规范的文件,通过javac命令编译成了jvm虚拟机能够识别加载的.class后缀文件,通过java命令,依托不同计算机平台的JVM,实现跨平台语言特性: Java语言的跨平台特性.drawio.png

JVM的内存模型

  1. 通过命令java JVMTest.class,JDK会通过jdk.dll创建启动一个JVM虚拟机,里面包含运行时数据区,一个类装载子系统,字节码执行引擎;
  2. 通过类装载子系统,把new的对象放入堆中(绝大部分),而把运行时常量、静态变量、类元信息放入方法区(元空间);
  3. 字节码执行引擎通过放在方法区中的类元信息执行相关方法调用,为线程分配一定的内存空间;
  4. 首先把需要使用的本地方法栈、从栈中为本线程分配的栈空间、分配的程序计数器放到分配到空间;
  5. 把JVMTest中main方法压入栈中,再把main方法中调用的compute方法压入栈中,栈中主要存放局部变量、操作数栈、动态链接、方法出口,每个方法都会在栈上进行压栈,并不是放在已经压的栈数据中;
  6. 栈中的指令执行时,首先把按照指令规则把指令压入操作数栈,执行到赋值或计算指令时,再把操作数栈中的数据取出到CPU中进行赋值或计算,并放入到局部变量中,局部变量中如果存在对象,存的是其指向堆实例的指针或内存地址;
  7. 动态链接一般指方法中调用的方法,其并不是在编译时就已经完成字符引用到直接引用的转换,而是在方法调用时,才进行;
  8. 栈、程序计数器、本地方法栈都是私有的,每个内存不共享,而堆与方法区共享使用;
  9. 每个线程执行时,字节码执行引擎都会对线程的程序计数器进行修改,记录当前线程执行的位置,如此设计是因为CPU执行线程过程中,保证在时间切片用完后,再切换回来时能继续运行指令;

Jvm整体结构与内存模型.drawio (1).png

JVM内存参数设置

基本的的设置参数参照下图: JVM内存参数设置.drawio.png

配置方法

  • SpringBoot:java -Xms258m -Xmx512m -jar your-server.jar
  • Tomcat:直接在目录bin下的catalina.sh文件中

内存区域设置注意事项

  • -XX:MaxMetaspaceSize:设置元空间的最大值,默认为-1,即不限制,或者说只限于本地内存大小;
  • -XX:MetaspaceSize: 指定元空间触发full gc的初始阈值(元空间无固定初始大小),以字节为单位,默认为21M,达到该阈值就会触发full gc进行类型卸载,同时收集器会对该值进行调整:如果释放了大量空间,就适当降低该值;如果释放了很少空间,在不超过-XX:MaxMetaspaceSize的前提下,就适当提高该值。这个与早期的-XX:PermSize的方法区(永久代)参数不一样,其代表的是永久代的初始容量。
  • 由于调整元空间的大小需要full gc,这是非常昂贵的操作,如果应用在启动的时候发生大量的full gc,通常是由于永久代或元空间触发了调整,基于此种情况,一般建议在JVM参数中MaxMetaspaceSize与MetaspaceSize设置为一样的值,并设置得比初始值要大,特别对应大型应用,启动时间久,效果犹其明显。
  • -Xss参数调整的是每个线程栈的内存空间上限,在相同栈空间内,此值设置的越小,可创建的线程栈就越多,反之越少,可以针对业务模型决定此参数的值。