# 频繁 GC
今天分享一个面试题,频繁 GC
怎么处理…
且不说为什么频繁 GC
, 先聊聊你怎么发现 JVM
频繁 GC
的呢?
告警啊!对的。
第一种就是 FullGC
的告警,
第二种就是 机器 CPU 的负载过高
第三种就是 大面积的接口超时,无法处理请求。
那就好说了,咱们分析下什么地方可以进行 GC
?
堆! 但不仅仅是堆,还是堆外内存也会进行 GC
。
当然我们主要说堆!
先看一下常用的名词.
# YoungGC
YoungGC
指发生在新生代的垃圾收集动作,新生代中的对象朝生夕死,所以 Minor GC
非常频繁,回收速度也比较快。频繁的 YoungGC
是正常的,要看下 YoungGC
之后存活的对象是否有问题。
对象优先在新生代 Eden
区中分配,如果 Eden
区没有足够的空间时,就会触发一次 Young GC
。
新生代的垃圾收集器有 Serial
, ParNew
.
经过几次 YGC 还存活的就到了老年代。
# old GC
指发生在老年代的 GC
,速度一般比 Minor GC
慢十倍以上。
老年代的垃圾收集器有 Serial Old
, Parallel Old
, CMS
.
老年代会在两种情况下触发 Old GC
:
一是开启分配担保机制,根据历次 Minor GC
后进入老年代的对象大于当前老年代内存大小,判断 Minor GC
有风险,则会触发 Old GC
;比如:
- 1. 系统并发高、执行耗时过长,或者数据量过大,导致
young gc
频繁,且gc
后存活对象太多,但是survivor
区存放不下(太小 或 动态年龄判断) 导致对象快速进入老年代 老年代迅速堆满 - 2. 程序一次性加载过多对象到内存 (大对象),导致频繁有大对象进入老年代
- 3. 存在内存溢出的情况,老年代驻留了大量释放不掉的对象, 只要有一点点对象进入老年代 就达到
full gc
的水位了
二是Minor GC
后剩余对象太多,老年代放不下了也会触发Old GC
。
# Full GC
FGC
收集整个 Young Gen
以及部分 old gen
的 GC
,只有垃圾收集器 G1
有这个模式。但是我们通常说的 Full GC
指的是 针对新生代、老年代、永久代的全体内存空间的垃圾回收。
full gc
触发条件有
- 1、老年代空间不足, 所以追因的方向就是导致 老年代空间不足的原因:
大量对象频繁进入老年代 + 老年代空间释放不掉 - 2、元空间内存不足,也会触发
Full GC
- 3、堆外内存
direct buffer memory
使用不当导致 - 4、代码问题。老年代占用不高,重启效果不明显。这种情况,大概率是在代码里搞执行了
System.gc()
;
# 解决
知道了上面的问题之后,解决就简单了。
- 1、观察年轻代
gc
的情况,多久执行一次、每次gc
后存活对象有多少survivor
区多大
存活对象比较多 超过survivor
区大小或触发动态年龄判断 => 调整内存分配比例 - 2、观察老年代的内存情况 水位情况,多久执行一次、执行耗时多少、回收掉多少内存
如果在持续的上涨,而且full gc
后回收效果不好,那么很有可能是内存溢出了 => 查看堆快照,定位内存溢出位置,修复代码。 - 3、如果年轻代和老年代的内存都比较低,而且频率低,那么有可能是元数据区加载太多东西了