文章目录
jvm整体接口详细架构图
类装载子系统
底层是C++实现的。
负责将字节码文件加载,解析到jvm内存区域上。
字节码执行引擎
底层是C++实现的。
1、负责执行jvm内存上的代码,字节码执行引擎会读取方法区里的字节码文件来执行代码。
2、线程代码执行时,字节码执行引擎会动态修改每个线程对应的程序计数器(程序计数器是线程私有的,每个线程都会有自己的一个程序计数器)。
3、线程代码执行时,jvm触发执行的gc操作(minor gc、full gc)都是通过字节码执行引擎来触发执行的。
内存模型(核心)
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5.4(oracle官方文档供参考)
上图内存模型中,黄色的代表线程共享的,紫色的代表线程私有的。
栈(Java Virtual Machine Stacks)
每个jvm里的线程都对应一个私有的虚拟机栈,与线程同时创建。
栈里存放的是很多个 栈帧。
栈帧
每一个方法都代表一个栈帧,包括方法之间的调用和递归调用,每调用一个方法,都会在栈里创建一个栈帧。
每个方法从调用到结束的过程,对应一个栈帧的入栈和出栈的过程。
那么,栈帧里存放的是什么东西呢?
- 局部变量表(table)
里边存放着方法里的局部变量和他对应的值,如果是对象的话,存放的是指向对象的指针,真正对象的值存放在 堆 里。 - 操作数栈
官方:Java 虚拟机提供指令以将局部变量或字段中的常量或值加载到操作数栈上。其他 Java 虚拟机指令从操作数栈中获取操作数,对其进行操作,然后将结果推回操作数栈。操作数栈还用于准备要传递给方法的参数和接收方法结果。
个人理解:操作数栈就是程序运行中,要做操作(比如加减乘除一系列操作)的一块临时的中转存放的内存空间。 - 动态链接
程序运行期间,将符号引用转化为直接引用,也就是符号对应的代码地址,那些地址就存放在动态链接里。
符号引用和直接引用清楚可以参考另一篇文章《jvm类加载和双亲委派机制》 - 方法出口
本地方法栈(Native Method Stacks)
本地方法栈里存放的是本地方法,给本地方法提供内存支持。
下图是本地方法,使用native修饰的方法,底层是c++编写的。
程序计数器(The pc Register)
字节码文件里的code代码块,在执行每一行的时候,字节码执行引擎会动态的将程序计数器的值改为这一行代码的索引位置。(Code代码块以0开始)
程序计数器的作用就是当线程抢夺到cpu资源后,可以根据计数器的值执行对应字节码里Code位置的代码
方法区(Method Area)也称元空间
- 常量池(constant pool)和运行时常量池
- 静态变量,如果是对象的话,存放的是指向对象的指针,真正对象的值存放在 堆 里。
- 类信息(其实就是代码)
堆(Heap)
堆中存放的是对象。
对象在堆里分为年轻代和老年代
年轻代
年轻代里分为 Eden区 和 Survivor区
- 回收年轻代的垃圾对象过程解释:
程序中产生的新的对象,都存放在Eden区,当Eden区存放满时,jvm的字节码执行引擎会做 minor gc 操作,将Eden区的垃圾的对象进行回收,将非垃圾对象存入survivor区 的s0上,这些非垃圾对象的分代年龄会+1,分代年龄存放在对象头中。当Eden区又存放满时,又会触发minor gc操作,这次的gc将Eden、s0两个区域都进行回收,将非垃圾对象存入 s1中,分代年龄+1。
老年代
当分代年龄变成15时,也就是进行了15次minor gc还没有被回收,则将他移动到老年代区域。
当老年代里的对象放满了,会触发full gc,该gc操作会回收整个堆和方法区,如果回收完老年代的内存还是不够的话,会触发OOM内存溢出。
如何区分垃圾对象和非垃圾对象(GC ROOT)
//todo 待补充
STW(stop-the-world)
停止掉用户线程。minor gc和full gc都会触发STW。
jvm为什么有stw机制?
//todo 待补充
jvisualvm工具 分析堆和gc
jdk自带的工具,输入命令行jvisualvm就能打开
上图能看到我启动的一个项目,对应的代码如下
运行这块代码,查看Visual GC(这个需要下载插件,可以百度下载步骤)里的监控
能看到堆中的Eden、s0、s1、old区、元空间还有gc触发的时间等等,可以进行分析
JVM内存参数设置
Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):
java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar microservice‐eureka‐server.jar
- 关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize:
设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize:
指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认是21M,达到该值就会触发 full gc进行类型卸载, 同时收集器会对该值进行调整(自动扩容): 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的-**XX:PermSize **参数意思不一样,-XX:PermSize代表永久代的初始容量。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大,对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。 - -Xss:
栈的内存大小,默认1M,如果设置过小的话,容易造成SOF(StackOverflowError)栈溢出。
-Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多。