JVM组成/JVM垃圾收集原理机制/JVM调优方案基础
JVM内存模型
JDK体系结构图
Java程序执行流程
通过javac命令编译成**.class**的字节码文件
然后通过JVM转换成二进制机器码
Java虚拟机结构图
运行一个程序,先通过javac转换成字节码文件 , 然后通过类加载子系统把字节码文件加载到Java虚拟机的内存区域中, 然后通过字节码执行引擎来执行内存区域中加载的代码
JVM组成
Java虚拟机由三大部分组成:类加载子系统、内存区域(也叫运行时数据区)、字节码执行引擎
JVM内存区域包括:堆、虚拟机栈(线程栈)、本地方法栈、方法区、程序计数器
线程私有:虚拟机栈(线程栈)、本地方法栈、程序计数器
线程共享:堆、方法区、
虚拟机栈:
用来放局部变量,只要有线程开始运行,Java就会为这个线程分配一块专属的内存空间(分配一小块虚拟机栈),想当于从总的虚拟机栈切一小块给这个线程使用。
如果线程里面有方法,那么执行到方法的时候,会从这个线程的虚拟机栈中为这个方法划分一块方法的专属空间(栈帧),来放这个方法里的局部变量。
栈帧
如图:栈有先进后出的特点, 先执行main方法,为mian方法划分空间, 然后执行compute方法,再为它分配空间,释放空间的时候,先释放compute的空间,再释放mian方法的空间。
栈帧的组成
栈帧的组成:局部变量、操作数栈、动态链接、方法出口
局部变量:存放方法的局部变量, 注意:比如main方法中定义了一个对象, 对象是要放在堆中的 , 但是这个对象也是局部变量,会放在栈帧中,栈帧中放的就是这个对象在堆中的内存地址。
操作数栈:相当于一块零时存储空间,JVM在操作数栈中执行局部变量的加减乘除操作
动态链接:程序执行到方法的时候,会将这个方法的字节码,放在JVM的方法区中,动态链接中存放的就是方法区中该方法的内存地址。告诉这个方法栈帧他的字节码在方法区的哪里。
字节码文件是二进制的格式
通过javap -c命令可以反汇编class字节码文件,形成更可读的格式,能获取程序更准确的执行流程
如下图:我们可以获取栈帧中的执行过程
帧栈中的执行过程:
比如我们定义一个
int a = 1;
int b = 2;
int c = a + b;
int a = 1;
先会在操作数栈中分配一个常量:1,然后在局部变量 中给 a 分配一个空间,然后把操作数栈中的1 出栈,然后放到 a 所在的局部变量空间中。
int b = 2; 同理。
int c = a + b;
将局部变量中的 a 的值 ,放入到操作数栈,将局部变量中的 b 的值 ,也放入到操作数栈 , 然后在操作数栈中弹出最上面的两个数据,也就是刚刚添加进去的a与b的值,然后给这两个值执行加法操作。然后从新压入回操作数栈
程序计数器
为什么要设计一个程序计数器:
Java是多线程运行,如果突然有一个优先级更高的线程把当前线程的时间片给抢走了,那么当前线程就会暂时挂起。程序计数器就是用来记录当前线程运行到哪一步。
JVM会为每一个线程在程序计数器中分配一个专属空间(类似虚拟机栈)
程序计数器的执行过程:
程序执行到方法的时候,会将这个方法的字节码,放在JVM的方法区中,字节码执行引擎执行方法区中的方法字节码, 会获取当前字节码的地址指令。然后字节码执行引擎会修改该线程的程序计数器中的数据。
方法区
在JDK1.8之前叫做永久代/持久代 , 在JDK1.8之后改成叫元空间。
方法区中存放:
方法区中存放:常量、静态变量、类信息
如果这个静态变量是对象, 对象是要放在堆中的,所以方法区中放的就是这个对象在堆中的内存地址。
本地方法栈
JAVA中有两种方法:JAVA方法和本地方法
JAVA方法是由JAVA编写的,编译成字节码,存储在class文件中
本地方法是由其它语言编写的,编译成和处理器相关的机器代码
本地方法保存在动态链接库中,即.dll(windows系统)文件中,格式是各个平台专有的
JAVA方法是与平台无关的,但是本地方法不是。
本地方法:被native修饰的方法。是java用来调用非Java代码(C/C++)的接口。
线程在执行过程遇到本地方法也需要分配空间,这个空间是从本地方法栈中分配
堆
堆由年轻代和老年代组成,年轻代占1/3,老年代占2/3。
年轻代中又分为:Eden区和Survivor区,Survivor区有两块, 比例是8:1:1。
new出来的对象优先放在Eden区,当Eden区满了,JVM会让字节码执行引擎在后台开启一个(minor GC)新生代垃圾收集线程,用来收集内存区域的垃圾对象。
JVM垃圾收集机制
可达性分析算法
GC Roots根节点:虚拟机栈的局部变量、方法区中的静态变量、本地方法栈的变量等等
将“GC Roots”对象作为起点,从这些节点开始向下搜索引用的对象,找到的对象都标记为非垃圾对象,其余未标记的对象都是垃圾对象。
被标记的非垃圾对象会被复制到Survivor区中 , 剩下的没有被标记的都会被删除回收
GC过程
new出来的对象优先放在Eden区,当Eden区满了,JVM会让字节码执行引擎在后台开启一个(minor GC)新生代垃圾收集线程, 使用可达性分析算法,标记Eden区中的非垃圾对象,然后将非垃圾对象复制到一块Survivor的s0区中,然后删除Eden区中的数据。
如果Eden区又满了,那么就会再启用minor GC 使用可达性分析算法标记出Eden区与Survivor的s0区中的非垃圾对象,然后将被标记的非垃圾对象,复制到另外一块没有数据的Survivor的s1区中,然后删除原来的Eden区与Survivor的s0区的数据。
如果Eden区又满了,那么会再次标记非垃圾对象,然后再次移动到另外一块Survivor区。就这样在两个Survivor区中来回移动。
分代年龄:标记对象经过几次GC回收,放在对象头中
在Survivor区每一次移动这个对象的分代年龄就会加一,当一个对象的分代年龄达到15的时候,也就是经过15次GC,那么这个对象就会被复制到老年代中。
一个对象中存放的信息
对象有对象头,对象头中存放锁的状态,锁的标记,分代年龄等
内存溢出
随着老年代数据的增加,老年代也会被放满。当老年代放满的时候,字节码执行引擎会启动full GC线程。会对整个堆做垃圾收集。如果没有回收到垃圾对象,那么老年代还是满的,同时Survivor区还在往老年代放数据,就会产生内存溢出报错。
JVM调优
JVM调优的目的
减少GC,特别是减少full GC。因为Java有STW机制在执行GC的时候会停掉用户线程,影响用户体验。
为什么要设计STW机制:
在进行标记的时候,如果工作线程不停止的话,那么肯定会有新对象生成。这些对象是没有被标记的,里面可能有存活的对象,也可能有已经没有被引用的垃圾对象。那么我们就需要不停的进行标记,影响我们GC的执行。
案例:
比如有一个电商网站,在活动的时候每秒的订单量特别大,每一个订单都有一个订单对象,一个订单对象有1kb ,这个订单对象可能还关联着优惠券、活动之类的信息。那么这个订单对象就有20kb,那么每秒假设300单,就有600kb/秒,在街上其他的操作,那么每秒大概产生60MB的对象,1秒后就会变成垃圾对象。
运行14秒就会沾满Eden区域,触发minor GC,最后几秒的对象一般不是垃圾对象,会放入Survivor区中,但是Survivor区不一定能放下就会放到Old区域中。Old要不了多久也会马上满了,所以可能几分钟就会触发一次full GC。但是这个时候之前的非垃圾对象已经变成垃圾对象被清除掉了。就不会触发内存溢出,但是频繁的GC会影响用户体验。
让其减少发生Full GC 的次数的解决方案:
调整堆内存空间,之前堆总共3G,年轻代1G,老年代2G,但是老年代不常用,所以从新分配堆内存空间,给年轻代2G,老年代1G,这样Eden区域就有1.6G,Survivor区域分别有200M,
那么之前14秒就会触发minor GC,现在可能要25秒才会触发,那么上一次minor GC 的非垃圾对象很多就会变成垃圾对象被清除了。这样就很少触发Full GC ,只用minor GC就可以解决很多
像这种调整年轻代与老年代的方案适合类似订单这种(对象生命周期特别短)的情况
单机大内存超高并发的系统进行JVM调优
只进行分配空间是不够的,需要针对minor GC与Full GC分别进行调优。
单机大内存的系统,一般年轻代和老年代空间都特别大。
比如,如果Eden区域比较大,等待Eden区域放满了再进行垃圾回收,那么用户线程就会暂停好久,影响用户体验,同时订单的请求可能就会超时。
解决方案:
采用G1垃圾收集器的思想:不等Eden区域全部用完再回收,比如用了三分之一,就开始触发minor GC线程。但是minor GC不会回收整个Eden区域,只回收一部分,因为大内存的Eden区域太大了,全部回收时间太长了。比如只回收十分之一的区域,那么花费时间就特别短,用户线程停顿时间就特别短,基本不会影响用户体验,订单请求就基本不会超时。
G1垃圾收集器有一个设置STW停顿时间的参数
JVM调优工具:
jvisualvm
JVM自带一个调优工具:jvisualvm
在命令行中执行jvisualvm这个命令就会打开一个可视化工具,里面有所有的本地的Java正在运行的Java进程
在这里有一个Visual GC,可以查看JVM各个区域的变化
如图:Old区域,Eden区域,S0 区域,s1区域
看图中右侧,s0与s1交替有值
最新JVM调优工具:Arthas(阿尔萨斯)
Arthas是Alibaba开源的Java诊断工具,用来辅助查找JVM的问题。
感谢图灵学院诸葛老师
附老师教学视频:https://www.bilibili.com/video/BV1Eb4y1R7zd?p=2