经典垃圾回收器和现代垃圾回收器
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 的主要阶段
- 初始标记(Initial Mark) :STW,标记 GC Roots 直接可达对象。
- 根区域扫描(Root Region Scanning) :并发,扫描新生代存活对象引用的老年代对象。
- 并发标记(Concurrent Marking) :与应用线程并发,遍历整个堆,标记所有存活对象。
- 重新标记(Remark) :STW,修正并发标记期间对象引用的变化。
- 清理(Cleanup) :并发,统计各 Region 垃圾比例,回收空 Region。
- 复制/回收(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。
三、调优建议
- 设置期望最大停顿时间 让 G1 尽量将单次 GC 停顿控制在 200ms 内。
-XX:MaxGCPauseMillis=200
-
合理设置堆和 Region 大小
- 堆越大,GC 越高效,但单次回收耗时也可能增加。
- Region 大小可用
-XX:G1HeapRegionSize=8m
调整(一般无需手动设置)。
-
调整 Mixed GC 触发阈值 老年代占用 45% 时触发混合回收,防止老年代膨胀。
-XX:InitiatingHeapOccupancyPercent=45
-
关注 Humongous 对象
- 大对象频繁分配会导致碎片和 Full GC,尽量避免大对象直接分配到堆。
-
开启详细 GC 日志 便于分析每次 GC 的耗时和效果
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log
-
监控 Full GC 和 Evacuation Failure
- Full GC 说明 G1 无法满足内存回收需求,需关注日志并优化内存使用或参数。
- Evacuation Failure(晋升失败)需加大堆或优化对象生命周期。
总结:
- 关注 GC 停顿时间、堆使用变化、Full GC 和 Humongous 对象分配。
- 通过调整
MaxGCPauseMillis
、堆大小、Mixed GC 阈值等参数,结合日志分析,优化 G1 GC 性能和应用响应性。
G1 详细执行阶段
一、G1 GC 的主要阶段
G1 GC 的回收过程主要分为以下几个阶段:
- 初始标记(Initial Mark)
- 根区域扫描(Root Region Scanning)
- 并发标记(Concurrent Marking)
- 重新标记(Remark)
- 清理(Cleanup)
- 复制/回收(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 作为回收目标。
-
回收过程:
- 标记存活对象。
- 把存活对象复制到新的 Region(通常是 Survivor 或其他空闲 Region)。
- 原 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 的主要阶段和算法
-
初始标记(Initial Mark)
- 标记 GC Roots 直接可达对象,STW,耗时极短。
- 算法:可达性分析。
-
根区域扫描(Root Region Scanning)
- 扫描新生代的 Survivor 区,标记其中引用的老年代对象。
- 算法:可达性分析,增量标记。
-
并发标记(Concurrent Marking)
- 遍历整个堆,标记所有可达对象,统计每个 Region 的存活对象数量。
- 算法:三色标记法,采用并发可达性分析。
-
重新标记(Remark)
- 修正并发标记期间对象引用的变化,确保标记准确。
- 算法:三色标记法的补充标记,通常用 SATB 或增量更新。
- STW,耗时较短。
-
清理(Cleanup)
- 统计各 Region 的垃圾比例,回收完全无存活对象的 Region,准备后续回收。
- 算法:标记-清除,只清理空 Region,不移动对象。
-
复制/回收(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 开始标记为 deprecated | JDK9 起默认 GC |
二、CMS 的主要缺点
-
内存碎片问题严重
- CMS 采用标记-清除算法,回收后不会整理内存,导致老年代容易产生碎片。
- 碎片多时,晋升失败会触发 Full GC,导致长时间 STW。
-
无法预测停顿时间
- CMS 不能根据业务需求设定最大停顿时间,停顿不可控。
-
浮动垃圾
- 并发标记期间产生的新垃圾对象要等下次 GC 才能回收,导致老年代空间利用率降低。
-
CPU 资源竞争
- 并发回收会与应用线程争抢 CPU,影响吞吐量。
-
维护和调优复杂
- 参数多,调优难度大,容易出错。
-
JDK 官方已弃用
- JDK9 开始 CMS 被标记为 deprecated,后续版本将移除。
三、G1 的优势
-
可预测的低停顿
- 支持
-XX:MaxGCPauseMillis
,GC 会根据目标自动调整回收行为,停顿时间更可控。
- 支持
-
Region 设计,回收更灵活
- 堆被划分为多个 Region,G1 可以只回收垃圾最多的 Region,提升效率。
-
标记-整理算法,几乎无碎片
- 回收时会整理内存,避免碎片问题。
-
混合回收(Mixed GC)
- 可以同时回收新生代和部分老年代,提升老年代回收效率。
-
更智能的调优和监控
- 日志详细,参数更智能,便于自动调优和问题定位。
-
官方推荐与持续优化
- 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,是因为它更智能、更高效、更易用,适合绝大多数生产场景。