zoukankan      html  css  js  c++  java
  • JVM垃圾回收器 :CMS回收器原理及调优

     1 名词解释

    可达性分析算法:用于判断对象是否存活,基本思想是通过一系列称为“GC Root”的对象作为起点(常见的GC Root有系统类加载器、栈中的对象、处于激活状态的线程等),基于对象引用关系,从GC Roots开始向下搜索,所走过的路径称为引用链,当一个对象到GC Root没有任何引用链相连,证明对象不再存活

    Stop The World:GC过程中分析对象引用关系,为了保证分析结果的准确性,需要通过停顿所有Java执行线程,保证引用关系不再动态变化,该停顿事件称为Stop The World(STW)

    Safepoint:代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要GC,线程可以在这个位置暂停。HotSpot采用主动中断的方式,让执行线程在运行期轮询是否需要暂停的标志,若需要则中断挂起

    2 CMS简介

    CMS(Concurrent Mark and Sweep 并发-标记-清除),是一款基于并发、使用标记清除算法的垃圾回收算法,只针对老年代进行垃圾回收。CMS收集器工作时,尽可能让GC线程和用户线程并发执行,以达到降低STW时间的目的

    通过以下命令行参数,启用CMS垃圾收集器:

    -XX:+UseConcMarkSweepGC

    值得补充的是,下面介绍到的CMS GC是指老年代的GC,而Full GC指的是整个堆的GC事件,包括新生代、老年代、元空间等,两者有所区分

    3 新生代垃圾回收

    能与CMS搭配使用的新生代垃圾收集器有Serial收集器和ParNew收集器。这2个收集器都采用标记复制算法,都会触发STW事件,停止所有的应用线程。不同之处在于,Serial是单线程执行,ParNew是多线程执行

    新生代

    4 老年代垃圾回收

    CMS GC以获取最小停顿时间为目的,尽可能减少STW时间,可以分为7个阶段

    CMS 7个阶段
    • 阶段 1: 初始标记(Initial Mark)

    此阶段的目标是标记老年代中所有存活的对象, 包括 GC Root 的直接引用, 以及由新生代中存活对象所引用的对象,触发第一次STW事件

    这个过程是支持多线程的(JDK7之前单线程,JDK8之后并行,可通过参数CMSParallelInitialMarkEnabled调整)

    初始标记
    • 阶段 2: 并发标记(Concurrent Mark)

    此阶段GC线程和应用线程并发执行,遍历阶段1初始标记出来的存活对象,然后继续递归标记这些对象可达的对象

    并发标记
    • 阶段 3: 并发预清理(Concurrent Preclean)

    此阶段GC线程和应用线程也是并发执行,因为阶段2是与应用线程并发执行,可能有些引用关系已经发生改变。 通过卡片标记(Card Marking),提前把老年代空间逻辑划分为相等大小的区域(Card),如果引用关系发生改变,JVM会将发生改变的区域标记位“脏区”(Dirty Card),然后在本阶段,这些脏区会被找出来,刷新引用关系,清除“脏区”标记

    并发预清理
    • 阶段 4: 并发可取消的预清理(Concurrent Abortable Preclean)

    此阶段也不停止应用线程. 本阶段尝试在 STW 的 最终标记阶段(Final Remark)之前尽可能地多做一些工作,以减少应用暂停时间 在该阶段不断循环处理:标记老年代的可达对象、扫描处理Dirty Card区域中的对象,循环的终止条件有: 1 达到循环次数 2 达到循环执行时间阈值 3 新生代内存使用率达到阈值

    • 阶段 5: 最终标记(Final Remark)

    这是GC事件中第二次(也是最后一次)STW阶段,目标是完成老年代中所有存活对象的标记。在此阶段执行: 1 遍历新生代对象,重新标记 2 根据GC Roots,重新标记 3 遍历老年代的Dirty Card,重新标记

    • 阶段 6: 并发清除(Concurrent Sweep)

    此阶段与应用程序并发执行,不需要STW停顿,根据标记结果清除垃圾对象

    并发清除
    • 阶段 7: 并发重置(Concurrent Reset)

    此阶段与应用程序并发执行,重置CMS算法相关的内部数据, 为下一次GC循环做准备

    5 CMS常见问题

    最终标记阶段停顿时间过长问题

    CMS的GC停顿时间约80%都在最终标记阶段(Final Remark),若该阶段停顿时间过长,常见原因是新生代对老年代的无效引用,在上一阶段的并发可取消预清理阶段中,执行阈值时间内未完成循环,来不及触发Young GC,清理这些无效引用

    通过添加参数:-XX:+CMSScavengeBeforeRemark。在执行最终操作之前先触发Young GC,从而减少新生代对老年代的无效引用,降低最终标记阶段的停顿,但如果在上个阶段(并发可取消的预清理)已触发Young GC,也会重复触发Young GC

    并发模式失败(concurrent mode failure) & 晋升失败(promotion failed)问题

    并发模式失败

    并发模式失败:当CMS在执行回收时,新生代发生垃圾回收,同时老年代又没有足够的空间容纳晋升的对象时,CMS 垃圾回收就会退化成单线程的Full GC。所有的应用线程都会被暂停,老年代中所有的无效对象都被回收

    晋升失败

    晋升失败:当新生代发生垃圾回收,老年代有足够的空间可以容纳晋升的对象,但是由于空闲空间的碎片化,导致晋升失败,此时会触发单线程且带压缩动作的Full GC

    并发模式失败和晋升失败都会导致长时间的停顿,常见解决思路如下:

    • 降低触发CMS GC的阈值,即参数-XX:CMSInitiatingOccupancyFraction的值,让CMS GC尽早执行,以保证有足够的空间
    • 增加CMS线程数,即参数-XX:ConcGCThreads,
    • 增大老年代空间
    • 让对象尽量在新生代回收,避免进入老年代

    内存碎片问题

    通常CMS的GC过程基于标记清除算法,不带压缩动作,导致越来越多的内存碎片需要压缩,常见以下场景会触发内存碎片压缩:

    • 新生代Young GC出现新生代晋升担保失败(promotion failed)
    • 程序主动执行System.gc()

    可通过参数CMSFullGCsBeforeCompaction的值,设置多少次Full GC触发一次压缩,默认值为0,代表每次进入Full GC都会触发压缩,带压缩动作的算法为上面提到的单线程Serial Old算法,暂停时间(STW)时间非常长,需要尽可能减少压缩时间

    参考文档:https://juejin.im/post/5b6b986c6fb9a04fd1603f4a#heading-9


    作者:分布式系统架构
    链接:https://juejin.im/post/5b6b986c6fb9a04fd1603f4a
    来源:掘金
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    题解 P4999 【烦人的数学作业】
    题解 P2657 【[SCOI2009] windy 数】
    题解【洛谷 P1466 [USACO2.2]集合 Subset Sums】
    乘法逆元
    浅谈二维前缀和
    浅谈位运算
    浅谈 Lucas 定理
    浅谈 exgcd
    【洛谷P1754 球迷购票问题】题解
    RPA机器人工厂化时代下,艺赛旗要做什么样的“四新”产品
  • 原文地址:https://www.cnblogs.com/shay/p/13157983.html
Copyright © 2011-2022 走看看