JVM虚拟机介绍
JVM虚拟机从大体上来说,可以分为5大模块。
1、栈(虚拟机栈、也可称为线程栈)
2、堆 (对象存放的内存)
3、方法区 (静态变量、静态方法、运行时常量)
4、程序计数器(在线程启动时,会给每个线程分配)
5、本地方法栈(本地方法)
栈
每当一个线程开始的时候,jvm就会给该线程分配一个栈内存。(因此也可称为线程栈)
每个栈内存都会有一个程序计数器,用来表示程序运行到第几行(这个第几行指的是在java字节码里的行数),例如CPU被抢占的时候,这个线程会挂起,那重新获得cpu的时候,需要程序计数器来指示从第几行继续运行。
栈帧
当执行到某个方法的时候,栈内存会给该方法分配一个栈帧,而每个栈帧又包含:局部变量表,操作数栈,动态链接,方法出口。
在方法执行之前,java虚拟机就将方法执行完的下一个要执行的地方,记录到方法出口。
堆
当new一个对象的时候,该对象会存放在堆里。
Math math = new Math();
栈里存对应堆里的地址 存在堆里
GC
堆内存里面有两种GC方式,一种是minor GC,是范围较小,对程序性能影响较小的一种GC方式。另一种是full GC,会触发STW(Stop The World),使所有线程都暂时停止。
minor GC
GC ROOT (采用可达性分析算法)从线程栈里对象的根节点开始,一直往下找,所有引用到的对象都会标记为非垃圾对象,(也就是说这些对象都是有用的),复制到survivor区,并直接清空Eden区。
如果一个对象经历一次GC没有被清理掉,那么该对象分代年龄+1。
当再次触发minor gc的时候,复制好的对象会在s0和s1之间来回移动。
如果分代年龄达到15,会挪到老年代。(一直存活者的对象会挪到老年代)
大对象(Edan区放不下)也会直接放入老年代
动态对象年龄判断:如果一批对象的总大小超过了survivor区的50%,那么也会分配到老年代。
full GC
对整个堆进行GC
整个堆内存被填满了会产生
OOM
Out Of Memory
堆内存溢出,程序挂掉。
所以对于大部分JVM调优来说,我们需要减少STW的次数,以达到使程序保持高性能的状态。
方法区
类是存放在方法区的Puclic static User user = new User();
静态变量放在方法区 指向 堆
本地方法区
实际上是使用物理内存 例如xxx.jar
当一个线程调用到本地方法的时候,本地方法区会在线程栈内分配一个本地方法栈内存,以供调用。
程序计数器
当一个线程运行的时候,在JVM赋予其线程栈的时候,会同时往里开辟一个程序计数器。
用来标记程序运行到了第几行,由字节码执行引擎不断修改。
JVM调优实例
如图所示,假设某个电商网站业务,每秒产生60M的对象,下一秒业务结束,就应该归为垃圾对象。
当程序持续运行,最后第13秒产生的对象可能会存入survivor区里面,也就是说,可能会有60M对象在那一瞬间是非垃圾对象,不会被回收。
动态对象年龄判断:如果一批对象的总大小超过了survivor区的50%,那么也会分配到老年代。
60M>100*50% 所以触发了这个,垃圾对象放到了老年代。
因此垃圾对象会被持续放入老年代,最终导致OOM。
调整JVM虚拟机内存
把老年代调小,年轻代放大。
不会触发动态年龄判断。
因此,对于JVM调优,最关键的是对业务主流程的评估,看看每秒产生多少大小的对象,再对堆的内存模型进行参数更改,从而避免频繁full GC。