数组动态扩容导致频繁FGC

参数:-XX:CMSScavengeBeforeRemark
含义:
Enable scavenging attempts before the CMS remark step.
开启或关闭在CMS重新标记阶段之前的清除(YGC)尝试
CMS并发标记阶段与用户线程并发进行,此阶段会产生已经被标记了的对象又发生变化的情况,若打开此开关,可在一定程度上降低CMS重新标记阶段对上述“又发生变化”对象的扫描时间,当然,“清除尝试”也会消耗一些时间
注,开启此开关并不会保证在标记阶段前一定会进行清除操作

CMS重新标记前,进行YGC清除操作

先不打开CMSScavengeBeforeRemark 参数;

     可以看出发生FGC的时候,并没有触发YGC,所以老年代的对象一直回收不了,因为老年代进行GC时,会把新生代的对象作为GC root,在本场景中,新生代的对象不回收,老年代的对象也无法回收,而且老年代的内存使用率已经超过设置的阈值90%,所以会不断的进行FGC。

然后 JVM参数中添加-XX:+CMSScavengeBeforeRemark,打开重新标记前进行YGC
JVM 源码分析12 YGC 深入分析

 

YGC实现过程

新生代内存分配失败触发YGC

    如果是因为新生代内存分配失败触发YGC时,JVM内部对应会生成一个VM_GenCollectForAllocation对象,提交到一个执行队列中,最终会由VM Thread执行它的doit()方法

这里需要关注的是satisfy_failed_allocation()方法

由垃圾回收策略决定执行satisfy_failed_allocation方法

1、如果GC_locker正在起作用,说明有线程正在通过JNI操作临界内存,那么就放弃GC动作,因为JNI操作完之后会可能会触发一次GC
2、如果上一次YGC是失败的,至于为什么上一次是失败的,可能是因为老年代没有足够的空间容纳新生代的对象,那么就不执行本次的YGC,直接进行FGC


JVM 源码分析12 YGC 深入分析

1、to空间不为空
2、没有下一个内存代,即没有老年代
3、其中情况1和2几乎不会发生,主要还是看这种情况,主要看老年代是否有足够的空间来容纳新生代的对象

   如果老年代中的可用空间大于gc_stats统计的新生代每次平均晋升的对象大小,或者可以容纳目前新生代所有的对象,表明可以执行正常的YGC动作,如果都不满足,就直接放弃本次YGC。

由FGC触发的YGC

在JVM参数中添加-XX:+CMSScavengeBeforeRemark,执行FGC之前会触发一次YGC,这个参数的好处是如果YGC比较有效果的话是能有效降低remark的时间长度,可以简单理解为如果大部分新生代的对象被回收了,那么GC root变少了,从而提高了remark的效率。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

JVM源码分析之YGC的来龙去脉

YGC是JVM GC当前最为频繁的一种GC,一个高并发的服务在运行期间,会进行大量的YGC,发生YGC时,会进行STW,一般时间都很短,除非碰到YGC时,存在大量的存活对象需要进行拷贝。

一次YGC过程主要分成两个步骤:
1、查找GC Roots,拷贝所引用的对象到 to 区;
2、递归遍历步骤1中对象,并拷贝其所引用的对象到 to 区,当然可能会存在自然晋升,或者因为 to 区空间不足引起的提前晋升的情况;

查找GC Roots

YGC的第一步根据GC Roots找出第一批活跃的对象,Hotspot中通过gch->gen_process_strong_roots方法实现

根据gc root找到活跃的对象

YGC在执行时只收集young generation,不收集old generation和perm generation,并不会做类的卸载行为,所以上述可选部分都作为Strong root,但是在FGC时就不会当作Strong root了

CMS做full gc的时候,需要扫描young generation作为它的Strong root。

如果一个old generation的对象引用了young generation,那么这个old generation的对象肯定也属于Strong root的一部分,这部分逻辑并没有在process_strong_roots中实现,而是在绿色框中实现了,其中rem_set中保存了old generation中dirty card的对应区域,每次对象的拷贝移动都会检查一下是否产生了新的跨代引用,比如有对象晋升到了old generation,而该对象还引用了young generation的对象,这种情况下会把相应的card置为dirty,下次YGC的时候只会扫描dirty card所指内存的对象,避免扫描所有的old generation对象。

遍历活跃对象

在查找GC Roots的步骤中,已经找出了第一批存活的对象,这些存活对象可能在 to-space,也有可能直接晋升到了 old generation,这些区域都是需要进行遍历的,保证所有的活跃对象都能存活下来。

JVM 源码分析12 YGC 深入分析

在遍历过程中,可能会遇到已经处理过的对象,如果遇到这样的对象,就不会再次进行复制了,如果该对象没有被拷贝过,则调用 copy_to_survivor_space 方法拷贝对象到to-space或者晋升到old generation,这里提一下ParNew的实现,因为是并发执行的,所以可能存在多个线程拷贝了同一个对象到to-space,不过通过原子操作,保证了只有一个对象是有效的。

如果该对象的年龄大于某个阈值,会晋升到old generation,或者在拷贝到to-space时空间不足,也会提前晋升到old generation,晋升过程通过老年代_next_gen的promote方法实现,如果old generation也没有足够的空间容纳该对象,则会触发晋升失败。



 

相关文章: