垃圾收集器ParNew&CMS与底层三色标记算法详解&G1

发布于:2025-06-30 ⋅ 阅读:(15) ⋅ 点赞:(0)

垃圾收集算法

在这里插入图片描述

分代收集理论

根据对象存活周期的不同将内存分为几块,一般将 Java 堆分为新生代和老年代,可以为新生代和老年代选择不同的算法。

一般可以为新生代选择标记-复制算法,为老年代选择标记-清除或者标记-整理算法。

标记-复制算法

将内存平分为相同的两块,每次只向其中一块放入对象。当这一块内存满了,把这一块还存活的对象放入另一块,然后清空这一块内存。

缺点:

浪费内存空间,有一半的内存都不会被存放对象。

在这里插入图片描述

标记-清除算法

  • 标记存活的对象,统一回收未被标记的对象。(一般使用这种方式)
  • 标记需要回收的对象,统一删除被标记的对象。

缺点:
1、如果需要标记的对象太多,会有效率问题,效率不高。
2、会产生大量内存碎片。

在这里插入图片描述

标记-整理算法

和标记-清除算法不同的是,标记之后,让所有存活的对象向一端移动,存活对象占用可回收对像的内存;如果存活对象比可回收对象少,再把未被覆盖的垃圾对象清除掉。
在这里插入图片描述

垃圾收集器

在这里插入图片描述

Serial (-XX:+UseSerialGC -XX:+UseSerialOldGC)

只会使一条垃圾收集线程,并且会暂停其他工作线程(STOP THE WORLD)。

新生代采用复制算法,老年代采用标记-整理算法。

在这里插入图片描述

Parallel Scavenge收集器 (-XX:+UseParallelGC(年轻代),-XX:+UseParallelOldGC(老年代))

和 Serial 不同的是,使用多线程进行垃圾收集。默认的线程数和 CPU 核数相同。

新生代采用复制算法,老年代采用标记-整理算法。关注点是吞吐量, 即 CPU 中运行用户代码的时间与 CPU 总消耗时间的比值。

JDK8 默认的新生代和老年代收集器。
在这里插入图片描述

ParNew 收集器(-XX:+UseParNewGC)

ParNew 和 Parallel 不同点是 ParNew 可以和 CMS 配合使用,但是 Parallel 不可以。

在这里插入图片描述

CMS收集器(-XX:+UseConcMarkSweepGC(old))

CMS (Concurrent Mark Sweep) 收集器是一种以获取最短回收停顿时间为目标的收集器,他非常注重用户体验,是 HotSpot 虚拟机第一款真正意义上的并发收集器,第一次实现了让垃圾收集线程和用户线程基本上同时工作。

在这里插入图片描述

  • 初始标记:会 STOP THE WORLD,只记录 gc roots 可以直接引用的对象,速度很快。
  • 并发标记:不会 STOP THE WORLD,查找 gc roots 直接引用对象的引用,即开始遍历整个对象图。这个过程耗时较长,但是和用户线程并行运行。可能会出现已经标记过的对象的状态出现改变。
  • 重新标记:会 STOP THE WORLD,这个过程会修正并发标记期间,因为用户继续运行而导致状态变化那些对象的状态(主要处理漏标问题,用到三色标记)。这个阶段暂停时间比初始标记快,比并发标记慢。
  • 并发清理:清理未被标记的对象。
  • 并发重置:重置本次 GC 过程中的标记数据。即清理和重置本轮 GC 过程中用于标记对象可达性的相关数据结构,如标记位图、内部状态信息等,以便为下一轮 GC 做准备。

缺点:
1、并发标记和并发清理的时候回和用户应用程序抢资源。
2、并发标记和并发清理阶段产生的垃圾无法处理,只能等下一次 GC 再清理。
3、CMS 使用 “标记-清理” 回收算法,这个算法会产生大量空间碎片。但是可以使用 -XX:+UseCMSCompactAtFullCollection 让 JVM 在执行完标记清除后再做整理。
4、如果上次垃圾回收还没执行完,再次触发垃圾回收(在并发标记和并发清理阶段会出现)。可能会进入 STOP THE WORLD,这时改用 Serial Old 垃圾收集器进行垃圾收集(一定要避免这种情况出现)。

CMS的相关核心参数

-XX:+UseConcMarkSweepGC:启用cms
-XX:ConcGCThreads:并发的GC线程数
-XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
-XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)
-XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整
-XX:+CMSScavengeBeforeRemark:用于在CMS垃圾收集器的重新标记阶段(Remark)之前,强制触发一次年轻代的垃圾回收。其目的是减少需要扫描的对象数量,从而缩短重新标记阶段的停顿时间。
-XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW
-XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;

垃圾收集底层算法实现

三色标记

三色标记法是一种用于垃圾回收算法中的对象标记方法,特别用于标记-清除型垃圾回收器。这种方法通过使用三种颜色(白色、灰色和黑色)来跟踪对象的可达性和垃圾回收状态,以避免对象的重复回收和丢失,可以解决并发标记过程中漏标的问题。

三色标记的基本概念
● 白色:表示对象尚未被检查。白色对象可能是垃圾,直到证明它们是可达的。
● 灰色:表示对象被检查过,并且其本身是可达的,但其引用的对象还未全部检查。
● 黑色:表示对象和它所有引用的对象都已检查且是可达的。

三色标记步骤

  1. 初始化:所有对象开始时都是白色。
  2. 标记开始:从GC Roots开始,根对象标记为灰色。
  3. 扫描灰色对象:
    ● 将灰色对象引用的所有白色对象标记为灰色。
    ● 然后将该灰色对象标记为黑色。
  4. 重复步骤3直到没有更多的灰色对象。
  5. 清除:未标记为黑色的对象为白色,即垃圾,可被回收。

使用三色标记的好处
● 避免循环引用:能够正确识别循环引用对象不被误判为不可回收。
● 并发性增强:支持并发标记,减少“Stop-the-World”时间。
● 渐进性:可以执行增量垃圾回收,减小程序暂停。
在实际的垃圾回收算法实现中,如G1和CMS,三色标记法被广泛应用,用于高效地管理内存和提高程序运行的性能。

漏标产生的原因
在并发标记期间,黑色跟节点增加的对白色节点的引用。这个时候 JVM 不会重新扫描黑色节点,不知道这个白色节点已经变色了。

漏标解决方式

  • 增量更新

当黑色节点增加对白色节点的引用时,将这个新的引用记录下来。
并发扫描结束后,以记录中黑色对象为根,再重新扫描一次。

  • 原始快照

灰色对象在成为白色对象之前,把删除的引用记录下来。
并发扫描结束后,以记录中灰色对象为根,再重新扫描一次,扫描到的白色对象直接标记为黑色,如果是浮动垃圾,等待下一轮 GC。

写屏障

增量更新和原始快照都是通过写屏障实现的。

写屏障就是指在赋值操作前后,把要删除或者新加的引用记录下来。

G1收集器(-XX:+UseG1GC)

G1 主要针对配备多个处理器和大容量内存的机器。在满足 GC 停顿时间的要求同时,具备高吞吐量。
在这里插入图片描述
G1 保留了年轻代和老年代的概念,但不再是物理隔阂了,每个空闲 Region 既可以存放年轻代的数据可以存放老年代的数据。

G1 将 Java 堆划分成多个大小相等的独立区域(Region),推荐分成 2048 个。

默认年轻代占堆内存 5%,在系统运行过程中,如果对象增多,会占用更多的 Region,但是默认不会超过总内存的 60%,

-XX:G1NewSizePercent:设置新生代初始占比
-XX:G1MaxNewSizePercent:设置新生代最多占比

G1 年轻代中 Eden 和 Survivor 的占比默认是 8:1:1,对象转移到老年代的触发条件也和之前几个收集器相同。

不同的是对大对象的处理,G1 专门分配大对象的 Region 叫 Humongous 区。当这个对象的大小超过一个 Region 大小的 50%,就被判定为大对象。如果对象太大,可能会横跨多个 Region 来存放。

Humongous 专门存放短期巨型对象,不用直接进入老年代,可以节约老年代的空间,避免因为老年代空间不够而产生的 GC 开销。

Full GC 时候也会回收 Humongous。

G1收集器一次GC步骤

在这里插入图片描述

  • 初始标记: 暂停所有的其他线程(STW),记录下 gc roots 直接引用的对象。 不再向下继续寻找,速度很快。
  • 并发标记: 同 CMS 的并发标记。
  • 最终标记: 同 CMS 的重新标记。
  • 筛选回收: 首先对各个 Region 的回收价值和成本进行排序(根据相同时间可以回收垃圾的多少进行排序),根据用户所期望的 GC 停顿 STW 时间来制定回收计划。

假设把垃圾全部回收完需要 300ms,但是设置的停顿时间是 200ms,那么就回收 200ms 的垃圾,剩余的垃圾下次再回收。
-XX:MaxGCPauseMillis:设置停顿时间,默认 200ms。

不管是年轻代或是老年代,回收算法主要用复制算法,即将一个 region 中存活的对象赋值到另一个 region 中。优点是不会有太多的内存碎片

YoungGC & MixedGC & Full GC
YoungGC
当 Eden 区回收时间和 -XX:MaxGCPauseMillis 设定的值差不多才会触发 YoungGC。

MixedGC
老年代堆的占有率达到 -XX:InitiatingHeapOccupancyPercent 这个参数设置的值会触发。

回收所有的年轻代,部分老年代以及大对象区。

使用复制算法发现没有足够的空间供存活对象拷贝时会触发 Full GC。

Full GC
停止系统程序(STW),使用单线程进行清理。

G1使用场合

  • 50%以上的堆被存活对象占用
  • 对象分配和晋升的速度变化非常大
  • 垃圾回收时间特别长,超过1秒
  • 8GB以上的堆内存(建议值)
  • 停顿时间是500ms以内

G1收集器参数设置

-XX:+UseG1GC:使用G1收集器
-XX:ParallelGCThreads:指定GC工作的线程数量
-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区
-XX:MaxGCPauseMillis:目标暂停时间(默认200ms)
-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%,值配置整数,默认就是百分比)
-XX:G1MaxNewSizePercent:新生代内存最大空间
-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
-XX:MaxTenuringThreshold:最大年龄阈值(默认15)
-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能就要触发MixedGC了
-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这个值,存活对象过多,回收的的意义不大。
-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
-XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。


网站公告

今日签到

点亮在社区的每一天
去签到