JVM调优相关基础知识
Minor GC/Young GC与Major GC/Full GC
大多数情况下,对象在新生代中 Eden 区分配。当 Eden 区没有足够空间进行分
配时,虚拟机将发起一次Minor GC。我们来进行实际测试一下。
在测试之前我们先来看看 Minor GC和Full GC 有什么不同呢?
- Minor GC/Young GC:指发生新生代的的垃圾收集动作,Minor
GC非常频繁,回收速度一般也比较快。 - Major GC/Full GC:一般会回收老年代,年轻代,方法区的垃圾,
Major GC的速度一般会比Minor GC的慢10倍以上(可能会导致长时间STW)。
Eden与Survivor区默认8:1:1
大量的对象被分配在eden区,eden区满了后会触发minor gc,
可能会有99%以上的对象成为垃圾被回收掉,剩余存活的对象会被挪到为空的那块
survivor区,下一次eden区满了后又会触发minor gc,把eden区和survivor去
垃圾对象回收,把剩余存活的对象一次性挪动到另外一块为空的survivor区,
因为新生代的对象都是朝生夕死的,存活时间很短,所以JVM默认的8:1:1的比例是很合适的,
让eden区尽量的大,survivor区够用即可JVM默认有这个参数
-XX:+UseAdaptiveSizePolicy
,会导致这个比例自动变化,如果不想这个比例有变化可以设置参数
-XX:-UseAdaptiveSizePolicy
如何判断对象可以被回收
堆中几乎放着所有的对象实例,对堆垃圾回收前的第一步就是要判断哪些对象已经死亡
(即不能再被任何途径使用的对象)。
引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,
计数器就减1;任何时候计数器为0的对象就是不可能再被使用的。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,
其最主要的原因是它很难解决对象之间相互循环引用的问题。
所谓对象之间的相互引用问题,如下面代码所示:除了对象objA 和 objB
相互引用着对方之外,这两个对象之间再无任何引用。但是他们因为互相引用对方,
导致它们的引用计数器都不为0,于是引用计数算法无法通知 GC 回收器回收他们。1
2
3
4
5
6
7
8
9public class Main {
public Main m;
public static void main(String[] args){
Main a = new Main();
Main b = new Main();
a.m = b;
b.m = a;
}
}
可达性分析算法
这个算法的基本思想就是通过一系列的称为“GC Roots” 的对象作为起点,
从这些节点开始向下搜索,找到的对象都标记为非垃圾对象,
其余未标记的对象都是垃圾对象GC Roots根节点:
线程栈的本地变量、静态变量、本地方法栈的变量等等
finalize()方法最终判定对象是否存活
即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,
这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历再次标记过程。
标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。
- 第一次标记并进行一次筛选。筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize方法,对象将直接被回收。
- 第二次标记如果这个对象覆盖了finalize方法,finalize方法是对象脱逃死亡命运的最后一次机会,如果对象要在finalize()中成功拯救自己,只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了
如何判断一个类是无用的
方法区主要回收的是无用的类,那么如何判断一个类是无用的类的呢?
类需要同时满足下面3个条件才能算是“无用的类” :
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class
对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
对象进入老年的的5种情况
GC存活次数达到临界值
虚拟机对每个对象定义了一个对象年龄(Age)计数器。当年龄增加到一定的临界值时,就会晋升到老年代中,该临界值由参数:
-XX:MaxTenuringThreshold来设置。
如果对象在Eden出生并在第一次发生MinorGC时仍然存活,并且能够被Survivor中所容纳的话,则该对象会被移动到Survivor中,并且设Age=1;以后每经历一次Minor GC,该对象还存活的话Age=Age+1。
大对象直接进入老年代
大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
JVM参数 -XX:PretenureSizeThreshold 可以设置大对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,
这个参数只在 Serial 和ParNew两个收集器下有效。
比如设置JVM参数:-XX:PretenureSizeThreshold=1000000 -XX:+UseSerialGC
,再执行下上面的第一个程序会发现大对象直接进了老年代为什么要这样呢?
为了避免为大对象分配内存时的复制操作而降低效率。
对象动态年龄判断
当前放对象的Survivor区域里(其中一块区域,放对象的那块s区),
一批对象的总大小大于这块Survivor区域内存大小的50%,
-XX:TargetSurvivorRatio 目标存活率,默认为50%
那么此时大于等于这批对象年龄最大值的对象,就可以直接进入老年代了,
例如Survivor区域里现在有一批对象,年龄1+年龄2+年龄n的多个年龄对象总和超过了
Survivor区域的50%,此时就会把年龄n以上的对象都放入老年代。
这个规则其实是希望那些可能是长期存活的对象,尽早进入老年代。
对象动态年龄判断机制一般是在minor gc之后触发的
Minor gc后存活的对象Survivor区放不下
这种情况会把存活的对象部分挪到老年代,部分可能还会放在Survivor区
老年代空间分配担保机制
年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间如果这个可用
空间小于年轻代里现有的所有对象大小之和(包括垃圾对象)就会看一个
-XX:-HandlePromotionFailure(jdk1.8默认就设置了)的参数是否设置了
如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次
minorgc后进入老年代的对象的平均大小。如果上一步结果是小于或者之
前说的参数没有设置,那么就会触发一次Fullgc,对老年代和年轻代一起
回收一次垃圾,如果回收完还是没有足够空间存放新的对象就会发生”OOM”当然,
如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,
那么也会触发full gc,full gc完之后如果还是没用空间放minorgc之后的存活对象,
则也会发生“OOM”
相关命令
添加启动参数
添加运行JVM参数: ‐XX:+PrintGCDetails
jstat命令
1.查看进程id
查看当前运行的所有的java进程,命令:【一定要注意,取那个你配置的JAVA_HOME全局变量的 那个java进程的PID】
2.jstat命令简介
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。
命令的格式如下:1
jstat [-命令选项] [vmid] [间隔时间/毫秒] [查询次数]
类加载统计
jstat -class PID
解析:
- Loaded:加载class的数量
- Bytes:所占用空间大小
- Unloaded:未加载数量
- Bytes:未加载占用空间
- Time:时间
编译统计
jstat -compiler PID
解析:
- Compiled:编译数量。
- Failed:失败数量
- Invalid:不可用数量
- Time:时间
- FailedType:失败类型
- FailedMethod:失败的方法
垃圾回收统计
jstat -gc PID
解析:
- S0C:第一个幸存区的大小
- S1C:第二个幸存区的大小
- S0U:第一个幸存区的使用大小
- S1U:第二个幸存区的使用大小
- EC:伊甸园区的大小
- EU:伊甸园区的使用大小
- OC:老年代大小
- OU:老年代使用大小
- MC:方法区大小
- MU:方法区使用大小
- CCSC:压缩类空间大小
- CCSU:压缩类空间使用大小
- YGC:年轻代垃圾回收次数
- YGCT:年轻代垃圾回收消耗时间
- FGC:老年代垃圾回收次数
- FGCT:老年代垃圾回收消耗时间
- GCT:垃圾回收消耗总时间
堆内存统计
jstat -gccapacity PID
解析:
- NGCMN:新生代最小容量
- NGCMX:新生代最大容量
- NGC:当前新生代容量
- S0C:第一个幸存区大小
- S1C:第二个幸存区的大小
- EC:伊甸园区的大小
- OGCMN:老年代最小容量
- OGCMX:老年代最大容量
- OGC:当前老年代大小
- OC:当前老年代大小
- MCMN:最小元数据容量
- MCMX:最大元数据容量
- MC:当前元数据空间大小
- CCSMN:最小压缩类空间大小
- CCSMX:最大压缩类空间大小
- CCSC:当前压缩类空间大小
- YGC:年轻代gc次数
- FGC:老年代GC次数
其他
新生代垃圾回收统计:jstat -gcnew PID
新生代内存统计:jstat -gcnewcapacity PID
老年代垃圾回收统计:jstat -gcold PID
老年代垃圾回收统计:jstat -gcoldcapacity PID
1.7永久代空间统计:jstat -gcpermcapacity PID
1.8元数据空间统计:jstat -gcmetacapacity PID
总结垃圾回收统计:jstat -gcutil PID
JVM编译方法统计:jstat -printcompilation PID