JVM运行情况预估
主要依靠jstat gc -pid 命令统计GC运行情况。如年轻代对象增长速率、Young GC的触发频率和每次耗时、每次Young GC后有多少对象存活和进入老年代、Full GC的触发频率和每次耗时等。在此信息基础上对jvm参数做出调整。
年轻代对象增长的速率
可以执行命令 jstat -gc pid 1000 10 (每隔1秒执行1次命令,共执行10次),通过观察EU(eden区的使用)来估算每秒eden大概新增多少对象,如果系统负载不高,可以把频率1秒换成1分钟,甚至10分钟来观察整体情况。注意,一般系统可能有高峰期和日常期,所以需要在不同的时间分别估算不同情况下对象增长速率。
Young GC的触发频率和每次耗时
知道年轻代对象增长速率我们就能推根据eden区的大小推算出Young GC大概多久触发一次,Young GC的平均耗时可以通过 YGCT/YGC公式算出,根据结果我们大概就能知道系统大概多久会因为Young GC的执行而卡顿多久。
每次Young GC后有多少对象存活和进入老年代
这个因为之前已经大概知道Young GC的频率,假设是每5分钟一次,那么可以执行命令 jstat -gc pid 300000 10 ,观察每次结果eden,survivor和老年代使用的变化情况,在每次gc后eden区使用一般会大幅减少,survivor和老年代都有可能增长,这些增长的对象就是每次 Young GC后存活的对象,同时还可以看出每次Young GC后进去老年代大概多少对象,从而可以推算出老年代对象增长速率。
Full GC的触发频率和每次耗时
知道了老年代对象的增长速率就可以推算出Full GC的触发频率了,Full GC的每次耗时可以用公式 FGCT/FGC 计算得出。
优化的理论依据
其实简单来说就是:
- 对象能在年轻代就被淘汰掉的尽量不让他进入老年代
- 尽量不要触发full GC
触发young GC的条件:
- eden区空间不足
触发full GC的条件:
- 老年代空间不足
- 永久代空间不足
- young GC时 触发了老年代空间分配担保机制。
对象进入老年代的条件(即影响老年代对象增长速率):
- 大对象直接进入老年代
- 大年龄对象进入老年代
- young GC时根据动态年龄判断机制,一次放入Survivor的对象大于Survivor区的50%,则多余的对象会放入老年代
full gc比minor gc还多的原因
- 元空间不够导致的多余full gc
- 显示调用System.gc()造成多余的full gc,这种一般线上尽量通过XX:+DisableExplicitGC参数禁用,如果加上了这个JVM启动参数,那么代码中调用System.gc()没有任何效果
- 老年代空间分配担保机制
分析思路
- 首先,根据老年代的对象增长速率,得出理论上发生full GC的频率。用理论频率与实际频率去对比。
- 如果实际频率远比理论频率高,那么根据full gc比minor gc还多的原因分析可能是哪一种原因。如尝试增大元空间大小、禁用System.gc()及避免触发老年代空间分配担保机制。
- 如果实例频率与理论频率相差不多,则说明问题出在老年代对象增长速率上。此时有两种分析思路:
3.1 考虑降低对象进入老年代的速率。用线程分析确定是否有大量的大对象产生,是否在年轻代young GC时触发了动态年龄判断机制使对象进入了老年代。
3.2 考虑增大堆大小,以此降低full GC频率。