2020年6月29日 作者 zeroheart

GC相关

https://mp.weixin.qq.com/s/_AKQs-xXDHlk84HbwKUzOw 
https://mp.weixin.qq.com/s/P8s3kuceBNovUP5adXpFCQ
这两篇讲的够了,我这边是做了些摘录,详细内容看上面引用的链接。

堆存放对象的实例和数组,是进行GC的区域。

引用计数方法,会有循环引用的现象,不能作为GC对象的标记。现代虚拟机使用可达性算法来标记清理的对象。

a, b 对象可回收,就一定会被回收吗?并不是,对象的 finalize 方法给了对象一次垂死挣扎的机会,当对象不可达(可回收)时,当发生GC时,会先判断对象是否执行了 finalize 方法,如果未执行,则会先执行 finalize 方法,我们可以在此方法里将当前对象与 GC Roots 关联,这样执行 finalize 方法之后,GC 会再次判断对象是否可达,如果不可达,则会被回收,如果可达,则不回收!
注意: finalize 方法只会被执行一次,如果第一次执行 finalize 方法此对象变成了可达确实不会回收,但如果对象再次被 GC,则会忽略 finalize 方法,对象会被回收!这一点切记!
那么这些 GC Roots 到底是什么东西呢,哪些对象可以作为 GC Root 呢,有以下几类
1.虚拟机栈(栈帧中的本地变量表)中引用的对象
2.方法区中类静态属性引用的对象
3.方法区中常量引用的对象
4.本地方法栈中 JNI(即一般说的 Native 方法)引用的对象

finalize方法,我是没用过。

垃圾回收方法

标记-清除、标记-整理、复制、分代(以上方法的整合)

分代的垃圾回收方法、分为新生代、老年代。比例1:2,新生代分为Eden区、s0,s1,比例是8:1:1
Young GC(也叫 Minor GC)
Old GC(也称为 Major GC/Full GC)

每经历一次YG,age会增加1,如果一直没回收,age=15会进old区
另外大对象,或者s中年龄一样的对象大于1半,那么年龄大于等于它的,会进入老年代。

空间分配担保
在发生 MinorGC 之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间,如果大于,那么Minor GC 可以确保是安全的,如果不大于,那么虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于则进行 Minor GC,否则可能进行一次 Full GC。

如果老年代满了,会触发 Full GC, Full GC 会同时回收新生代和老年代(即对整个堆进行GC),它会导致 Stop The World(简称 STW),造成挺大的性能开销。
什么是 STW ?所谓的 STW, 即在 GC(minor GC 或 Full GC)期间,只有垃圾回收器线程在工作,其他工作线程则被挂起。

Minor GC 也会造成 STW,但只会触发轻微的 STW

当晋升到老年代的对象大于了老年代的剩余空间时,就会触发FGC(Major GC),FGC处理的区域同时包括新生代和老年代。除此之外,还有以下4种情况也会触发FGC:

1.老年代的内存使用率达到了一定阈值(可通过参数调整),直接触发FGC。
2.空间分配担保:在YGC之前,会先检查老年代最大可用的连续空间是否大于新生代所有对象的总空间。如果小于,说明YGC是不安全的,则会查看参数 HandlePromotionFailure 是否被设置成了允许担保失败,如果不允许则直接触发Full GC;如果允许,那么会进一步检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果小于也会触发 Full GC。
3.Metaspace(元空间)在空间不足时会进行扩容,当扩容到了-XX:MetaspaceSize 参数的指定值时,也会触发FGC。
4.System.gc() 或者Runtime.gc() 被显式调用时,触发FGC。

FGC过于频繁、YGC耗时过长是比较典型的GC问题,影响程序响应,需要特别关注,FGC耗时过长、YGC过于频繁两种情况的严重程度低一些,但是对于高并发或者高可用的程序也需要关注。


1. 清楚从程序角度,有哪些原因导致FGC? 
大对象:系统一次性加载了过多数据到内存中(比如SQL查询未做分页),导致大对象进入了老年代。

内存泄漏:频繁创建了大量对象,但是无法被回收(比如IO对象使用完后未调用close方法释放资源),先引发FGC,最后导致OOM.

程序频繁生成一些长生命周期的对象,当这些对象的存活年龄超过分代年龄时便会进入老年代,最后引发FGC.

程序BUG导致动态生成了很多新类,使得 Metaspace 不断被占用,先引发FGC,最后导致OOM.

代码中显式调用了gc方法,包括自己的代码甚至框架中的代码。

JVM参数设置问题:包括总内存大小、新生代和老年代的大小、Eden区和S区的大小、元空间大小、垃圾回收算法等等。

2. 清楚排查问题时能使用哪些工具
公司的监控系统:大部分公司都会有,可全方位监控JVM的各项指标。
JDK的自带工具,包括jmap、jstat等常用命令:
# 查看堆内存各区域的使用率以及GC情况
jstat -gcutil -h20 pid 1000,效果如下图
# 查看堆内存中的存活对象,并按空间排序
jmap -histo pid | head -n20
# dump堆内存文件
jmap -dump:format=b,file=heap pid
可视化的堆内存分析工具:JVisualVM、MAT等
-XX:+HeapDumpOnOutOfMemoryError 可以配置发生OOM时候自动dump,后续再分析。

3. 排查指南
查看监控,以了解出现问题的时间点以及当前FGC的频率(可对比正常情况看频率是否正常)

了解该时间点之前有没有程序上线、基础组件升级等情况。

了解JVM的参数设置,包括:堆空间各个区域的大小设置,新生代和老年代分别采用了哪些垃圾收集器,然后分析JVM参数设置是否合理。

再对步骤1中列出的可能原因做排除法,其中元空间被打满、内存泄漏、代码显式调用gc方法比较容易排查。

针对大对象或者长生命周期对象导致的FGC,可通过 jmap -histo 命令并结合dump堆内存文件作进一步分析,需要先定位到可疑对象。

通过可疑对象定位到具体代码再次分析,这时候要结合GC原理和JVM参数设置,弄清楚可疑对象是否满足了进入到老年代的条件才能下结论。
cpu高的排查
top -c 列出进程,按P,按照cpu使用率排序
top -Hp pid,列出线程tid 或者 ps -mp pid -o THREAD,tid,time
printf "%x\n" tid => 假设是b26
jstack -l pid > ./pid.stack
cat 2609.stack | grep 'b26' -C 8
再分析,如果是gc代码一直运行,那就检查GC相关问题,如果是自己写的代码问题,那就检查死循环之类的。

可以直接利用这个脚本,一键打印cpu占用高的线程
https://github.com/oldratlee/useful-scripts/blob/master/docs/java.md#-show-busy-java-threads

•  死锁,Deadlock(重点关注) 
•  等待资源,Waiting on condition(重点关注) 
•  等待获取监视器,Waiting on monitor entry(重点关注) 
•  阻塞,Blocked(重点关注) 
•  执行中,Runnable 
•  暂停,Suspended 
•  对象等待中,Object.wait() 或 TIMED_WAITING 
•  停止,Parked 


cms,并发标记清除,速度最快,但是有碎片,是G1之前的首选

G1可以设置用户期望的stw时间(jvm尽量保证)