为什么 cms 新生代垃圾回收时能做到不全表扫描,G1能做到回收控制

为什么 cms 新生代垃圾回收时能做到不全表扫描,G1能做到回收控制,第1张

父文章 : java 垃圾回收总结和面试和应用_个人渣记录仅为自己搜索用的博客-CSDN博客

问题:  新生代回收为什么可以不全表扫描标记?

答案: 只标记在新生代区的根引用+ cardTable上的标记?

    cardTable作用见下文.

    G1活用了这个概念, 而 G1 可以认为是打开了另一个方向的大门:只回收部分垃圾来减少停顿时间。不过为了达到只回收部分 reigon,每个 region 都需要 RememberSet 来记录各 region 之间的引用。这个内存的开销其实还是挺大的,可能会占据整堆的20%或以上。

1、什么是跨代引用?

红色的线表示由虚拟机栈中发出的引用。显然B--->A、E--->F都是跨代引用。

跨代引用逻辑图

2、跨代引用对MonitorGC的影响

JVM GC 判断对象是否可以回收使用可达性分析的方法,可达性分析首先需要找到 GC Roots 对象。

常规GC-Root:

  • 1)虚拟机栈(栈帧中的局部变量表)中引用的对象【Stack Local】。
  • 2)本地方法栈(native方法)引用的对象。
  • 3)方法区中类静态属性、静态方法引用的对象。
  • 4)方法区中常量引用的对象。

MonitorGC个性GC-Root:

  • 5) RememberSet数据结构(CardTable是具体实现类似数组)

这里重点讲一下CardTable:
        作用:采用空间换时间,不需要扫描整个Heap空间,降低MonitorGC耗时。跨代引用带来的问题,采用CardTable很好的规避了遍历整个老年代的问题。(phil 注但会带来浮动垃圾的增多. 老年代比较稳定, 故浮动垃圾可承受.)


        HotSpot JVM的卡页(Card Page)大小为512字节,卡表(Card Table)被实现为一个简单的字节数组,即卡表的每个标记项为1个字节。

CardTable标记着是否存在老年代引用新生代

如上图发现:B、C所在的Card Page在CardTable中被标记上了。

【思考🤔】MonitorGC的GC-Root中包括CardTable,如何提高MonitorGC效率?

就需要降低跨代引用的对象,尽量设置稍大些的From、To区域,尽量将对象消灭在Young Gen区域。同时也表示对象不是越早进入老年代越好(老年代对象引用新生代对象就是一个很好的说明)!!!。

3、跨代引用对CMS中 OldGC的影响

CMS-OldGC回收♻️分为7个步骤:

CMS的7个步骤

重点步骤解读:

  • 1、初始标记(Initial Mark)

    • 目标:进行可达性分析,标记GC ROOT能直接关联到的对象。
    • 标记范围:Young Gen + Old Gen。
    • 线程:JDK1.7是单线程,JDK1.8是多线程(-XX:+CMSParallelInitialMarkEnabled调整)
    • STW:触发Stop-The-World
    • 特点:速度极快
  • 2、并发标记(Concurrent Mark)

    • 目标:遍历阶段1初始标记出来的存活对象,然后继续递归标记这些对象可达的对象。(黑白灰三色标记法)。
    • 标记范围:Young Gen + Old Gen。
    • STW:不触发
    • 特点:慢,很耗时。
    • 特殊 *** 作:通过Old区卡片标记(Card Marking),提前把老年代空间逻辑划分为相等大小的区域(Card Page),如果引用关系发生改变,JVM会将发生改变的区域标记位“脏区”(Dirty Card), 也就是JVM会对“并发标记”阶段应用线程新产生的对象及对象涉及的引用修改做记录DirtyCard(Mod-Union Table)
  • 3、预清理(Preclean)

    • 目标:2阶段中记录在Mod-Union Table的这些脏区会被找出来,刷新引用关系,清除“脏区”标记。
    • 标记范围: Old Gen。
    • 线程:GC线程和应用线程也是并发执行
    • STW:不触发
    • 特点:速度一般
  • 4、可中断的预清理(Concurrent Abortable Preclean)

    • 目标:降低垃圾回收时对应用的暂停时间,整个过程最耗时步骤在5(最终标记),所以4步骤提前进行。
    • 触发条件:此阶段在Eden区使用超过2M时启动,当然2M是默认的阈值,可以通过参数修改。
    • 标记范围:Young Gen + Old Gen。
      • 处理From、To区对象,标记可达到老年代的对象。
      • 和3阶段一样,扫描处理Dirty Card中的对象。
    • 终止逻辑:CMS为了避免这个阶段没有等到Minor GC而陷入无限等待,提供了参数CMSMaxAbortablePrecleanTime ,默认为5s,含义是如果可中断的预清理执行超过5s,不管发没发生Minor GC,都会中止此阶段,进入Remark。
    • 线程:GC线程和应用线程也是并发执行。
    • STW:不触发
    • 特点:速度一般
    • 特殊:下一个阶段要执行最耗时且STW的Remark阶段,Remark阶段会将整个YoungGen作为GC-Root进行 *** 作,如果4阶段能触发一次MonitorGC就再好不过了。
  • 5、重新标记(Final ReMark)

    • 目标:GC事件中第二次(也是最后一次)STW阶段,目的是完成老年代中所有存活对象的标记。
    • 标记范围:Young Gen + Old Gen。
    • 线程:GC线程独占执行
    • STW:触发Stop-The-World
    • 特点:速度较慢,可以说是整个CMS-Old GC的瓶颈点
    • 特殊:
      • 1、遍历新生代对象,重新标记
      • 2、根据GC Roots,重新标记
      • 3、遍历老年代的Dirty Card,重新标记
  • 6、并发清除(Concurrent Sweep)

    • 目标:根据标记结果清除垃圾对象。
    • 标记范围:Old Gen。
    • 线程:GC线程和应用线程也是并发执行
    • STW:不触发
    • 特点:速度一般。
  • 7、并发重置(Concurrent Reset)

    • 目标:重置CMS算法相关的内部数据, 为下一次GC循环做准备。
    • 线程:GC线程和应用线程也是并发执行
    • STW:不触发
    • 特点:速度一般。

4、问题思考🤔:

【思考🤔】CMS oldgc中remark耗时问题,跨代引用是图一中的B--->A 还是 E--->F?
答案:E--->F。
通过:-XX:+CMSScavengeBeforeRemark ,触发MonitorGC降低的就是YoungGen区的对象,从而达到标记源头减少,降低remark时间。
【思考🤔】CMS 跨代引用是图一中的B--->A 有何危害,虚拟机是如何避开的?
答案:CardTable。
逻辑:采用创建CardTable空间区域,来避免扫整个老年代。当B无引用的时候,cardTable会被标记,一次MonitorGC就会回收♻️掉。B是如何做到没引用的,同E--->F是一样的逻辑。

【思考🤔】CMS里有两个需要STW的阶段:initial mark,remark、这两个标记有什么不一样么?使用的GC-Root一样么?
答案:不一样。
初始标记:使用的是常规的GC-Root(虚拟机栈栈帧中的局部变量表、本地方法栈native方法、方法区中类的静态属性和方法、方法区中常量等引用的对象)
重新标记:

  • 常规的GC-Root。(只有MonitorGC才会使用跟可达分析)
  • 新生代所有对象。
  • 遍历老年代的DirtyCard(ModUnionTable)。

【思考🤔】同时带来了另外一个问题,MonitorGC可没有遍历整个老年代,而是采用CardTable通过时间换空间的做法,CMS-OldGC为什么采用遍历新生代所有的对象呢  

     这就是YoungGen 与 OldGen存在明显的差异,时刻记住这点,新生代变动比较大:

  • 老年代对象都是经历过多次GC回收♻️存活下来了,对象存在的变数极低。( phil 补充 老年代稳定,意味着 cardTable是稳定的, 不太会出现 引用删除的情况,导致从cardTable标记到新生代的对象变成浮动垃圾,等下次cms后的新生代回收才会被清理掉.  )  所以采用空间换时间效果较好。
  • 新生代对象属于变化特别快的区域,如果采用空间换时间,既浪费了空间,也没有提升性能。( phil 补充 新生代变动大,且量也大 如果新生代也有cardTable,意味着 cardTable会比较大,新生代占1/3 ,还不如可中断的新生代垃圾回收后再最终标记,变动也大, 经常会出现 引用删除的情况,如果新生代垃圾回收不及时, 将导致从新生代cardTable标记到老生代的对象变成浮动垃圾, 等下次新生代回收后的cms才会被清理掉.   )

【思考🤔】既然initial mark阶段+concurrent mark阶段已经扫果了young gen 为何还要再次Remark?

这个问题你可以考虑这种情况,如果remark阶段不重新扫描young gen,那么young gc发生时(old gc发生的同时young gc也是可以发生的)年轻代有对象gc年龄到了,晋升到老年代,如果不重新标记,那么这个对象是不是就被漏标了呢。

 

【思考🤔】CardTable与mod-union table有什么关系,都是干什么的?

dirty mod-union table 也是存在CardTable上的.

这个问题是这样的,在分代垃圾回收时,跨代引用是要考虑的,比如young gc时老年代对象B引用年轻代的对象A,这个老年代对象B就被称为假根。hotspot是这样处理的,它把堆内存分为card的集合,一个card是512B,然后用一个全局的卡表来记录引用的变化。就比如卡表中的一个字节用来对应堆上的一个card是否存在跨代引用。B对象所在的card在全局卡表上对应字节标记为dirty就说明这个card存在跨代引用。而cms为了处理漏标的情况,也会记录引用关系的变化(比如黑对象指向了白对象)同样记录在card table中。这个card table只有一份既要用来支持young GC又要用来支持CMS。每次young GC过程中都涉及重置和重新扫描card table。那岂不是cms所需引用关系就没有了,所以又产生了一个mod-union table(一个bit对应一个card)用来在CMS concurrent marking正在运行的过程中,每当发生一次young GC,当youngGC要重置card table里的某个记录时,将card table的记录更新到mod union table中,这样在重新标记时card table外加mod-union table就足以记录在并发标记过程中old gen发生的所有引用变化了。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/919782.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存