浅谈jvm full gc

1、前言

jvm的内存模型分为:堆、本地方法栈、虚拟机栈,方法区、程序计数器。

其中,gc(垃圾回收)主要集中在堆,堆又划分为2个区域:新生代、老年代。

新生代空间不足时,会触发 minor gc,回收新生代的垃圾。

老年代空间不足时,会触发 full gc,回收老年代的垃圾,同时,full gc 会触发 minor gc。

2、新生代

新生代分为 1 个 Eden 区和 2 个 survivor区(Fromto),当创建一个对象时,一般都分配在 Eden 区(除非对象很大,超过了jvm参数 -XX:PretenureSizeThreshold,则直接分配在老年代), Eden 区会越来越大,当 Eden 区空间不足时,会STW,进行 minor gc(gc算法是标记复制算法),标记存活的对象,并将其复制到 From 区,然后将 Eden 区清空。由于大部分对象的生命周期很短,朝生夕死,所以 Eden 区存活的对象很少,因此 minor gc 的工作量较小,执行时间短,STW的影响非常小。

Eden 区清空后,继续分配对象,直到 Eden 区再次空间不足,此时,触发 minor gc,将 Eden 、From 区中存活的对象复制到 To 区,并将 Eden、From 区清空。注意,此时要将 From 和 To 的身份互换,也就意味着 To 区变成了 From 区

Eden 区又空了,继续分配对象,直到空间不足,再次 minor gc,将 Eden、From区存活的对象复制到 To 区,再次将 Eden、From 区清空,From 和 To 身份互换。

如此循环下去。

浅谈jvm full gc_第1张图片

几个关键点:

1、如果 Eden 太小

会导致频繁的 minor gc,虽然单次 minor gc 的耗时短,但是频率很高的话,也会对服务性能造成影响。

2、如果 Eden 太大

会挤压 From 和 To 区的空间,导致minor gc 时,To 区容易没有足够的空间存储活下来的对象,就会直接存到老年代。

3、From 和 To 的大小一样。

4、三个区的大小比例默认:Eden :From:to = 8:1:1

但是现在JDK8中,默认是开启了自动调整,根据程序运行情况,动态调整三个区的空间比例,如果想要自己设置固定的比例,一定要先关闭自动调整,-XX:-UseAdaptiveSizePolicy,再设置比例:-XX:SurvivorRatio=8, 8 表示 Eden 是 From 和 To 的8倍。

5、新生代的大小,可由jvm参数设置:-Xmn1024m,表示1024M大小。

6、有些对象的生命周期较长,所以在多次 minor gc 后,新生代存活的对象会越来越多,那怎么办?

3、老年代

老年代就是用来存储生命周期长、体积大的对象。

哪些情况下,对象会进入老年代呢?

1、每一个对象都是有年龄的,当对象经历过一次 minor gc 并活下来后,年龄就 + 1,当年龄达到阈值 -XX:MaxTenuringThreshold 时(默认15),就会挪到老年代。

2、minor gc后,To 区容不下存活的对象,此时,这些对象会进入老年代。

3、新生代的标记-复制算法有个缺点,对于大对象,算法效率会降低,为避免这一缺点,直接将大对象存到老年代,大对象的判断标准是,当大于 -XX:PretenureSizeThreshold 参数值时,为大对象。

4、To 区存活的对象中,相同年龄的对象大于空间的一半时,大于等于此年龄的对象直接进入老年代。

一说老年代,就不得不提 “担保机制”。

在 minor gc 之前,如果老年代可用空间 > 新生代所有对象,则 minor gc 后,老年代一定够用,则直接 minor gc 就行,否则,就看 jvm 是否有 “担保机制”(-XX:HandlePromotionFailure 此参数控制,JDK8默认开启),如果没有,则进行 full gc,把老年代的空间腾出来,如果有 “担保机制”,则判断 “老年代可用空间” 是否大于 “每次minor gc进入老年代的对象的平均大小”,如果大于,说明大概率老年代够用,则执行 minor gc,否则,说明老年代大概率不够用,则进行full gc。

最后,如果 full gc 之后,老年代空间还不够用,就会报 OOM。

 

你可能感兴趣的:(full,gc,新生代,老年代,担保机制)