Java JVM 垃圾回收器(二):现代垃圾回收器 之 CMS 和 G1

11 阅读26分钟

经典垃圾回收器和现代垃圾回收器

CMS 及以后的垃圾回收器被称为“现代垃圾回收器”,CMS 之前的通常称为“传统垃圾回收器”或“经典垃圾回收器”。


为什么这样区分?

1. 并发与低停顿
  • 传统垃圾回收器(如 Serial GC、Parallel GC)在回收时会长时间 Stop-The-World(STW),所有应用线程暂停,影响响应速度。
  • 现代垃圾回收器(从 CMS 开始,如 CMS、G1、ZGC、Shenandoah)引入了并发标记、并发清理等机制,GC 线程和应用线程可以同时工作,大大降低了停顿时间,提升了应用的响应性。
2. 适应多核和大堆
  • 现代 GC 更好地支持多核 CPU 和大内存堆,能在大规模服务端场景下保持良好性能。
3. 更智能的算法
  • 现代 GC 引入了三色标记法、SATB、分区回收、预测停顿等更智能的算法和机制,能更好地平衡吞吐量与延迟。

总结

  • CMS 之前:Serial GC、Parallel GC,称为“传统”或“经典”垃圾回收器,特点是全停顿、简单高效但不适合低延迟场景。
  • CMS 及以后:CMS、G1、ZGC、Shenandoah,称为“现代垃圾回收器”,特点是并发、低停顿、适合大堆和多核环境。

本质区别
现代垃圾回收器的目标是降低应用停顿时间,提高响应性和可扩展性,而传统垃圾回收器更关注实现简单和吞吐量。

CMS GC(Concurrent Mark Sweep,并发标记清除 GC)

一、什么是 CMS GC?

CMS GC(Concurrent Mark Sweep)是 Java 早期主流的低停顿垃圾回收器,主要用于老年代(Old Generation)
它的目标是最小化 GC 停顿时间,提升应用的响应性,适合对延迟敏感的服务端应用。

  • 启用参数:-XX:+UseConcMarkSweepGC
  • 新生代通常配合 ParNew GC(并行复制算法)

二、运行机制和原理

CMS GC 的老年代回收过程分为四个主要阶段

1. 初始标记(Initial Mark)

  • Stop-The-World,暂停所有应用线程。
  • 只标记从 GC Roots 直接可达的对象。
  • 时间很短。

2. 并发标记(Concurrent Mark)

  • 与应用线程并发执行
  • 从初始标记的对象出发,遍历整个对象图,标记所有可达对象。
  • 这一步不会暂停应用线程。

3. 重新标记(Remark)

  • Stop-The-World,暂停所有应用线程。
  • 由于并发标记阶段应用线程还在运行,可能有新引用关系产生,所以需要重新扫描并补充标记。
  • 采用多线程并行,时间较短。

4. 并发清除(Concurrent Sweep)

  • 与应用线程并发执行
  • 清理所有未被标记的对象,回收内存空间。
  • 这一步不会暂停应用线程。

三、CMS GC 的特点

  • 低停顿:只有初始标记和重新标记阶段会短暂 Stop-The-World,大部分回收过程与应用并发进行。
  • 并发回收:GC 线程和应用线程同时工作,减少长时间停顿。
  • 标记-清除算法:不会移动对象,回收后可能产生内存碎片。

四、常见问题与优化

1. 内存碎片

  • CMS 采用标记-清除算法,不整理内存,可能导致老年代出现大量碎片,分配大对象时失败。
  • 可以通过 -XX:+UseCMSCompactAtFullCollection 让 Full GC 时整理内存,但会有较长停顿。

2. 并发模式失败(Concurrent Mode Failure)

  • 如果在并发清除阶段,老年代空间被耗尽,JVM 会退回到 Serial Old GC(单线程标记-整理),产生长时间停顿。
  • 可通过 -XX:CMSInitiatingOccupancyFraction 提前触发 CMS GC,减少并发模式失败概率。

3. 浮动垃圾(Floating Garbage)

  • 并发标记和清除阶段,应用线程可能新建对象,这些对象不会被本次 GC 回收,只能等下次 GC。

五、常用参数

  • -XX:+UseConcMarkSweepGC:启用 CMS GC。
  • -XX:CMSInitiatingOccupancyFraction=75:老年代使用率达到 75% 时触发 CMS GC。
  • -XX:+UseCMSCompactAtFullCollection:Full GC 时整理内存,减少碎片。
  • -XX:ParallelCMSThreads=N:设置并发标记/清除线程数。

六、适用场景

  • 对响应时间要求高、希望减少 GC 停顿的服务端应用(如 Web 服务、在线交易系统等)。
  • 不适合频繁分配大对象、老年代空间紧张的场景。

七、总结

  • CMS GC 是一种以低停顿为目标的老年代垃圾回收器,采用并发标记-清除算法,大部分回收过程与应用线程并发进行。
  • 优点是停顿短,缺点是可能产生内存碎片和并发模式失败。
  • 适合对延迟敏感的服务端应用,但已被 G1 GC 等更先进的回收器逐步取代。

ParNew 垃圾回收器

ParNew 是专门为配合 CMS 老年代回收器而设计的新生代垃圾回收器。它和 CMS 一起使用时,负责新生代的垃圾回收。


一、什么是 ParNew GC?

  • ParNew GC 是新生代的并行(多线程)垃圾回收器,本质上是 Serial GC 的多线程版本。
  • 主要用于配合 CMS GC,因为 CMS 只能和 ParNew 或 Serial GC 搭配,不能和 Parallel Scavenge(Parallel GC)搭配。

二、ParNew 的运行机制和原理

1. 分代结构

  • 新生代分为 Eden 区和两个 Survivor 区(From、To),和其他新生代 GC 一样。

2. 复制算法(Copying)

  • ParNew 采用复制算法

    • 大部分新对象分配在 Eden 区。
    • 当 Eden 区满时,触发 Minor GC。
    • 存活对象从 Eden 和 From Survivor 区复制到 To Survivor 区,年龄增加,达到阈值后晋升到老年代(由 CMS 管理)。
    • GC 结束后,From/To 区角色互换。

3. 多线程并行回收

  • ParNew 支持多线程(通过 -XX:ParallelGCThreads 设置线程数,默认与 CPU 核心数相关)。
  • 多个 GC 线程同时参与对象标记、复制和清理,大大提升了新生代回收效率,尤其在多核环境下。

4. Stop-The-World

  • ParNew GC 期间,所有应用线程都会被暂停(Stop-The-World),只有 GC 线程在工作。

三、ParNew 的优缺点

优点

  • 多线程并行,回收速度快,适合多核服务器。
  • 与 CMS GC 完美配合,是 CMS 唯一支持的多线程新生代回收器。

缺点

  • 只能配合 CMS 使用,不能和 Parallel GC(吞吐量优先 GC)一起用。
  • 停顿时间依然存在,不适合极低延迟场景。

四、常用参数

  • -XX:+UseParNewGC:启用 ParNew GC(通常和 -XX:+UseConcMarkSweepGC 一起用)。
  • -XX:ParallelGCThreads=N:设置 GC 线程数。

五、适用场景

  • 需要低停顿、对响应时间敏感的服务端应用,且老年代用 CMS GC。
  • 多核服务器环境。

六、总结

  • ParNew GC 是新生代的多线程并行回收器,采用复制算法,专为配合 CMS 老年代回收器设计。
  • 适合多核、低延迟需求的服务端应用。
  • 现代 JVM 推荐使用 G1、ZGC 等更先进的回收器,CMS/ParNew 已逐步被淘汰。

G1 GC(Garbage First GC)

一、什么是 G1 GC?

G1 GC(Garbage First GC)是 Oracle HotSpot JVM 推出的面向服务端、低停顿、高吞吐量的垃圾回收器。
它的设计目标是可预测的低停顿时间,适合大堆内存和对响应时间有要求的应用。

  • JDK9 及以后,G1 GC 成为默认垃圾回收器。
  • 启用参数:-XX:+UseG1GC

二、G1 GC 的核心原理

1. 分区(Region-based)管理堆内存

  • G1 GC 将整个堆划分为多个大小相等的Region(区域),每个 Region 一般为 1~32MB。
  • 新生代、老年代不再是物理连续的大块空间,而是由若干 Region 组成的逻辑集合。
  • Region 可以动态在新生代和老年代之间切换角色。

2. 分代收集

  • G1 依然有新生代(Young)和老年代(Old)的概念,但它们由不同的 Region 动态组成。
  • 新生代 GC(Young GC)和老年代 GC(Mixed GC)都以 Region 为单位进行。

3. 并发与并行

  • G1 GC 支持多线程并行回收(Parallel),也支持与应用线程并发执行(Concurrent),大大降低了 Stop-The-World 停顿时间。

4. 预测停顿目标

  • G1 GC 允许用户通过参数(如 -XX:MaxGCPauseMillis=200)指定期望的最大 GC 停顿时间,G1 会尽量满足这个目标。

5. 优先回收垃圾最多的 Region(Garbage First)

  • G1 GC 会优先选择“垃圾最多”的 Region 进行回收,最大化每次 GC 的回收效率。

三、G1 GC 的主要回收过程

1. Young GC(新生代回收)

  • 回收新生代 Region,采用多线程复制算法,存活对象晋升到 Survivor 或老年代 Region。

2. Mixed GC(混合回收)

  • 回收新生代 Region,同时回收部分老年代 Region(优先垃圾最多的)。
  • 这是 G1 的核心创新,能在不全堆回收的情况下,逐步清理老年代,避免 Full GC。

3. Full GC(全堆回收)

  • 当内存压力过大或特殊情况时,G1 也会触发 Full GC,回收整个堆(效率比 CMS、Parallel Old 更高)。

4. 并发标记(Concurrent Marking)

  • G1 会在后台并发标记所有存活对象,统计每个 Region 的“垃圾比例”,为后续 Mixed GC 做准备。

四、G1 GC 的主要阶段

  1. 初始标记(Initial Mark) :STW,标记 GC Roots 直接可达对象。
  2. 根区域扫描(Root Region Scanning) :并发,扫描新生代存活对象引用的老年代对象。
  3. 并发标记(Concurrent Marking) :与应用线程并发,遍历整个堆,标记所有存活对象。
  4. 重新标记(Remark) :STW,修正并发标记期间对象引用的变化。
  5. 清理(Cleanup) :并发,统计各 Region 垃圾比例,回收空 Region。
  6. 复制/回收(Evacuation/Collection) :STW,多线程复制存活对象,回收垃圾最多的 Region。

五、G1 GC 的优缺点

优点

  • 可预测的低停顿时间(可配置)。
  • 支持大堆内存(数十 GB 以上)。
  • 并发与并行,充分利用多核 CPU。
  • 避免老年代碎片问题。

缺点

  • 实现复杂,调优参数较多。
  • 在极端低延迟场景下,停顿时间可能不如 ZGC、Shenandoah。
  • Mixed GC 期间,应用吞吐量可能略有下降。

六、常用参数

  • -XX:+UseG1GC:启用 G1 GC。
  • -XX:MaxGCPauseMillis=200:期望最大 GC 停顿时间(毫秒)。
  • -XX:InitiatingHeapOccupancyPercent=45:并发标记的触发阈值(堆使用率)。
  • -XX:G1HeapRegionSize=8m:Region 大小。

七、总结

  • G1 GC 是面向服务端、低停顿、高吞吐量的垃圾回收器,采用 Region 分区、并发并行、优先回收垃圾最多的区域等机制,适合大堆、对响应时间有要求的应用。
  • 通过合理调优参数,可以兼顾吞吐量和停顿时间。

关于 G1 的一些问题

-XX:InitiatingHeapOccupancyPercent=45

-XX:InitiatingHeapOccupancyPercent=45 是什么?

  • 这个参数控制 G1 GC 并发标记周期(Concurrent Marking Cycle)的触发时机
  • 它的含义是:当整个堆(包括新生代和老年代)的使用率达到 45% 时,G1 GC 会启动并发标记周期,为后续的 Mixed GC 做准备。

不是普通的 Minor GC 触发阈值

  • 这个参数**不是控制普通 Minor GC(新生代 GC)**的触发时机。
  • 只影响 G1 的并发标记和后续 Mixed GC,即老年代的垃圾回收准备阶段。

为什么要提前触发?

  • 提前启动并发标记,可以让 G1 有足够的时间在老年代空间耗尽前,完成标记和回收,避免频繁 Full GC
  • 如果设置得太高,可能来不及回收,导致 Full GC;设置得太低,GC 频率会增加,影响吞吐量。

总结

-XX:InitiatingHeapOccupancyPercent=45 表示当整个堆使用率达到 45% 时,G1 GC 会启动并发标记周期,为老年代的回收做准备,不是普通 Minor GC 的触发阈值。

G1 日志样例,调优建议

G1 GC 日志样例

2024-06-14T10:00:00.123+0800: 1.234: [GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0156789 secs]
   [Parallel Time: 12.3 ms, GC Workers: 4]
      [GC Worker Start (ms): Min: 1234.5, Avg: 1235.0, Max: 1235.5, Diff: 1.0]
      [Ext Root Scanning (ms): Min: 0.5, Avg: 0.7, Max: 0.9, Diff: 0.4]
      [Update RS (ms): Min: 1.0, Avg: 1.2, Max: 1.4, Diff: 0.4]
      [Scan RS (ms): Min: 2.0, Avg: 2.2, Max: 2.4, Diff: 0.4]
      [Object Copy (ms): Min: 5.0, Avg: 5.5, Max: 6.0, Diff: 1.0]
      [Termination (ms): Min: 2.0, Avg: 2.2, Max: 2.4, Diff: 0.4]
   [Code Root Fixup: 0.1 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.1 ms]
   [Other: 2.2 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 1.0 ms]
      [Ref Enq: 0.1 ms]
      [Redirty Cards: 0.2 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
   [Eden: 32.0M(32.0M)->0.0B(32.0M) Survivors: 0.0B->1024.0K Heap: 100.0M(256.0M)->70.0M(256.0M)]
 [Times: user=0.04 sys=0.00, real=0.01 secs]

二、日志详细解析

  • GC pause (G1 Evacuation Pause) (young) (initial-mark):
    说明本次是 G1 的新生代回收(young),并且是初始标记阶段(initial-mark),发生了 Stop-The-World(STW)停顿。
  • Parallel Time: 12.3 ms, GC Workers: 4:
    并行回收耗时 12.3ms,使用了 4 个 GC 线程。
  • [GC Worker Start ...] 到 [Termination ...]:
    详细展示每个 GC 线程在各阶段的耗时,便于分析并行效率。
  • [Eden: 32.0M(32.0M)->0.0B(32.0M)]:
    Eden 区从 32M 回收到 0,说明新生代对象全部被回收或晋升。
  • Survivors: 0.0B->1024.0K:
    Survivor 区从 0 增加到 1024K,说明有部分对象晋升到 Survivor 区。
  • Heap: 100.0M(256.0M)->70.0M(256.0M):
    堆使用从 100M 降到 70M,总堆大小 256M。
  • [Times: user=0.04 sys=0.00, real=0.01 secs]:
    用户态、内核态和实际耗时,实际停顿 10ms。

三、调优建议

  1. 设置期望最大停顿时间 让 G1 尽量将单次 GC 停顿控制在 200ms 内。
-XX:MaxGCPauseMillis=200
  1. 合理设置堆和 Region 大小

    • 堆越大,GC 越高效,但单次回收耗时也可能增加。
    • Region 大小可用 -XX:G1HeapRegionSize=8m 调整(一般无需手动设置)。
  2. 调整 Mixed GC 触发阈值 老年代占用 45% 时触发混合回收,防止老年代膨胀。

-XX:InitiatingHeapOccupancyPercent=45
  1. 关注 Humongous 对象

    • 大对象频繁分配会导致碎片和 Full GC,尽量避免大对象直接分配到堆。
  2. 开启详细 GC 日志 便于分析每次 GC 的耗时和效果

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log
  1. 监控 Full GC 和 Evacuation Failure

    • Full GC 说明 G1 无法满足内存回收需求,需关注日志并优化内存使用或参数。
    • Evacuation Failure(晋升失败)需加大堆或优化对象生命周期。

总结:

  • 关注 GC 停顿时间、堆使用变化、Full GC 和 Humongous 对象分配。
  • 通过调整 MaxGCPauseMillis、堆大小、Mixed GC 阈值等参数,结合日志分析,优化 G1 GC 性能和应用响应性。

G1 详细执行阶段

一、G1 GC 的主要阶段

G1 GC 的回收过程主要分为以下几个阶段:

  1. 初始标记(Initial Mark)
  2. 根区域扫描(Root Region Scanning)
  3. 并发标记(Concurrent Marking)
  4. 重新标记(Remark)
  5. 清理(Cleanup)
  6. 复制/回收(Evacuation/Collection)

这些阶段会在不同类型的 GC(Young GC、Mixed GC、Full GC)中以不同组合出现。


二、各阶段详细说明及采用的算法

1. 初始标记(Initial Mark)
  • 作用:标记从 GC Roots 直接可达的对象。
  • 算法可达性分析(Root Tracing) ,只处理新生代存活对象。
  • 特点:Stop-The-World,耗时极短。
  • 触发时机:通常在 Young GC 或 Mixed GC 开始时。

2. 根区域扫描(Root Region Scanning)
  • 作用:扫描新生代的 Survivor 区,标记其中引用的老年代对象。
  • 算法可达性分析,增量标记。
  • 特点:与应用线程并发进行。

3. 并发标记(Concurrent Marking)
  • 作用:遍历整个堆,标记所有可达对象,统计每个 Region 的存活对象数量。
  • 算法三色标记法(Mark-Sweep 的标记阶段) ,采用并发可达性分析。
  • 特点:与应用线程并发进行,极大减少停顿。

4. 重新标记(Remark)
  • 作用:修正并发标记期间对象引用的变化,确保标记准确。
  • 算法三色标记法的补充标记,通常用 SATB(Snapshot-At-The-Beginning)或增量更新。
  • 特点:Stop-The-World,耗时较短,采用多线程并行。

5. 清理(Cleanup)
  • 作用:统计各 Region 的垃圾比例,回收完全无存活对象的 Region,准备后续回收。
  • 算法标记-清除(Mark-Sweep) ,只清理空 Region,不移动对象。
  • 特点:大部分并发进行,只有极短的 STW。

6. 复制/回收(Evacuation/Collection)
  • 作用:将存活对象从回收目标 Region 复制到新的 Region,回收垃圾最多的 Region。
  • 算法复制算法(Copying/Scavenge) ,多线程并行。
  • 特点:Stop-The-World,但可控且并行,回收效率高。

三、G1 GC 的回收类型与阶段组合

  • Young GC:只回收新生代 Region,采用复制算法。
  • Mixed GC:回收新生代和部分老年代 Region,采用复制算法。
  • Full GC:极少发生,采用标记-整理(Mark-Compact)算法,Stop-The-World。

四、总结表

阶段主要算法是否并发说明
初始标记可达性分析STW,标记 GC Roots
根区域扫描可达性分析并发,扫描 Survivor 区
并发标记三色标记法并发,遍历整个堆
重新标记三色标记法(补充标记)STW,多线程,修正引用变化
清理标记-清除是/否并发为主,极短 STW
复制/回收复制算法STW,多线程,回收目标 Region
Full GC标记-整理STW,极少发生

总结:
G1 GC 通过分阶段、分区、并发与并行的方式,结合可达性分析、三色标记、复制算法等多种回收算法,实现了高效、低停顿的垃圾回收。每个阶段采用最适合的算法,兼顾了吞吐量和响应时间。

G1 内存碎片问题

1. G1 GC 的“标记-清理”与“整理”

  • G1 GC 的正常回收(Young GC、Mixed GC) :采用复制算法,把存活对象从回收的 Region 复制到新的 Region,这样不会产生碎片。
  • G1 GC 的并发标记和清理阶段:确实是“标记-清理”,这一步只是统计和回收完全空的 Region,不会移动对象,也不会整理内存。
  • G1 GC 的 Full GC 阶段:采用标记-整理(Mark-Compact)算法,会移动对象,整理内存,消除碎片。

2. G1 会不会有内存碎片?

  • 正常情况下,G1 通过复制算法和 Region 回收,大部分时间不会有严重的内存碎片,因为对象总是在 Region 之间整体搬迁。
  • 但在极端情况下,如果老年代 Region 中存活对象分布极不均匀,且无法腾出足够的空 Region,可能会出现碎片化,导致大对象分配失败。
  • 此时 G1 会触发 Full GC,用标记-整理算法整理内存,消除碎片。

3. 总结

  • G1 GC 绝大多数情况下不会有严重的内存碎片问题,因为它通过复制和 Region 管理避免了碎片。
  • 只有在极端情况下,碎片过多时才会触发 Full GC 进行整理。

关于 G1 Region

1. 什么是“空的 Region”?

  • G1 GC 把堆划分为很多 Region。
  • 当某个 Region 里的对象全部都不可达(即都成了垃圾),这个 Region 就变成了“空 Region”。

2. 空 Region 是怎么回收的?

  • 空 Region 的回收非常简单
    G1 只需要把这个 Region 标记为“空闲”,以后可以直接用于新对象分配,无需复制或移动任何对象。
  • 不会有对象复制
    因为里面已经没有存活对象了,所以不需要把对象复制到别的 Region。

3. 不是空的 Region 怎么回收?

  • 对于非空的 Region(即还有存活对象的 Region),G1 会在 Mixed GC 或 Young GC 时,把这些 Region 作为回收目标。

  • 回收过程

    1. 标记存活对象。
    2. 把存活对象复制到新的 Region(通常是 Survivor 或其他空闲 Region)。
    3. 原 Region 变成“空 Region”,下次可以重新分配。

4. 总结

  • 空 Region:直接标记为可用,无需复制对象。
  • 非空 Region:存活对象会被复制到其他 Region,原 Region 回收后变为空 Region。

一句话总结:
G1 回收空 Region 时,不需要复制对象,只需把 Region 标记为空闲即可;只有有存活对象的 Region 才会发生对象复制和整理。

G1 空 Region 的出现

空 region 的出现频率通常不会特别高,但也不是极少见,具体情况取决于应用的对象生命周期和内存使用模式。


1. 为什么空 region 不会特别多?

  • 大多数 Region 都有存活对象:在实际应用中,尤其是老年代 Region,往往会有一些长期存活的对象,导致 Region 很难完全变空。
  • 新生代 Region 更容易变空:新生代对象“朝生夕死”,Minor GC 后有些 Eden 或 Survivor Region 可能会完全变空,但这些 Region 很快会被重新分配。

2. 什么时候会出现空 region?

  • 新生代 GC 后:Eden 区的对象大多被回收,Eden Region 可能会变成空 Region。
  • Mixed GC/老年代回收后:如果某个老年代 Region 里的对象全部被回收(比如大对象生命周期结束),这个 Region 会变成空 Region。
  • 应用对象生命周期短且分布集中时:比如批量处理、缓存清理等场景,某些 Region 可能会一次性被清空。

3. 现实中的比例

  • 新生代空 Region 出现较多,但会很快被复用。
  • 老年代空 Region 出现较少,因为老年代对象存活率高,Region 很难完全腾空。

总结:
空 Region 在新生代更常见,在老年代较少。G1 的设计就是通过不断整理和复制,让更多 Region 变为空闲,提高内存利用率,但在实际业务中,完全空的 Region 并不会特别多。

关于 G1 的一些理解

G1 收集器是把整个堆分为多个 region,每个 region 存放新生代或老年代,并且会有个标志表明是新生代还是老年代。垃圾回收的时候,会回收所有的 region,新生代和老年代分别采用不同的回收算法,把存活的对象尽量的放在同一个 region 里,原来的 region 就有可能变成空 region,下次直接在空的 region 里分配新的对象,是直接覆盖的,空的 region 也不用回收,直接把新对象覆盖上去就行了。

1. Region 的分配和角色

  • G1 GC 会把整个堆划分为许多 Region,每个 Region 在某一时刻只属于新生代(Eden/Survivor)或老年代。
  • 每个 Region 都有标志,表明当前属于新生代还是老年代。

2. 回收过程

  • Young GC:只回收新生代 Region(Eden 和 Survivor),采用复制算法,把存活对象复制到新的 Survivor 或老年代 Region。
  • Mixed GC:回收新生代 Region,同时回收部分老年代 Region(优先垃圾最多的),同样采用复制算法。
  • Full GC:极少发生,回收整个堆,采用标记-整理算法。

3. 空 Region 的利用

  • 回收后,如果某个 Region 没有存活对象,就变成“空 Region”。
  • 空 Region 不需要再回收,下次分配新对象时,直接把新对象分配到空 Region,原内容会被覆盖。
  • G1 会优先在空 Region 分配新对象,提升内存利用率。

4. 不同 Region 不会混合存放新生代和老年代对象

  • 每个 Region 只存放一种类型的对象(新生代或老年代),不会混合。

总结:
你的理解是对的:G1 把堆分成多个 Region,每个 Region 只属于新生代或老年代。回收时采用复制算法,存活对象被搬迁,原 Region 可能变成空 Region,空 Region 直接用于新对象分配,无需额外回收操作。

G1 重新总结整理

一、G1 GC 简介

G1 GC(Garbage First Garbage Collector)是 Oracle HotSpot JVM 推出的面向服务端、低停顿、高吞吐量的垃圾回收器。
它的设计目标是可预测的低停顿时间,适合大堆内存和对响应时间有要求的应用。JDK9 及以后,G1 GC 成为默认垃圾回收器。


二、Region 的划分与管理

  • G1 GC 会把整个堆划分为许多大小相等的 Region(区域),每个 Region 一般为 1~32MB。
  • 每个 Region 在某一时刻只属于新生代(Eden/Survivor)或老年代(Old) ,不会混合存放。
  • JVM 会为每个 Region 维护一个标志,表明它当前属于新生代还是老年代。
  • 新生代和老年代的对象不会混合存放在同一个 Region

三、Region 的分配与回收

  • 新对象分配:优先分配到 Eden Region,Eden Region 用完后分配到空 Region。
  • Young GC(新生代回收) :只回收新生代 Region(Eden 和 Survivor),采用复制算法,把存活对象复制到新的 Survivor 或老年代 Region。
  • Mixed GC(混合回收) :回收新生代 Region,同时回收部分老年代 Region(优先垃圾最多的),同样采用复制算法。
  • Full GC:极少发生,回收整个堆,采用标记-整理算法。

四、空 Region 的利用

  • 回收后,如果某个 Region 没有存活对象,就变成“空 Region”。
  • 空 Region 不需要再回收,下次分配新对象时,直接把新对象分配到空 Region,原内容会被覆盖。
  • G1 会优先在空 Region 分配新对象,提升内存利用率。

五、G1 GC 的主要阶段和算法

  1. 初始标记(Initial Mark)

    • 标记 GC Roots 直接可达对象,STW,耗时极短。
    • 算法:可达性分析。
  2. 根区域扫描(Root Region Scanning)

    • 扫描新生代的 Survivor 区,标记其中引用的老年代对象。
    • 算法:可达性分析,增量标记。
  3. 并发标记(Concurrent Marking)

    • 遍历整个堆,标记所有可达对象,统计每个 Region 的存活对象数量。
    • 算法:三色标记法,采用并发可达性分析。
  4. 重新标记(Remark)

    • 修正并发标记期间对象引用的变化,确保标记准确。
    • 算法:三色标记法的补充标记,通常用 SATB 或增量更新。
    • STW,耗时较短。
  5. 清理(Cleanup)

    • 统计各 Region 的垃圾比例,回收完全无存活对象的 Region,准备后续回收。
    • 算法:标记-清除,只清理空 Region,不移动对象。
  6. 复制/回收(Evacuation/Collection)

    • 将存活对象从回收目标 Region 复制到新的 Region,回收垃圾最多的 Region。
    • 算法:复制算法,多线程并行,STW。

六、G1 GC 的优缺点

优点:

  • 可预测的低停顿时间(可配置)。
  • 支持大堆内存。
  • 并发与并行,充分利用多核 CPU。
  • 避免老年代碎片问题,空 Region 可直接复用。

缺点:

  • 实现复杂,调优参数较多。
  • 极端低延迟场景下,停顿时间可能不如 ZGC、Shenandoah。

七、总结

  • G1 GC 通过将堆划分为多个 Region,每个 Region 只属于新生代或老年代,结合复制算法和并发标记,实现了高效、低停顿的垃圾回收。
  • 回收后变成空的 Region 可以直接用于新对象分配,无需额外回收操作,极大提升了内存利用率和分配效率。
  • G1 GC 适合大堆、对响应时间有要求的服务端应用,是现代 JVM 的主流选择之一。

CMS 和 G1 对比

下面详细对比 CMS 和 G1 垃圾回收器,并解释为什么 CMS 被逐步淘汰、JDK 默认选择 G1。


一、CMS 与 G1 的核心对比

特性CMS(Concurrent Mark Sweep)G1(Garbage-First)
堆结构新生代+老年代,物理分区整个堆划分为多个 Region,逻辑分区
新生代算法ParNew(多线程复制)G1 自己实现的复制算法
老年代算法标记-清除(Mark-Sweep)标记-整理(Mark-Compact)
并发回收支持(标记、清理阶段并发)支持(标记、清理阶段并发)
停顿预测不支持支持(可配置最大停顿时间)
内存碎片易产生碎片,需 Full GC 整理标记-整理,几乎无碎片
大对象处理直接进入老年代Humongous Region 专门处理
回收粒度整个老年代Region 级别,按需回收
日志与监控日志较简单日志详细,易于分析
调优复杂度参数多,调优复杂参数较少,调优更智能
JDK支持JDK9 开始标记为 deprecatedJDK9 起默认 GC

二、CMS 的主要缺点

  1. 内存碎片问题严重

    • CMS 采用标记-清除算法,回收后不会整理内存,导致老年代容易产生碎片。
    • 碎片多时,晋升失败会触发 Full GC,导致长时间 STW。
  2. 无法预测停顿时间

    • CMS 不能根据业务需求设定最大停顿时间,停顿不可控。
  3. 浮动垃圾

    • 并发标记期间产生的新垃圾对象要等下次 GC 才能回收,导致老年代空间利用率降低。
  4. CPU 资源竞争

    • 并发回收会与应用线程争抢 CPU,影响吞吐量。
  5. 维护和调优复杂

    • 参数多,调优难度大,容易出错。
  6. JDK 官方已弃用

    • JDK9 开始 CMS 被标记为 deprecated,后续版本将移除。

三、G1 的优势

  1. 可预测的低停顿

    • 支持 -XX:MaxGCPauseMillis,GC 会根据目标自动调整回收行为,停顿时间更可控。
  2. Region 设计,回收更灵活

    • 堆被划分为多个 Region,G1 可以只回收垃圾最多的 Region,提升效率。
  3. 标记-整理算法,几乎无碎片

    • 回收时会整理内存,避免碎片问题。
  4. 混合回收(Mixed GC)

    • 可以同时回收新生代和部分老年代,提升老年代回收效率。
  5. 更智能的调优和监控

    • 日志详细,参数更智能,便于自动调优和问题定位。
  6. 官方推荐与持续优化

    • JDK9 起默认 GC,持续获得 Oracle 和 OpenJDK 的优化和支持。

四、为什么 CMS 被淘汰,JDK 默认选择 G1?

  • 技术演进:G1 解决了 CMS 的核心痛点(碎片、不可预测停顿、调优复杂)。
  • 维护成本:CMS 代码老旧,维护成本高,社区和 Oracle 更愿意投入到 G1、ZGC、Shenandoah 等新一代 GC。
  • 用户体验:G1 能更好地满足现代大堆、低延迟、高吞吐的需求。
  • 官方策略:JDK9 起 CMS 被标记为 deprecated,G1 成为默认,推动用户迁移。

五、总结

  • CMS 优点是低停顿,但有碎片、不可预测停顿、调优难等缺点,已被官方弃用。
  • G1 通过 Region、标记-整理、混合回收和停顿预测,成为现代 Java 应用的主流选择。
  • JDK 默认选择 G1,是因为它更智能、更高效、更易用,适合绝大多数生产场景。