美团的GC文档:
https://tech.meituan.com/tags/gc.html
https://tech.meituan.com/2017/12/29/jvm-optimize.html
最近遇到了一个问题,频繁Full GC,通过平台诊断发现是日志打印导致的,出现了内存泄露。
内存泄漏是指本应该被GC回收的无用对象没有被回收,导致的内存空间的浪费,当内存泄露严重时会导致OOM。Java内存泄露根本原因是:长生命周期的对象持有短生命周期对象的引用,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被GC回收。
异步打印日志,打印日志的对象一直没有被回收,引用的日志内容虽然没用了,但是引用一直存在,无法回收,导致泄露。
还有一种情况,打印的打日志对象太多,打印不完,导致FullGC。
https://blog.csdn.net/CoderLai/article/details/100096045
大对象,是由于有个查询条件查询的数据量过大,产生慢查询,mysql耗时30多秒,有索引但是是索引范围查询。打印log时把该大对象整体全部打印所致,异步打印日志线程一直在运行没有完成,导致cpu过高,且大对象没有被回收,一直占用老年代内存。新生代进行回收时,由于老年代内存不足,空间担保机制失败所以一直进行full gc
其他案例
https://tech.meituan.com/2017/12/29/jvm-optimize.html
这篇文档提到了三个案例,第一个是minor GC 频繁,显然是新生代太小了,新生代和老年大默认大小 1:2, Egen:from:to默认8:1:1.
当新生代增大了,增加了标记时间但是减少了复制时间,而复制时间是更耗时的,所以标记时间可以忽略,性能还是提高了的。
第二个案例是CMS回收期的STM问题,CMS有很多步骤,其中remark是会STM的,其他都是并行的,为什么remark如此耗时呢?为了防止不准确,比如跨代引用,需要扫描整个堆,包括老年代和新生代。下图的对象A,如果只扫描老年代是不可达的,实际是可达的。这样就增加了扫描对象的数量,耗时。
新生代的对象生命周期很短,所以在并行标记之前,执行一次minor GC,回收一下新生代,减少remark扫描的对象,提升效率。
JDK8默认的垃圾回收期是PS,不是CMS,所以CMS用的少,为什么不默认CMS呢?
最后一个案例是永久代扩容导致的,这个了解就好了。
总结
频繁GC很大原因的内存泄露,异步线程操作大对象,注意,