007-JVM垃圾回收器

前言

        文章主要描述ParNew、CMS和G1,常规的物理分代GC推荐:物理分代垃圾回收器

三种垃圾回收算法

1、标记清除

  1. 把无用的内存标记
  2. 清除垃圾:清除只是把垃圾内存的起始结束地址做一个记录即可(对象头mark word的状态位也要标记为11)

优点

        速度快

缺点

        容易产生内存碎片,如果此时插入一个数组对象,实际内存空间够用,但是由于各自不连续,导致数组对象无法存储

2、标记整理

  1. 标记无效内存
  2. 删除无效内存并重新排布内存对象,避免碎片产生,但整理内存也导致速度较慢

优点

        防止内存碎片

缺点

        速度慢

3、标记复制

  1. 标记垃圾空间,如下图

  2. 把存活数据从from移到to区,如下图

  3. 完全复制过去以后,再把from内存直接清空,如下图

  4. 最后再把to变成from,把from变成to,反转一下,如图

缺点

        需要空闲一部分空间

优点

        不存在内存碎片,速度快

JVM垃圾回收器

        JVM垃圾回收器集成了上述的垃圾回收算法,不同状态会选择相应算法

1、物理分代(YGC:Serial、ParNew、ParallelScavenge,OldGC:Serial Old、CMS、Parallel Old)

        一直在用,长时间存在,或者大对象都会放到老年代,朝生昔死的对象放到新生代,其中survivor有两种,一种from区域,一种to区,eden:from:to默认比例8:1:1,新生代:老年代默认比例:1:2

        一般一次可以回收80-90%的空间(清除连续空间只需要标记下就行,极快)

2、逻辑分代垃圾回收(G1)

        如下图,整个heap不再分代,划分成了2048个等大region,G1仍然应用了新生代、老生代的分区逻辑,即被使用的region会被标记上eden、survivor、old和humongous(存放大对象)

3、新一代垃圾回收(ZGC、Shenandoah)

        还未上生产,所以没看,以后补上.

4、使用说明

 分别是物理分代+串行、物理分代+并行、物理分代+并发、逻辑分代+并发

常见的GC组合 

JDK8支持的GC组合
新生代老生代其它
Serial (标记复制)*Serial Old(标记整理)*-XX:+UseSerialGC 
Serial(标记复制)CMS(标记清除)JDK8弃用
ParNew(标记复制)Serial Old(标记整理)JDK8弃用
ParNew(标记复制)CMS + Serial Old-XX:+UseConcMarkSweepGC 
Parallel Scavenge (标记复制)*Serial Old(标记整理) *-XX:-UseParallelOldGC、-XX:+UseParallel(<jdk7-U4)
Parallel Scavenge(标记复制) *Parallel Old (标记整理)*-XX:+UseParallelOldGC
G1(标记复制)-XX:+UseG1GC

【注:】

        其中标 * 的gc组合垃圾回收只有YGC和FullGC,OldGC不可以单执行。原因是OldGC是STW机制+标记整理算法,相对耗时,只能在关键时刻用,因此只有FullGC才能触发OldGC。 

5、vm相关参数(新垃圾收集器参数有变化)

        垃圾回收器的选用(根据阿里毕玄文章整理,应用时还需实践为主

  1. 几十M以内:单核 -XX:+UseSerialGC,多核 -XX:-UseParallelOldGC
  2. 几百M——3G:-XX:+UseParallelOldGC
  3. 3G——8G:-XX:+UseParallelOldGC,不行再-XX:+UseConcMarkSweepGC
  4. CMS 8G——20G:-XX:+UseConcMarkSweepGC
  5. 20G——百十G:-XX:+UseG1(rocketmq 默认就是8G 用的是G1)
  6. 几百G——4T:ZGC 和  Shenandoah

空间分配担保机制

        适用于物理分代GC,在发生Minor GC之前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象的总空间如果大于,则此次Minor GC是安全的,如果小于,则虚拟机会查看HandlePromotionFailure 设置的值是否允许担保失败。如果HandlePromotionFailure=true,那么会继续检查老年代最大可用连续空间是否大于历次晋升到老年代的对象的平均大小,如果大于,执行Minor GC;如果小于或者HandlePromotionFailure=false,则改为Full GC。 

Full GC

        GC的类型有minor gc、major gc和full gc,后面会讲到,这里只讲通用的full gc。

1、介绍

        full gc是利用minor gc+major gc实现全堆清理的一类gc算法,一般是堆内存清理的最后手段,也可以由特定指令调用,比如 YGC promotion failure 或者 concurrent mode failure 失败后触发Full GC、System.gc() 触发Full GC、metaSpace 触发Full GC。推荐:STW引发的Full GC

0.514: [GC (Allocation Failure) [PSYoungGen: 4445K->1386K(28672K)] 168285K->165234K(200704K), 0.0036830 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.518: [Full GC (Ergonomics) [PSYoungGen: 1386K->0K(28672K)] [ParOldGen: 163848K->165101K(172032K)] 165234K->165101K(200704K), [Metaspace: 3509K->3509K(1056768K)], 0.0103061 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
0.528: [GC (Allocation Failure) [PSYoungGen: 0K->0K(28672K)] 165101K->165101K(200704K), 0.0019968 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.530: [Full GC (Allocation Failure) [PSYoungGen: 0K->0K(28672K)] [ParOldGen: 165101K->165082K(172032K)] 165101K->165082K(200704K), [Metaspace: 3509K->3509K(1056768K)], 0.0108352 secs] [Times: user=0.03 sys=0.00, real=0.01 secs] 

 YGC promotion failure触发Full GC日志

【注:】之所以又走GC 又走Full GC 是因为PSGC默认开启参数ScavengeBeforeFullGC 导致Full GC 前会先执行一次YGC

0.585: [GC (System.gc()) [PSYoungGen: 251986K->1403K(917504K)] 251986K->1411K(1966080K), 0.0020937 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.587: [Full GC (System.gc()) [PSYoungGen: 1403K->0K(917504K)] [ParOldGen: 8K->1261K(1048576K)] 1411K->1261K(1966080K), [Metaspace: 3508K->3508K(1056768K)], 0.0083870 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]

 System.gc() 触发Full GC日志

0.288: [GC (Metadata GC Threshold) [PSYoungGen: 3347K->1328K(38400K)] 3347K->1328K(125952K), 0.0015915 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
0.290: [Full GC (Metadata GC Threshold) [PSYoungGen: 1328K->0K(38400K)] [ParOldGen: 0K->1176K(61952K)] 1328K->1176K(100352K), [Metaspace: 2946K->2946K(1056768K)], 0.0065967 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 

MetaSpace触发Full GC日志  

2、注意事项

        2.1、full gc整合minorgc和majorgc实现全堆清理+方法区清理,只是与其它GC相比full gc 是门面逻辑,因此你会看到不同的full gc效果,如G1 是serial old、并行组合是 Parallel Scavenge+Parallel Old ...

        2.2、full gc只是实现全堆清理的算法之一只有GC日志打印Full GC记录才表示JVM执行了一次Full GC,开发者应以虚拟机为准,比如UseCMSGC期间,ygc promotion failure 触发的GC日志一般是【GC (cms initing mark...)】而不是【Full GC (...)】记录。

-----------------------------------------------------------正文开始--------------------------------------------------------- 

低延迟垃圾回收器

        当前垃圾收集器的重要指标是吞吐量和低延迟,而web开发中由于高并发需求使得JVM内存达到了百十G,对于大内存的GC如何满足低延迟需求就显得格外重要,而CMSG1就是主流的低延迟生产服务GC,当然还有ZGC和Shenandoah,暂时略

JVM吞吐量

        介绍:

        是一个比率,业务程序运行时间 / 程序总运行时间,其中程序总运行时间 = 业务运行时间+垃圾回收时间,如果基数是1个月,吞吐量是99/100,那么gc回收时间=1月*1/100,即8个小时,这表示一个月内有8个小时在gc回收,而gc又要STW,这意味着用户每个月有8个小时无法使用,因此基数越小越好,即内存越小越好;如果用户高并发访问会生成大量垃圾,快速消耗内存,因此并发量越小越好;

         优化:

        1、基数优化,基数 = 一次业务运行时间+一次gc回收时间,要想调低这两个时间就必须设置小内存,比如3G以内

        2、并发优化,降低连接数,尽量复用对象以延缓内存消耗

低延时

        介绍:为了使用户随时并发访问jvm进程,感知不到gc回收,GC实现了并发清理,即和业务线程并发运行,解决了大内存的垃圾回收问题,大内存也解决了用户并发产生大量垃圾的问题

        优化:

        1、低延时是通过并发思路实现,因此要使用多核cpu最大程度发挥并发性能

        2、合理设置并发量和内存,并发量大内存就得大,内存大垃圾回收一次速度就慢,垃圾回收速度慢于垃圾产生速度就会OOM,因此要让并发量和垃圾回收速度有一个平衡,这个平衡的核心在于GC对大内存的垃圾回收能力

三色标记

        背景:CMS选择了并发清理来处理大内存垃圾,也就是所说的低延时回收器。

        目的:通过三色标记共享GC信息,方便并发GC时减少中断时间

        原理:根据可达性分析算法,从GC Roots开始进行遍历访问,可达的则为存活对象:

最终结果:A、D、E、F、G 可达

我们把遍历对象图过程中遇到的对象,按“是否访问过”这个条件标记成以下三种颜色:

  • 白色:尚未访问过。
  • 黑色:本对象已访问过,而且本对象 引用到 的其他对象 也全部访问过了。
  • 灰色:本对象已访问过,但是本对象 引用到 的其他对象 尚未全部访问完。全部访问后,会转换为黑色。

三色标记遍历过程

        JVM设置白、灰、黑三个集合,遍历流程如下:

  1. 初始时,所有对象都在 【白色集合】中;
  2. 将GC Roots 直接引用到的对象 挪到 【灰色集合】中;
  3. 从灰色集合中获取对象:
    3.1. 将本对象 引用到的 其他对象 全部挪到 【灰色集合】中;
    3.2. 将本对象 挪到【黑色集合】里面。
  4. 重复步骤3,直至【灰色集合】为空时结束。
  5. 结束后,仍在【白色集合】的对象即为GC Roots 不可达,可以进行回收。

        当STW时,对象间的引用 是不会发生变化的,可以轻松完成标记。而当需要支持并发标记时,即标记期间应用线程还在继续跑,对象间的引用可能发生变化多标漏标的情况就有可能发生。

  • 浮动垃圾(多标):将原本应该被清除的对象,误标记为存活对象。后果是垃圾回收不彻底,不过影响不大,可以在下个周期被回收;
  • 对象消失(漏标):将原本应该存活的对象,误标记为需要清理的对象。后果很严重,影响程序运行,是不可容忍的。

        能不能在并发标记期间,将用户线程对引用关系的修改都保存起来?并发标记完成后,再将这些保存的修改过程,重新进行标记和调整?能,CMS 就是这么干的。它将并发标记期间引用发生变化的对象都暂存起来,并发标记完成后,再重新对这些暂存的对象重新进行一次标记。虽然重新标记的过程是需要 STW 的,但是重新标记的对象数量远远小于并发标记阶段的对象数量,因此停顿时间也是短暂且相对固定的,因此这个方法可行!

遗留问题

多标-浮动垃圾

假设已经遍历到E(变为灰色了),此时应用执行了 objD.fieldE = null :

D > E 的引用断开

此刻之后,对象E/F/G是“应该”被回收的。然而因为E已经变为灰色了,其仍会被当作存活对象继续遍历下去。最终的结果是:这部分对象仍会被标记为存活,即本轮GC不会回收这部分内存

这部分本应该回收 但是 没有回收到的内存,被称之为“浮动垃圾”。浮动垃圾并不会影响应用程序的正确性,只是需要等到下一轮垃圾回收中才被清除。

另外,针对并发标记开始后的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分对象期间可能会变为垃圾,这也算是浮动垃圾的一部分。

漏标-读写屏障

假设GC线程已经遍历到E(变为灰色了),此时应用线程先执行了:s

var G = objE.fieldG; 
objE.fieldG = null;  // 灰色E 断开引用 白色G 
objD.fieldG = G;  // 黑色D 引用 白色G

E > G 断开,D引用 G

此时切回GC线程继续跑,因为E已经没有对G的引用了,所以不会将G放到灰色集合;尽管因为D重新引用了G,但因为D已经是黑色了,不会再重新做遍历处理。
最终导致的结果是:G会一直停留在白色集合中,最后被当作垃圾进行清除。这直接影响到了应用程序的正确性,是不可接受的。

漏标必须要同时满足以下两个条件:

  1. 赋值器插入了一条或者多条从黑色对象到白色对象的新引用;
  2. 赋值器删除了全部从灰色对象到该白色对象的直接或间接引用。

这两个条件必须全部满足,才会出现对象消失的问题。那么我们只需要对上面条件进行破坏,破坏其中的任意一个,都可以防止对象消失问题的产生。这样就产生了两种解决方案:

  • 增量更新:Incremental Update。
  • 原始快照:Snapshot At The Beginning,SATB。

增量更新破坏的是第一个条件,当黑色对象插入新的指向白色对象的引用时,就将这个新加入的引用记录下来,待并发标记完成后,重新对这种新增的引用记录进行扫描;

原始快照破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,也是将这个记录下来,并发标记完成后,对该记录进行重新扫描。

HotSpot 虚拟机中,不管是新增还是删除,这种记录的操作都是通过写屏障实现的。我们可以将写屏障理解为 JVM 对引用修改操作的一层 AOP,注意它与内存屏障是两个不同的东西。

增量更新与原始快照在 HotSpot 中都有实际应用,其中增量更新用在 CMS 中,原始快照用在了 G1、Shenandoah 等回收器中。

解决方法1:增量更新

增量更新破坏的是第一个条件,在新增一条引用时,将该记录保存。实际的实现中,通常是将引用相关的节点进行重新标记。考虑下图中的例子:

上面就是一次引用关系修改导致的对象消失问题。增量更新进行的处理,就是将由 A 到 C 的这条新增的引用关系进行保存。首先看下 Dijkstra 等人提出的方式:

write_barrier(obj, field, newobj) {
    if (newobj.mark == FALSE) {
        newobj.mark = TRUE;
        push(newobj, $mark_stack);
    }
    *field = newobj;
}

如果新引用的对象 newobj 没有被标记,那么就将其标记后堆到标记栈里。换句话说, 如果 newobj 是白色对象,就把它涂成灰色。这样操作后的结果如下图所示:

此时 C 被涂成了灰色,它将在后续被重新扫描,阻止了对象消失。

Steele 提出了一种更严厉的方法,它相比 Dijkstra 的方法,可以减少错误标记的对象数量。

write_barrier(obj, field, newobj) {
    if($gc_phase == GC_MARK && obj.mark == TRUE && newobj.mark == FALSE) {
        obj.mark = FALSE;
        push(obj, $mark_stack);
    }
    *field = newobj;
}

如果在标记过程中发出引用的对象是黑色对象,且新的引用的目标对象为灰色或白色,那么我们就把发出引用的对象涂成灰色。这样操作后的结果如下图:

此时 A 由原来的黑色变成了灰色,将在后续被重新扫描。

解决方法2:原始快照

原始快照破坏的是第二个条件,当灰色对象要删除指向白色对象的引用关系时,就将这个要删除的引用记录下来,并发扫描结束后,在将这些记录重新扫描一次。

write_barrier(obj, field, newobj) {
    oldobj = *field;
    if(gc_phase == GC_MARK && oldobj.mark == FALSE) {
        oldobj.mark = TRUE;
        push(oldobj, $mark_stack);
    }
    *field = newobj;
}

当 GC 进入到标记阶段且 oldobj 没被标记时,则标记 oldobj,并将其记录。也就是说,在标记阶段中如果指针更新前引用的 oldobj 是白色对象,就将其涂成灰色。

1上图依旧是对象消失的例子。a 到 b 中,产生了一条由 A 到 C 的引用关系,这里并没有像增量更新那样将 A 或者 C 标为灰色,相反原始快照中允许出现从黑色指向白色的引用。而在从 b 到 c 中,删除了由 B 到 C 的引用关系。这时候就需要进行处理,将 C 涂为灰色。

实现三色标记法的垃圾回收器

        可达性分析的垃圾回收器几乎都借鉴了三色标记的算法思想,尽管实现的方式不尽相同:比如黑白灰只是区分标识的方言,各有个都方言格式、灰色集合可以通过栈/队列/缓存日志等方式进行实现、遍历方式可以是广度/深度遍历等等。

对于读写屏障,以Java HotSpot VM为例,其并发标记时对漏标的处理方案如下:

  • CMS:写屏障 + 增量更新
  • G1:写屏障 + SATB(原始快照)
  • ZGC:读屏障

        工程实现中,读写屏障还有其他功能,比如写屏障可以用于记录跨代/区引用的变化,读屏障可以用于支持移动对象的并发执行等。功能之外,还有性能的考虑,所以对于选择哪种,每款垃圾回收器都有自己的想法。

值得注意的是,CMS中使用的增量更新,在重新标记阶段,除了需要遍历 写屏障的记录,还需要重新扫描遍历GC Roots(当然标记过的无需再遍历了),这是由于CMS对于astore_x等指令不添加写屏障的原因,具体可参考这里

为什么G1用SATB?CMS用增量更新?

        CMS用增量更新:如果选SATB,则容易产生更多浮动垃圾,CMS虽然管理较大内存,但总体来说并不富裕,CMS还容易因内存泄露降级成SerialOld引起FullGC,因此选择增量更新虽然是深度扫描但更合适。

        G1用SATB:G1管理大内存,G1本身的数据就要占据大概1/5的空间,因此只有大内存才用G1,大内存就表示有足够的内存来接受垃圾浮动,因此选择SATB这种更快速的浅扫描机制更合适。

记忆集与卡表

        在新生代做GCRoots可达性扫描过程中可能会碰到跨代引用的对象,如果又去对老年代再去扫描效率太低了。为此,在新生代可以引入记录集(Remember Set)的数据结构(记录从非收集区到收集区的指针集合),避免把整个老年代加入GCRoots扫描范围。事实上并不只是新生代、 老年代之间才有跨代引用的问题, 所有涉及部分区域收集(Partial GC) 行为的垃圾收集器, 典型的如G1、 ZGC和Shenandoah收集器, 都会面临相同的问题。

        垃圾收集场景中,收集器只需通过记忆集判断出某一块非收集区域是否存在指向收集区域的指针即可,无需了解跨代引用指针的全部细节。

跨代引用

        由于年龄分代的缘故,堆中跨代对象之间会出现引用的场景,比如

        场景1(Young GC)——老年代的对象引用新生代的对象(B->A);

        场景2(Old GC)——新生代的对象引用老年代的对象(E->F)。

        场景1要获取跨代引用的GC Roots不可能直接把整个堆都扫描完,那这样效率也太低了,所以就需要RSet来提高扫描效率。

【补:】        

为什么场景2不需要额外维护一个RSet

        场景1:Old Gen的对象都是经历多次GC回收存活的,对象变动的变数极低,非常稳定,这意味着很少有老对象指向新对象,如果有少量的老对象发生变更指向新对象用RSet记录更高效,因此推荐Young GC使用RSet。

        场景2:Young Gen 对象变化频繁(毕竟主要为业务逻辑,讲究的就是随业务频繁变动),这意味着对象可能一会指向这一会指向那,在这种场景下RSet会被高频使用(由于变动频繁,dirty还不一定真实),试想一下RSet被大量标记不就是指Young Gen有大量对象跨代引用么,既浪费了空间也没有提升性能;另外Old GC执行之前还可能触发Young GC,这也能为Yong Gen清理掉大量垃圾。因此推荐Old GC直接扫描Young Gen获取GC Roots。

RSet

        也叫记忆集,垃圾收集器在新生代中建立了记忆集这样的集合,作用是避免跨代场景下扫描整个老年代来识别 GC Roots 的低效。

        对于记忆集来说,我们可以理解为他是一个抽象类,那么具体实现它的方法将由子类去完成。这里我们简单列举一下实现记忆集的三种方式:
        1.字长精度
        2.对象精度
        3.卡精度(卡页)

G1

        G1是卡精度实现的RSet,每个Region都附有一个RSet,RSet中的元素用于记录地址去指向某个Region内的一块特定大小的内存块,这些内存块是Region的组成单位,称为卡页(card page)。 

        当判断某个Region内是否有对象被其它Region所引用时,只需要遍历所属的RSet元素即可,如果发现记录了CardPage说明正在被引用,即当前谁指向了我(point-in) 

        JVM对于卡页的维护也是通过写屏障,即当对象引用发生变更时,不只有三色标记的更新还有Card Table的更新。

RSet多种引用情况下的记录描述:

引用描述     RSet是否记录原因描述
分区内部的引用不记录因为是针对一个分区进行的垃圾回收,要么这个分区被回收,要么不被回收。
新生代引用新生代不记录G1的三种回收算法(YGC/MIXED GC/FULL GC)都会全量处理新生代分区,所以新生代都会被遍历到。因此无需记录这种引用关系。
新生代引用老年代不记录G1的YGC回收新生代,无需这个引用关系。混合GC时,G1会采用新生代分区作为根,那么在遍历新生代分区时就能找到老年代分区了,无需这个引用关系。对于FGC来说,所有分区都会被处理,也无需这个引用关系。
老年代引用新生代记录YGC在回收新生代时,如果新生代的对象被老年代引用,那么需要标记为存活对象。即此时的根对象有两种,一个是栈空间/全局变量的引用,一个是老年代到新生代的引用。

老年代引用老年代

记录混合GC时,只会回收部分老年代,被回收的老年代需要正确的标记哪些对象存活。


G1的RSet解读_g1 rset_淹不死的水的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/stone_yw/article/details/105982148?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169085969416800186524187%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169085969416800186524187&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-105982148-null-null.142%5Ev91%5Econtrol_2,239%5Ev12%5Econtrol2&utm_term=RSet&spm=1018.2226.3001.4187

CMS

        由于Young GC + CMS GC是物理分区,只需要为Young Gen配置一个RSet即可。这是一种point-out(外部指针),即我指向了谁。

Card Table

        JVM会维护一个全局数组,即Card Table ,每个元素占用1 B,每个元素下标都对应一个Card Page。如果Card Page有跨代引用,元素就需要打上标记,就是dirty(因此只需1B空间)。而Card Page 实际就是堆中的大小固定的内存块,JVM会将一个连续的堆空间分割成大量Card Page,每个Card Page默认占用内存 512 B。

        例如在一个大小为 1 GB 的堆下,Card Table 的长度为 2097151 (1GB / 512B)。

G1        

        Region由Card Page构成,同样以 1 GB 的堆举例,每个 Region 大小为 1 MB,即每个 Region 由 2048 个 Card Page 构成。

        每个 Region 附有一个 RSet,RSet 通过 Hash 表实现,这个 Hash 表的key指向引用方的 Region,而value是一个数组,数组的元素是引用方对象所对应的 Card Page 在 Card Table 中的下标,也就是说数组元素存的是Card Table下标。

【注:】

        G1中虽然有大量的RSet实现,但是都在共享Card Table资源,如果Card Table的某个元素已经标记dirty则不需要再次标记

G1的region、cardtable和rset的关联icon-default.png?t=N7T8https://juejin.cn/post/7030409449034645541

CMS

        与G1不同,JVM只会为Old Gen划分Card Page,RSet实现是Card Table。

        除了Young GC 时处理跨代引用,CMS GC在并发预清理阶段也会用到,这是为了防止老年代对象发生变更引发漏标

谈一下对hotspot虚拟机中CMS收集器要设置Mod Union Table的理解_chaisencs的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/chaisencs/article/details/84277222?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163540428716780269848425%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163540428716780269848425&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~baidu_landing_v2~default-4-84277222.pc_v2_rank_blog_default&utm_term=Mod-Union+Table&spm=1018.2226.3001.4450

Dirty Card

         当Card Table的某个元素被dirty标记,对应的Card Page即为Dirty Card。Dirty Card是为了Young GC时能够快速定位到Old Gen有哪些Card出现了跨代引用而设计,不需要再扫描整个Old Gen,提升了回收效率。

        Dirty Card 和三色标记的维护都是通过写屏障实现:例如对象A的属性指向对象B,这时写屏障会判断对象A是否是老年代,对象B是否是新生代,是则在cardtable找到对象A的位置标记为dirty,还有就是判断对象A是否是黑/灰,对象B是否是白来实现漏标处理

Dirty Card & 三色标记

        dirty card 负责溯源:即找到需要重新梳理的GC Roots;

        三色标记 负责垃圾标记:黑色表示对象完全被扫描;灰色表示对象本身完全被扫描但是引用对象还未完全被扫描;白色表示完全没被扫描

CSet

        收集集合(Collection Set,G1独有)存储待回收的一系列目标region。在筛选回收阶段分批次回收CSet所有region。

        CSet分为两种:
        1.CSet of Young Collection:CSet of Young Collection 回收 Young Region 跟 Survivor Region;
        2.CSet of Mix Collection:CSet of Mix Collection 则是筛选回收 OldGen和 Humongous,通过RSet计算Region中对象的活跃度,活跃阈值参数:-XX:G1MixedGCLiveThresholdPercent(默认85%),只有高于这个阈值才准进入CSet,还可通过-XX:G1OldCSetRegionThresholdPercent(默认10%)设置占整个堆的比例上限。

ParNew

触发时机

  1. ParNew负责新生代的垃圾回收,当新生代空间不足则触发该GC(eden + survivor from -> survivor to + tenuring)
  2. system.gc()引发full gc、metaSpace引发full gc等

GC流程

  1. 空间分配担保机制判断是否可行性;
  2. 从class存储区的static变量、栈中的ordinary object map、JNI、dirty card汇总GCRoots;
  3. 复制对象到survivorto和老年代(存活对象年龄+1,如果达到15复制到老年代,如果survivorto空间不足则提前晋升);

晋升机制

  1. 对象晋升:复制阶段from区域的对象年龄+1达到15的存活对象会复制到老年代
  2. 提前晋升:如果survivor to的内存在此过程中存满了,则启动动态年龄规划,如存活对象的年龄有1、2、3、4,把年龄为1的所有对象的内存累加值计为s1,年龄2的所有对象的内存累加值计为s2,如果s1+s2的空间>survivor 的一半,那么年龄>=2的存活对象就直接都晋升到老年代
  3. 大对象:大对象直接存到老年代,可通过-XX:PretenureSizeThreshold设置大对象size

CMS

介绍

        CMS:是一个低延时GC,为了使用户随时并发访问jvm进程,感知不到gc回收,CMS 使用并发清理,即和业务线程并发运行,消除了以往大内存垃圾回收时STW引发的用户长久等待问题,大内存也使得jvm进程并发量得以提升。CMS是标记清除算法,因此清理速度极快

        安全点:到达安全点会更新一次ordinary object map,该map记录了线程对对象的引用,而且不惟一,每个栈帧可能有多个。

        安全点位置:

                方法返回之前

                调用某个方法之后

                抛出异常的位置

                循环的末尾

        安全点更新:线程到安全点,则通信所有线程一起更新ordinary object map

        安全区域:如果安全点更新期间有线程处于中断,则线程进入安全区域,即一直中断直到外面所有的线程更新成功

        阶段切换:不管是CMS GC还是Mixed GC,每个阶段都会以某一个统一的安全点为界

【注:】安全点和安全区域是一种状态标识,安全点用于运行态线程,安全区域用于中断线程 

触发时机

        1、轮询机制:通过CMSWaitDuration(参数默认2000ms)设置时间定期检测,若发现满足条件,就进行 CMS

                1.1、参数CMSInitiatingOccupancyFraction 默认92,即老年代空间使用92%触发CMS

                1.2、参数CMSScavengeBeforeRemark 修饰时,判断survivor to 是否为空,不为空则触发CMS。这里提一句,CMSScavengeBeforeRemark表示CMS的remark前要去执行一次YGC,而YGC判断to space不为空标记为ygc失败,这又导致死循环触发CMS,只能去除此参数JVM发生频繁 CMS GC,罪魁祸首是这个参数!icon-default.png?t=N7T8https://blog.csdn.net/zl1zl2zl3/article/details/89087327

        2、promotion failure:YGC promotion failure 触发CMS。这里也是一个重要的点,较以往STW的GC来说,比如OldGC如果是Searial Old,那么promition failure触发的将是Full GC

CMS GC流程

  1. CMS-initial-mark 初始标记,STW,速度很快,获取GC ROOTS
  2. CMS-concurrent-mark 并发阶段,由于并发的使用引入了三色标记(通过增量更新解决漏标),可与应用线程并发执行。新生代可能在此期间存满引发YGC,如果引发YGC会打断当前阶段直至YGC结束
  3. CMS-preclean 阶段目的是深扫描增量更新后由黑变灰的对象,同样使用到了 card marking,这是为了找到前面 concurrent-marking 阶段被应用线程并发修改的增量更新,它们会作为GC Roots进行三色标记的遍历,是防止漏标的一部分。
    1. 遍历Dirty Card、Mod UnionTable(位图,每个元素都指向一个card page,记录card page状态,并发阶段和YGC阶段都需要Dirty Card,当Dirty Card在某一阶段被clean则另一阶段就无法再次使用,所以要备份到Mod Union Table 以供另一阶段能扫描到);
    2. 将Dirty Card备份到Mod Union Table;
    3. 清理dirty card标记并执行根可达算法。
  4. CMS-concurrent-abortable-preclean:concurrent-abortable-preclean 阶段目的是减轻 final remark 阶段(会暂停应用线程)的负担,这个阶段同样会对 dirty card 的扫描/清理,和 concurrent-preclean 的区别在于,concurrent-abortable-preclean 会重复地以迭代的方式执行,直到满足退出条件。
    1. CMSScheduleRemarkEdenSizeThreshold CMSScheduleRemarkEdenPenetration,默认值分别是2M、50%,即eden空间使用超过2M时启动可中断的并发预清理(CMS-concurrent-abortable-preclean),直到eden空间使用率达到50%时中断,进入cms-remark阶段。
    2. 满足了 1 的退出条件后不会立即进入cms-remark阶段,此时希望发生一次minor gc,以减轻 cms-remard 阶段对新生代扫描压力,因此CMS提供了参数 CMSMaxAbortablePrecleanTime ,默认为5s (并发嘛,不寒颤),5s 内如果没有minor gc 则进入cms-remark阶段。
  5. CMS-remark 最终标记,STW(需要重获GC Roots并执行根可达算法,大部分根对象和依赖对象是没问题,小部分需要标记和重新标记)
    1. 如果CMS-concurrent-abortable-preclean阶段在退出时并未执行minor gc,大概率说明新生代无需清理,当然也可以设置 -XX:+CMSScavengeBeforeRemark 强行在该阶段前先minor gc;
    2. 这时候已经趋于稳定,引用变动幅度很小,最后获取GC Roots并执行根可达算法;
  6. CMS-concurrent-sweep 并发清除(非STW,把白色集合的对象全部清理,保留黑色集合的所有对象)
  7. CMS-concurrent-reset 并发重设状态等待下次CMS的触发

【注:】

  1. CMS GC过程中GC Roots指向整个堆(young gen、old gen),即使young gen的对象不可用,只要其指向old gen,cms依然认其为gc root;
  2. 如果年轻代比较大,导致扫描时长比较长,可以设置参数 XX:+CMSScavengeBeforeRemark,即在重新标记之前进行一次YGC
  3. STW类的OldGC一旦执行就要STW,因此要在关键时刻用,这个关键时刻就是Full GC,而且只有Full GC可以触发Old GC;而CMS GC使用了并发清理,不存在STW,就是说CMSGC执行不会影响业务线程执行,因此CMS GC多了很多触发机制去预防OldGen过高的空间占用。比如可以设置-XX:+UseCMSInitiatingOccupancyOnly,即开启OldGen的阈值,通过-XX:CMSInitiatingOccupancyFraction 设置阈值,当OldGen阈值超标就触发CMSGC

Full GC

        ParNew+Searial Old,只有发生concurrent mode failure才会转为ParNew+Searial Old

CMS产生的问题:promotion failed和concurrent mode failure,及解决方案_cms concurrent mode failure_wa_c777的博客-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/ScorpC/article/details/114626650#:~:text=%E8%A7%A3%E5%86%B3%E8%BF%99%E4%B8%AA%E9%97%AE%E9%A2%98%E7%9A%84%E9%80%9A%E7%94%A8%E6%96%B9%E6%B3%95%E6%98%AF%20%EF%BC%9A%20%E8%B0%83%E4%BD%8E%E8%A7%A6%E5%8F%91CMS%20GC%E6%89%A7%E8%A1%8C%E7%9A%84%E9%98%80%E5%80%BC%20%EF%BC%8CCMS,GC%E8%A7%A6%E5%8F%91%E4%B8%BB%E8%A6%81%E7%94%B1CMSInitiatingOccupancyFraction%E5%80%BC%E5%86%B3%E5%AE%9A%EF%BC%8C%E9%BB%98%E8%AE%A4%E6%83%85%E5%86%B5%E6%98%AF%E5%BD%93%E6%97%A7%E7%94%9F%E4%BB%A3%E5%B7%B2%E7%94%A8%E7%A9%BA%E9%97%B4%E4%B8%BA68%25%E6%97%B6%EF%BC%8C%E5%8D%B3%E8%A7%A6%E5%8F%91CMS%20GC%EF%BC%8C%E5%9C%A8%E5%87%BA%E7%8E%B0concurrent%20mode%20failure%E7%9A%84%E6%83%85%E5%86%B5%E4%B8%8B%EF%BC%8C%E5%8F%AF%E8%80%83%E8%99%91%E8%B0%83%E5%B0%8F%E8%BF%99%E4%B8%AA%E5%80%BC%EF%BC%8C%E6%8F%90%E5%89%8DCMS%20GC%E7%9A%84%E8%A7%A6%E5%8F%91%EF%BC%8C%E4%BB%A5%E4%BF%9D%E8%AF%81%E6%97%A7%E7%94%9F%E4%BB%A3%E6%9C%89%E8%B6%B3%E5%A4%9F%E7%9A%84%E7%A9%BA%E9%97%B4%E3%80%82

CMS优点

        低延时,适合稍大内存服务器,适合部署高并发jvm进程

CMS缺点

  1. 需要重新标记阶段通过STW 兜底,低效;
  2. 浮动垃圾可能引发concurrent mode failure,导致Searial Old参与Full GC引发STW; 
  3. CMS垃圾回收算法是标记清除,会引起碎片问题,需要设置FullGC执行次数来触发内存压缩:
    1. -XX:+UseCMSCompactAtFullCollection        //开启fullgc后整理功能
    2. -XX:CMSFullGCsBeforeCompaction        //然后设置连续fullgc的次数以压缩一次old gen,因此并不是每次fullgc都会压缩一次old gen

推荐博文:jvm 优化篇-(8)-跨代引用问题(RememberSet、CardTable、ModUnionTable、DirtyCard)icon-default.png?t=N7T8https://www.jianshu.com/p/f1ff4ab0fed7

文中提到Mod Union Table,再推荐一篇Mod Union Table文章参考icon-default.png?t=N7T8https://blog.csdn.net/chaisencs/article/details/84277222?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522163540428716780269848425%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=163540428716780269848425&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~baidu_landing_v2~default-4-84277222.pc_v2_rank_blog_default&utm_term=Mod-Union+Table&spm=1018.2226.3001.4450

物理分代通用的常用参数

堆设置
-Xms:初始堆大小
-Xmx:最大堆大小
-Xmn:新生代大小
-XX:NewRatio:设置新生代和老年代的比值。如:为3,表示年轻代与老年代比值为1:3
-XX:SurvivorRatio:新生代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:为3,表示Eden:Survivor=3:2,一个Survivor区占整个新生代的1/5  
-XX:MaxTenuringThreshold:设置转入老年代的存活次数。如果是0,则直接跳过新生代进入老年代
-XX:PermSize、-XX:MaxPermSize:分别设置永久代最小大小与最大大小(Java8以前)
-XX:MetaspaceSize、-XX:MaxMetaspaceSize:分别设置元空间最小大小与最大大小(Java8以后)
收集器设置
-XX:+UseSerialGC:设置串行收集器
-XX:+UseParallelGC:设置并行收集器
-XX:+UseParalledlOldGC:设置并行老年代收集器
-XX:+UseConcMarkSweepGC:设置并发收集器
垃圾回收统计信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器设置
-XX:MaxTenuringThreshold=n:设置最大晋升年龄
-XX:ParallelGCThreads=n:设置并行收集器收集时使用的CPU数。并行收集线程数。
-XX:MaxGCPauseMillis=n:设置并行收集最大暂停时间
-XX:GCTimeRatio=n:设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)
-XX:ScavengeBeforeFullGC:fullgc前执行一次ygc
并发收集器设置
-XX:+UseCMSInitiatingOccupancyOnly:开启老年代阈值
-XX:CMSInitiatingOccupancyFraction:设置老年代阈值如80%(oldGenUsedMem/oldGenFreeMem)
-XX:CMSScavengeBeforeRemark:重新标记前执行一次ygc
-XX:ScavengeBeforeFullGC:fullgc前执行一次ygc
-XX:+UseCMSCompactAtFullCollection:开启fullgc压缩
-XX:CMSFullGCsBeforeCompaction:设置连续fullgc的次数以压缩一次old gen
-XX:+CMSIncrementalMode:设置为增量模式。适用于单CPU情况。
-XX:ParallelGCThreads=n:设置并发收集器新生代收集方式为并行收集时,使用的CPU数。并行收集线程数

G1

介绍

        G1是一个低延时+高吞吐的GC,逻辑分代,物理分区(Region 分eden、survivor from、survivor to、 old和 humongous。其中humongous是存放大对象的old 分区,属old的特殊划分),就是说一个Region在这轮GC中逻辑上可能是新生代,下轮可能就是老年代,根据需求动态调整;

        region num默认2048个,region size等大,根据机器自动设置region size,参数G1HeapRegionSize可以显式调整region size,范围是1-32m,且必须=log2N

        G1如果同时设置了暂停时间和年轻代大小,比如Xmn/MaxNewSize/NewRatio等,并且年轻代最大和最小值一样,那么相当于禁用了region动态调整的功能,可能会导致暂停时间失效。因为region的清理速度要看region数量、size和机器性能,和暂停时间没有任何关系。所以G1不要去设置年轻代的大小,让G1根据设置的暂停时间去自动规划年轻代的region num和size,开发者只需要去设置下阈值,如G1MaxNewSizePercent=50%

Young GC 触发时机

  1. G1NewSizePercent的默认值是5%,G1MaxNewSizePercent的默认值是60%,即heap最高扩展到60%的新生代空间,超过就触发Young GC
  2. Mixed GC的初始标记阶段

Young GC流程

  1. 扫描根:找到class存储区的static变量、栈中的ordinary object map、JNI中引用的新生代对象;
  2. 更新 RSet:处理 dirty card queue 中的 card,更新 RSet。应用状态下对象变更不会立即更新RSet,因为上下文切换影响效率,所以引入了dirty card queue,即变脏的card的地址存入queue,当queue压力变大则处理数据,当queue压力小就不处理,直到要YGC之际,彻底清空 queue 来更新 RSet;
  3. 处理 RSet:识别被老年代对象指向的 Eden 中的对象,这些被指向的 Eden 中的对象被认为是存活的对象。
  4. 复制对象:此阶段汇集了1和2的对象构成对象树,遍历对象树,Eden 区内存段中存活的对象会被复制到 Survivor 区中空的内存分段,Survivor 区内存段中存活的对象如果年龄未达阈值,年龄会加1,达到阀值会被会被复制到 old 区中空的内存分段。如果 Survivor 空间不够,Eden 空间的部分数据会直接晋升到老年代空间。
  5. 处理引用:处理 Soft,Weak,Phantom,Final,JNI Weak 等引用。最终 Eden 空间的数据为空,GC 停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。

Mixed GC触发时机

        默认老年代(old+humongus)占据堆内存的45%就要触发Mixed GC;

        需要注意的是G1 如果Young GC 失败会触发full gc;

Mixed GC流程

        1.初始标记(STW):从GC Roots标记直接可达的对象,Mixed的定义是年轻代+大对象+部分老年代,因而在这个阶段先捎带完成年轻代GC再开始标记,整个阶段STW

        2.根区域扫描:G1 GC在初始标记的存活区域扫描对老年代的引用,并且标记被引用的对象,该阶段和应用程序并发进行(根扫描涉及到survivor区域,如果有YGC发生会影响survivor变动,因此此阶段禁止YGC)

50.819: [GC concurrent-root-region-scan-start]
51.408: [GC concurrent-root-region-scan-end, 0.5890230]
...
350.994: [GC pause (young)    # young gc阻塞,等待根扫描结束
351.093: [GC concurrent-root-region-scan-end, 0.6100090]
351.093: [GC concurrent-mark-start],0.37559600 secs]

Mixed GC - 初始标记(根分区扫描)源码分析icon-default.png?t=N7T8https://blog.csdn.net/FightingITPanda/article/details/121857944        3.并发标记:G1 GC在整个堆中查找存活对象并标记。该阶段和应用线程并发进行,可以被STW年轻代垃圾回收器中断

        4.最终标记(STW):重新获取gcroots执行根扫描、补标

        5.筛选清除(STW):将堆内高性价比的部分垃圾(年轻的和老年代统一存入Collection Set处理)分批次清理,G1的吞吐量可控,可由GCPauseMillis设置每次停顿时间

G1垃圾收集器原理剖析 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/6947136493458554888

G1 垃圾收集器 - 掘金 (juejin.cn)icon-default.png?t=N7T8https://juejin.cn/post/6932040158602756109

Full GC

        G1 FullGC 是全堆执行Serial Old,如果是在大内存场景,Full GC的收集代价将超过以往任何GC。

  1. YGC、MixedGC下,年轻代分区转移存活对象失败

  2. MixedGC下,老年代分区转移存活对象时无法找到可用的空闲分区

  3. MixedGC下,分配大对象时在老年代无法找到足够的连续分区

 优点

  1. 回收算法统一:所有region均执行复制算法;
  2. 软实时,参数动态规划:垃圾分批回收,参数会根据内存状态动态变更;
  3. 低延迟和高吞吐:G1不仅实现了低延时,还能控制停顿时间,使用者可以手动指定单次筛选回收的时间片,毫秒级别。
  4. 大内存,无碎片

缺点

  1. G1垃圾回收器是逻辑分代,每个region分配一个card table,默认要维护2048个card table,需要空置出15-20%的空间来放置这些数据;
  2. 浮动垃圾问题,原始快照虽然降低了扫描深度但是增加了产生浮动垃圾的概率;
  3. 筛选回收数据默认是八次回收,也就是说为了控制响应速度,需要把垃圾多批清理,这个过程有可能OOM引发Full GC
  4. FullGC是全堆STW,代价太大,建议尽量使用默认G1MaxNewSizePercent、InitiatingHeapOccupancyPercent
1、-XX:MaxGCPauseMillis:
暂停时间,默认值200ms。这是一个软性目标,G1会尽量达成,如果达不成,会逐渐做自我调整。对于Young GC来说,会逐渐减少Eden区个数,减少Eden空间那么Young GC的处理时间就会相应减少;对于Mixed GC,G1会调整每次Choose Cset的比例,默认最大值是10%,当然每次选择的Cset少了,所要经历的Mixed GC的次数会相应增加。同时减少Eden的总空间时,就会更加频繁的触发Young GC,也就是会加快Mixed GC的执行频率,因为Mixed GC是由Young GC触发的,或者说借机同时执行的。频繁GC会对对应用的吞吐量造成影响,每次Mixed GC回收时间太短,回收的垃圾量太少,可能最后GC的垃圾清理速度赶不上应用产生的速度,那么可能会造成串行的Full GC,这是要极力避免的。所以暂停时间肯定不是设置的越小越好,当然也不能设置的偏大,转而指望G1自己会尽快的处理,这样可能会导致一次全部并发标记后触发的Mixed GC次数变少,但每次的时间变长,STW时间变长,对应用的影响更加明显。

2、-XX:GCTimeRatio=nnn:
表示在GC花费不超过应用程序执行时间的1/(1+nnn),nnn为大于0小于100的整数。
默认情况下,设置此值为99,运行用户代码时间是GC停顿时间的99倍,即GC最大花费时间比率为1%

【官方对MaxGCPauseMillis和GCTimeRatio的使用建议】
尽量不设置最大堆,选择合适的目标吞吐量
如果可以达到吞吐量目标,但是暂停时间太长,请选择一个暂停时间目标进行折衷,尽管这样会降低吞吐量
如果未达到吞吐量目标,意味着堆不够用导致垃圾回收频繁,请设置尽可能大的堆(小于物理可用内存)

3、-XX:G1HeapRegionSize:
Region大小,若未指定则默认最多生成2048块,每块的大小需要为2的幂次方,最大值为32M。Region的大小主要是关系到Humongous Object的判定,当一个对象超过Region大小的一半时,则为巨型对象,那么其会至少独占一个Region,如果一个放不下,会占用连续的多个Region。当一个Humongous Region放入了一个巨型对象,可能还有不少剩余空间,但是不能用于存放其他对象,这些空间就浪费了。如果应用里有很多大小差不多的巨型对象,可以适当调整Region的大小,尽量让他们以普通对象的形式分配,合理利用Region空间。

4、-XX:G1NewSizePercent、-XX:G1MaxNewSizePercent:
新生代比例有两个数值指定,下限:-XX:G1NewSizePercent,默认值5%,上限:-XX:G1MaxNewSizePercent,默认值60%。G1会根据实际的GC情况(主要是暂停时间)来动态的调整新生代的大小,主要是Eden Region的个数。最好是Eden的空间大一点,毕竟Young GC的频率更大,大的Eden空间能够降低Young GC的发生次数。但是Mixed GC是伴随着Young GC一起的,如果暂停时间短,那么需要更加频繁的Young GC,同时也需要平衡好Mixed GC中新生代和老年代的Region,因为新生代的所有Region都会被回收,如果Eden很大,那么留给老年代回收空间就不多了,最后可能会导致Full GC。

5、-XX:ConcGCThreads:
非STW期间的GC工作线程数,默认是-XX:ParallelGCThreads/4。当并发时间过长时,可以尝试调大GC工作线程数,但是这也意味着此期间应用所占的线程数减少,会对吞吐量有一定影响。

6、-XX:ParallelGCThreads:
并行GC线程数,也就是在STW阶段工作的GC线程数,其值遵循以下原则:
① 如果用户显式指定了ParallelGCThreads,则使用用户指定的值。
② 否则,需要根据实际的CPU所能够支持的线程数来计算ParallelGCThreads的值,
  计算方法见步骤③和步骤④。
③ 如果物理CPU所能够支持线程数小于8,则ParallelGCThreads的值为CPU所支持的线程数。这里的阀值为8, 
  是因为JVM中调用nof_parallel_worker_threads接口所传入的switch_pt的值均为8。
④ 如果物理CPU所能够支持线程数大于8,则ParallelGCThreads的值为8加上一个调整值,调整值的计算方式为:物理CPU所支持的线程数减去8所得值的5/8或者5/16,JVM会根据实际的情况来选择具体是乘以5/8还是5/16。
比如,在64线程的x86 CPU上,如果用户未指定ParallelGCThreads的值,则默认的计算方式为:ParallelGCThreads = 8 + (64 - 8) * (5/8) = 8 + 35 = 43。

7、-XX:G1MixedGCLiveThresholdPercent:被纳入Cset的Region的存活空间占比阈值,不同版本默认值不同,有65%和85%。在全局并发标记阶段,如果一个Region的存活对象的空间占比低于此值,则会被纳入Cset。此值直接影响到Mixed GC选择回收的区域,当发现GC时间较长时,可以尝试调低此阈值,尽量优先选择回收垃圾占比高的Region,但此举也可能导致垃圾回收的不够彻底,最终触发Full GC。

8、-XX:G1OldCSetRegionThresholdPercent:CSet设置Mixed GC回收的Region最大比例,默认10%,也就是每轮Mixed GC附加的Cset的Region不超过全部Region的10%,最多10%,如果暂停时间短,那么可能会少于10%。一般这个值不需要额外调整。

9、触发全局并发标记的老年代使用占比:通过-XX:InitiatingHeapOccupancyPercent指定,默认值45%,也就是老年代占堆的比例超过45%。如果Mixed GC周期结束后老年代使用率还是超过45%,那么会再次触发全局并发标记过程,这样就会导致频繁的老年代GC,影响应用吞吐量。同时老年代空间不大,Mixed GC回收的空间肯定是偏少的。可以适当调高IHOP的值,当然如果此值太高,很容易导致年轻代晋升失败而出发Full GC,所以需要多次调整测试。

10、触发Mixed GC的堆垃圾占比:通过-XX:G1HeapWastePercent指定,默认值5%,也就是在全局标记结束后能够统计出所有Cset内可被回收的垃圾占整对的比例值,如果超过5%,那么就会触发之后的多轮Mixed GC,如果不超过,那么会在之后的某次Young GC中重新执行全局并发标记。可以尝试适当的调高此阈值,能够适当的降低Mixed GC的频率。

11、Mixed GC 最大分批回收次数:通过-XX:G1MixedGCCountTarget指定,默认值8。也就是在一次全局并发标记后,最多接着8此Mixed GC,也就是会把全局并发标记阶段生成的Cset里的Region拆分为最多8部分,然后在每轮Mixed GC里收集一部分。这个值要和上一个参数配合使用,8*10%=80%,应该来说会大于每次标记阶段的Cset集合了。一般此参数也不需额外调整。

12、G1为分配担保预留的空间比例:通过-XX:G1ReservePercent指定,默认10%。即老年代会空闲10%的空间给新生代的对象晋升,如果经常发生新生代晋升失败导致Full GC,那么可以适当调高此阈值。但是调高此值同时也意味着降低了老年代的实际可用空间。

13、-XX:SoftRefLRUPolicyMSPerMB:如果SoftReference过多,会有频繁的老年代收集。可以指定每兆堆空闲空间的软引用的存活时间,默认值是1000,也就是1秒。可以调低这个参数来触发更早的回收软引用。如果调高的话会有更多的存活数据,可能在GC后堆占用空间比会增加。 对于软引用,还是建议尽量少用,会增加存活数据量,增加GC的处理时间。

14、-XX:MaxTenuringThreshold:
晋升年龄阈值,默认值15。一般新生对象经过15次Young GC会晋升到老年代

15、-XX:TargetSurvivorRatio:
默认50%,survivorto的对象按照年龄从低到高累加对象size,当size超过该参数时会提前晋升>=参与累加对象的最大年龄的对象到老年代,一般这个值不需要调整
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是几个 JVM 垃圾回收相关的面试题及其答案: 1. JVM 垃圾回收都有哪些? JVM 垃圾回收主要分为以下几种:串行垃圾回收、并行垃圾回收、CMS 垃圾回收、G1 垃圾回收等。 2. 串行垃圾回收和并行垃圾回收的区别是什么? 串行垃圾回收和并行垃圾回收的主要区别在于垃圾回收的方式。串行垃圾回收是单线程执行的,即在垃圾回收过程中只有一个线程在执行,而并行垃圾回收是多线程执行的,即在垃圾回收过程中可以有多个线程同时执行。 3. CMS 垃圾回收的特点是什么? CMS 垃圾回收是一种以最短回收停顿时间为目标的垃圾回收。它采用分代收集算法,在回收老年代时,采用标记-清除算法,并发标记和并发清除,以减少垃圾回收的停顿时间,提高系统的响应速度。 4. G1 垃圾回收的特点是什么? G1 垃圾回收是一种面向服务端应用的垃圾回收,它采用分代收集算法,在回收堆内存时,采用标记-整理算法。它具有以下特点:高效、可预测、可配置、可并发、可暂停等。 5. 垃圾回收的主要算法有哪些? 垃圾回收主要采用以下几种算法:标记-清除算法、复制算法、标记-整理算法、分代算法等。 以上是一些常见的 JVM 垃圾回收面试题及其答案,希望能对你有所帮助。在面试过程中,需要根据具体的问题进行回答,同时也需要对垃圾回收的原理和实现有清晰的认识,才能更好地回答相关的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值