一、垃圾回收算法
1.标记清除算法(Mark-Sweep)
分为两个阶段:
标记阶段:从根对象(如栈引用、静态变量等)出发,遍历所有可达对象并标记为 "存活"。
清除阶段:遍历整个内存区域,回收所有未被标记的对象(即垃圾),释放其占用的内存。
**优点:**实现简单,不需要移动对象。
**缺点:**效率低,记和清除过程都需要遍历所有对象,耗时较长,尤其在对象数量多的时候。
回收后会产生大量不连续的内存碎片,可能导致后续大对象无法分配内存(即使总空间足够)。
2.复制算法(Copying)
复制算法可以解决标记-清除算法的效率和内存碎片问题。
**原理:**将内存划分为两个大小相等的区域(如 From 区和 To 区),每次只使用其中一个区域(From 区)。当 From 区内存不足时,标记所有存活对象,将其复制到另一个未使用的区域(To 区),并按顺序排列(避免碎片);清除 From 区所有对象,然后交换 From 区和 To 区的角色,重复使用。
优点:效率高:只处理存活对象,复制过程中自然消除内存碎片.
适合存活对象少的场景(如新生代,因为大部分对象朝生夕灭)。
**缺点:**内存利用率低,需要预留一半内存作为复制区域,实际可用内存仅为总容量的一半。
不适合存活对象多的场景(复制成本高)。
3.标记 - 整理算法(Mark-Compact)
结合了 "标记 - 清除" 和 "复制" 的思想,分为三个阶段:
标记阶段:同标记 - 清除算法,标记所有存活对象。
整理阶段:将所有存活对象向内存一端移动,按顺序排列。
清除阶段:回收边界外的所有内存(即未标记的垃圾)。
**优点:**解决了标记 - 清除算法的内存碎片问题。
内存利用率高于复制算法(无需预留一半空间)。
缺点:效率较低,比标记 - 清除多了 "整理" 阶段(移动对象并更新引用),耗时更长。
适合存活对象多的场景(如老年代,对象存活率高)。
4.分代收集算法(Generational Collection)
不是独立的算法,而是根据对象的生命周期(存活时间)将内存划分为不同区域(如新生代、老年代),结合上述算法针对性处理。
新生代:对象存活时间短(大部分创建后很快被回收),采用 复制算法(效率高,适合低存活率场景)。新生代通常进一步分为 Eden 区和两个 Survivor 区(From/To),比例一般为 8:1:1,减少内存浪费。
老年代 :对象存活时间长(多次回收后仍存活),采用 标记 - 清除 或 标记 - 整理算法(适合高存活率场景,避免复制成本)。
优点:充分利用不同代对象的特性,提高垃圾回收效率,是目前主流 JVM(如 HotSpot)的默认策略。
二、延迟和吞吐量
先理解两个概念延迟和吞吐量
**延迟:**这里特指 "GC 停顿时间"(STW,Stop-The-World),即垃圾回收时用户线程被暂停的时间。延迟优先的目标是尽可能缩短单次 GC 的停顿时间(比如控制在 10ms 以内),避免用户感知到卡顿(如 Web 请求超时、游戏卡顿)。延迟小,GC 停顿时间短,应用程序可以更快响应用户请求。
吞吐量(Throughput):指 "用户线程运行时间占总时间的比例"(吞吐量 = 用户线程时间 /(用户线程时间 + GC 时间))。吞吐量优先的目标是尽可能让用户线程占用更多 CPU 时间,减少 GC 的总耗时占比(比如控制 GC 时间占比不超过 1%),适合后台计算、批处理等不关注实时响应的场景。
延迟优先的策略会降低吞吐量,吞吐量优先会增加延迟。
1.为什么延迟增加会保证吞吐量?
"延迟增加会保证吞吐量" 的核心逻辑是:通过允许单次 GC 的停顿时间(延迟)适当增加,减少 GC 的总次数和总耗时,从而让用户线程获得更多连续运行的时间,最终提高整体吞吐量。这里的 "延迟增加" 特指 "单次 GC 的 STW(Stop-The-World)时间变长",而吞吐量的提升源于对 "GC 总开销" 的优化。
**1.1.**GC 操作本身是有 "固定开销" 的(如初始化 GC 线程、遍历根对象、更新内存管理元数据等),这些开销与回收的垃圾量无关。若 GC 触发过于频繁(每次只回收少量垃圾),这些固定开销会被反复叠加,导致 GC 总时间急剧增加;而适当延长单次 GC 的延迟(让 GC 一次处理更多垃圾),可减少 GC 次数,从而降低固定开销的总占比。比如

**1.2.**减少 GC 对用户线程的 "干扰成本",频繁的 GC 不仅增加总时间,还会通过 "线程切换" 干扰用户线程的连续运行:频繁的 GC 不仅增加总时间,还会通过 "线程切换" 干扰用户线程的连续运行;每次 GC 触发时,CPU 需要从 "用户线程" 切换到 "GC 线程"(保存上下文、加载 GC 线程状态),GC 结束后,再从 "GC 线程" 切换回 "用户线程"(恢复上下文)。这些切换操作会消耗 CPU 资源,且会打断用户线程的缓存局部性(如 CPU 缓存中的数据因切换失效,需要重新加载),导致用户线程运行效率下降。而 "单次延迟增加、次数减少" 的策略,能减少线程切换的频率,让用户线程获得更长的连续运行时间:减少切换成本,CPU 更多时间用于实际业务计算;保持更好的缓存局部性,用户线程执行效率更高
**1.3.**避免 "碎片化导致的额外 GC 开销",吞吐量优先的 GC(如 Parallel Old)在老年代采用 "标记 - 整理算法",虽然单次整理会增加延迟(需要移动对象、更新引用),但能彻底消除内存碎片。内存碎片的危害是:即使总内存充足,大对象也可能因找不到连续空间而触发额外的 Full GC(例如,10GB 堆中有 2GB 碎片,但一个 3GB 的大对象仍无法分配,必须触发 GC 整理碎片)。这种 "为了处理碎片而额外触发的 GC" 会显著增加总 GC 时间,降低吞吐量。而允许单次延迟增加(接受整理阶段的耗时),可避免碎片累积,减少这类 "不可控的额外 GC",从而保证总 GC 时间的稳定性,间接提升吞吐量。
三、垃圾回收器
一、JDK 1.3 及之前:单线程回收器(Serial GC 时代)
这一阶段仅支持Serial GC,是 JVM 最早的垃圾回收器,无专门的新生代 / 老年代回收器划分,而是同一回收器处理整堆。
**核心回收器:**Serial GC
适用区域:同时负责新生代和老年代。
新生代处理:
算法:复制算法(单线程标记 + 复制存活对象)。
特点:回收时暂停所有用户线程(STW),因新生代对象存活率低,单次回收快但频率高。
老年代处理:
算法:标记 - 整理算法(单线程标记 + 移动存活对象)。
特点:老年代对象存活率高,单次回收耗时较长(STW 时间比新生代长),频率低。
新生代与老年代回收器的区别:
算法不同:新生代用复制(低存活场景高效),老年代用标记 - 整理(高存活场景避免碎片)。
耗时与频率:新生代回收频率高、耗时短;老年代频率低、耗时长(因对象多且需整理)。
线程模型:均为单线程(无并行 / 并发能力),STW 影响随堆大小增加而显著。
二、JDK 1.4:多线程吞吐量优先回收器登场
随着多核 CPU 普及,单线程 Serial GC 效率不足,JDK 1.4 引入Parallel Scavenge(新生代多线程回收器),老年代仍依赖 Serial Old(Serial GC 的老年代部分)。
**核心回收器组合:**Parallel Scavenge(新生代) + Serial Old(老年代)
**新生代:**Parallel Scavenge
算法:复制算法(多线程并行标记 + 复制,利用多核优势)。
特点:专注 "吞吐量"(用户线程运行时间占比),可通过 -XX:GCTimeRatio 控制 GC 时间占比;STW 时间比 Serial GC 短(多线程并行)。
**老年代:**Serial Old
算法:标记 - 整理算法(单线程执行)。
特点:单线程处理高存活对象,STW 时间长(成为吞吐量瓶颈)。
新生代与老年代回收器的区别:
线程模型:新生代多线程并行,老年代单线程串行(核心区别)。
效率瓶颈:新生代因多线程效率高,老年代单线程拖慢整体吞吐量。
适用场景:新生代适合低存活对象快速回收,老年代仅能处理小堆(因单线程限制)。
三、JDK 5:老年代并行回收器完善(Parallel GC 成熟)
JDK 5 引入Parallel Old(老年代多线程回收器),与 Parallel Scavenge 组成完整的 "吞吐量优先" 组合(Parallel GC)。
**核心回收器组合:**Parallel Scavenge(新生代) + Parallel Old(老年代)
**新生代:**Parallel Scavenge
算法:复制算法(多线程并行),与 JDK 1.4 一致,优化了线程调度。
**老年代:**Parallel Old
算法:标记 - 整理算法(多线程并行执行标记和整理)。
特点:解决了 Serial Old 单线程瓶颈,全堆多线程并行回收,吞吐量大幅提升。
新生代与老年代回收器的区别
算法差异:新生代复制(低存活场景高效),老年代标记 - 整理(高存活场景避免复制成本)。
线程协同:均为多线程并行,回收时同步 STW(无并发阶段),适合堆内存中等规模(GB 级)。
调优重点:新生代关注 "Eden 区与 Survivor 区比例",老年代关注 "整理效率"(避免大对象导致的频繁 Full GC)。
四、JDK 6:低延迟并发回收器(CMS 登场)
对于响应时间敏感的应用(如 Web 服务),Parallel GC 的长 STW 不可接受,JDK 6 引入CMS(Concurrent Mark Sweep) 作为老年代低延迟回收器,新生代搭配ParNew(专为 CMS 优化的多线程回收器)。
**核心回收器组合:**ParNew(新生代) + CMS(老年代)
**新生代:**ParNew
算法:复制算法(多线程并行,与 Parallel Scavenge 类似)。
特点:唯一能与 CMS 配合的新生代回收器(支持 "并发模式失败" 时的分配担保);STW 时间短(多线程),频率高。
**老年代:**CMS
算法:标记 - 清除算法(大部分阶段与用户线程并发)。
核心流程:初始标记(STW,快)→ 并发标记(与用户线程并行)→ 重新标记(STW,短)→ 并发清除(与用户线程并行)。
特点:STW 时间极短(仅初始和重新标记阶段),适合低延迟场景;但产生内存碎片,并发阶段占用 CPU 资源。
新生代与老年代回收器的区别
线程模型:新生代并行 STW(所有 GC 线程同时工作,暂停用户线程);老年代并发(GC 线程与用户线程同时运行,仅短时间 STW)。
算法目标:新生代追求 "快速回收短生命周期对象"(复制算法无碎片);老年代追求 "低延迟"(标记 - 清除牺牲碎片换速度)。
缺陷互补:ParNew 无碎片但需 STW;CMS 低延迟但有碎片,需定期用 Serial Old 进行 Full GC 整理(称为 "CMS 退化")。
五、JDK 7:大堆回收器 G1 实验性引入
传统回收器(如 CMS)对大堆(10GB 以上)处理效率下降(碎片、并发成本高),JDK 7 引入G1(Garbage-First),采用 "区域化" 设计,弱化新生代与老年代的物理划分。
**核心回收器:**G1(整堆回收,动态代划分)
G1 将堆划分为多个大小相等的 Region(通常 1-32MB),每个 Region 可动态扮演 Eden/Survivor/Old 角色(逻辑上的新生代 / 老年代)。
- 逻辑新生代(Eden + Survivor Region):
- 算法:复制算法(并行标记 + 复制存活对象到新 Region)。
- 特点:回收时优先处理垃圾多的 Eden Region,STW 时间短(类似 Minor GC)。
- 逻辑老年代(Old Region):
- 算法:标记 - 整理算法(结合复制,回收时将存活对象复制到新 Region 并整理)。
- 特点:通过 "混合回收"(Mixed GC)处理老年代,即同时回收部分 Old Region 和新生代,避免 Full GC;无内存碎片。
新生代与老年代(逻辑)的区别
物理划分:无固定边界,通过 Region 动态切换角色(核心区别于传统分代)。
回收策略:新生代回收(类似 Minor GC)仅处理 Eden 和 Survivor Region;老年代通过 "混合回收" 逐步处理,避免一次性回收全量老年代导致的长 STW。
目标控制 :可通过 -XX:MaxGCPauseMillis 设定目标停顿时间,G1 会优先选择垃圾占比高的 Region 回收(兼顾吞吐与延迟)。
六、JDK 8:G1 正式启用,CMS 仍为主流
JDK 8 中 G1 从实验性变为正式可用,但默认回收器仍是 Parallel GC(Parallel Scavenge + Parallel Old),CMS 仍广泛用于低延迟场景。
核心回收器组合:
默认:Parallel Scavenge(新生代) + Parallel Old(老年代)(吞吐量优先)。
可选:ParNew(新生代) + CMS(老年代)(低延迟);G1(整堆,平衡吞吐与延迟)。
新生代与老年代区别(同 JDK 6/7):
Parallel GC:新生代多线程复制,老年代多线程标记 - 整理,均并行 STW。
CMS 组合:新生代并行 STW,老年代并发标记 - 清除。
G1:逻辑代划分,动态 Region 管理,回收策略灵活。
七、JDK 9:G1 成为默认回收器
JDK 9 中 G1 替代 Parallel GC 成为默认回收器,更适合中等规模堆(10-100GB),兼顾吞吐量和延迟。
核心回收器:G1(默认)
新生代(逻辑):Eden/Survivor Region 占比动态调整(默认新生代占堆 50%,可自动优化)。
老年代(逻辑):通过混合回收逐步清理,减少 Full GC 频率。
新生代与老年代区别
与 JDK 7 的 G1 一致,核心是 "动态 Region 管理" 和 "优先回收垃圾多的区域",弱化物理代划分,更关注停顿时间控制。
八、JDK 11+:低延迟大堆回收器崛起(ZGC、Shenandoah)
JDK 11 及之后,针对 TB 级堆和毫秒级停顿需求,引入 ZGC 和 Shenandoah,彻底弱化 "新生代 / 老年代" 的传统分代模型。
1. ZGC(JDK 11 实验性,JDK 14 生产就绪)
- 设计理念:整堆 Region 划分,无严格新生代 / 老年代,通过 "颜色指针" 和 "读屏障" 实现几乎全并发回收。
- 处理方式:所有对象(无论新老)均在 Region 中管理,回收时并发标记、并发移动,STW 仅发生在初始标记和最终标记(<1ms)。
- 新老对象区别:无明确代划分,但内部通过 "年龄" 跟踪对象存活时间,优先回收短期对象(类似新生代逻辑),但无物理隔离。
2. Shenandoah(JDK 12 实验性,JDK 14 生产就绪)
- 设计理念:整堆 Region 划分,通过 "转发指针" 和 "写屏障" 实现并发整理,支持 TB 级堆。
- 处理方式:回收过程(标记、整理)几乎全并发,STW 时间极短(<10ms);无严格代划分,通过 "年龄" 优化回收顺序。
新生代与老年代的区别(ZGC/Shenandoah)
- 无物理代划分:不再区分固定的新生代 / 老年代区域,对象按存活时间动态管理(核心区别于传统 GC)。
- 回收策略统一:新对象和老对象用相同的并发算法处理,避免传统分代中 "新生代回收依赖老年代状态" 的耦合问题。
- 优化方向:通过 "年龄过滤" 优先回收短期对象(类似新生代逻辑),但无独立的复制 / 标记阶段,效率更高。
九、JDK 14 及之后:CMS 废弃,低延迟 GC 主导
- JDK 14:CMS 被标记为废弃(维护成本高),ZGC 和 Shenandoah 正式进入生产阶段。
- JDK 17(LTS):完全移除 CMS,G1 仍是默认 GC(适合中等堆),ZGC/Shenandoah 成为超大堆(TB 级)和低延迟场景的首选。
| JDK 版本 | 主流回收器组合 | 新生代回收器特点 | 老年代回收器特点 | 核心区别 |
|---|---|---|---|---|
| 1.3 及之前 | Serial GC | 单线程复制,频率高、STW 短 | 单线程标记 - 整理,频率低、STW 长 | 算法不同,均单线程 |
| 1.4 | Parallel Scavenge + Serial Old | 多线程复制,吞吐量优先 | 单线程标记 - 整理,效率低 | 新生代多线程,老年代单线程 |
| 5-8(默认) | Parallel Scavenge + Parallel Old | 多线程复制,吞吐量优先 | 多线程标记 - 整理,吞吐量优先 | 均多线程并行,算法不同(复制 vs 标记 - 整理) |
| 6-13(可选) | ParNew + CMS | 多线程复制,支持 CMS 协作 | 并发标记 - 清除,低延迟、有碎片 | 新生代并行 STW,老年代并发(核心是线程模型差异) |
| 7+(G1) | G1 | 逻辑新生代,Region 复制,动态调整 | 逻辑老年代,Region 标记 - 整理,混合回收 | 无固定物理代,动态 Region 管理,优先回收垃圾多的区域 |
| 11+(ZGC/Shenandoah) | ZGC / Shenandoah | 无物理新生代,并发回收短期对象 | 无物理老年代,并发回收长期对象 | 无代划分,统一并发算法,STW 极短 |
四、CMS垃圾回收器
CMS(Concurrent Mark Sweep,并发标记清除)是 JVM 中一款以低延迟为核心目标的老年代垃圾回收器,专为响应时间敏感的应用(如 Web 服务、实时交易系统)设计。它通过 "并发标记" 和 "并发清除" 阶段减少 STW(Stop-The-World,暂停用户线程)时间,尽可能降低 GC 对业务的影响。
1.CMS 的核心设计目标
最小化 STW 时间:通过让 GC 线程与用户线程 "并发执行"(大部分阶段不暂停用户线程),将 STW 控制在毫秒级,避免用户感知卡顿。
适配老年代 :主要负责老年代回收,新生代通常搭配ParNew 回收器(唯一能与 CMS 协作的新生代回收器)。
2.CMS 的回收流程(老年代)
CMS 的回收过程分为4 个主要阶段,其中仅 2 个阶段需要 STW,其余阶段与用户线程并发执行:
1. 初始标记(Initial Mark)------ STW(极短)
- 目标:标记 "根对象直接引用的老年代对象"(如栈中引用、静态变量指向的老年代对象)。
- 特点:仅扫描根对象的直接关联,不遍历整个对象图,因此 STW 时间极短(通常几十微秒到几毫秒)。
2. 并发标记(Concurrent Mark)------ 与用户线程并发
- 目标:从初始标记的对象出发,遍历整个老年代的对象引用链,标记所有存活对象。
- 特点:GC 线程与用户线程同时运行,不暂停业务;但用户线程可能修改对象引用(如创建新对象、断开引用),导致部分对象标记不准确(后续阶段修正)。
- 耗时:最长的阶段(取决于老年代大小和对象数量),但不影响用户线程执行。
3. 重新标记(Remark)------ STW(较短)
- 目标:修正并发标记期间因用户线程操作导致的 "标记偏差"(如对象被删除或新增引用)。
- 实现:通过 "卡表"(记录并发阶段被修改的内存区域)快速定位需重新扫描的对象,避免全堆遍历。
- 特点:STW 时间比初始标记长,但远短于并发标记(通常几毫秒到几十毫秒)。
4. 并发清除(Concurrent Sweep)------ 与用户线程并发
- 目标:回收所有未被标记的垃圾对象(清理内存),释放空间。
- 特点 :GC 线程与用户线程并发执行,不暂停业务;采用 "标记 - 清除" 算法,不移动存活对象,因此会产生内存碎片。
3.CMS 的优缺点
优点:
- 低延迟:仅初始标记和重新标记阶段 STW,总停顿时间极短(通常 < 100ms),适合响应敏感场景。
- 并发执行:大部分工作与用户线程并行,不阻塞业务流程,用户感知弱。
缺点:
- 内存碎片:采用 "标记 - 清除" 算法,不整理存活对象,长期运行会产生大量碎片,可能导致大对象无法分配内存(即使总空间充足),最终触发 Full GC(使用 Serial Old 回收器进行标记 - 整理,STW 时间长)。
- CPU 资源消耗高:并发阶段 GC 线程与用户线程竞争 CPU,若 CPU 核心少(如 < 4 核),会导致用户线程运行变慢(吞吐量下降)。
- 浮动垃圾:并发清除阶段用户线程新产生的垃圾(未被标记)无法被本次回收,需留到下次 GC,因此老年代需预留部分空间(通常 10%-20%),否则可能因内存不足触发 "并发模式失败"(强制 STW,改用 Serial Old 回收)。
- 不支持增量回收:并发标记和清除阶段不可中断,若老年代过大,可能导致长时间占用 CPU。
4.CMS 的适用场景
- 响应时间优先:如 Web 服务、API 接口、实时交易系统(需快速响应用户请求)。
- 中小规模老年代:堆内存不宜过大(建议 < 10GB),否则并发标记 / 清除阶段耗时过长,CPU 占用过高。
- CPU 核心充足:需预留足够 CPU 支持 GC 线程(通常建议 CPU 核心数≥4),避免并发阶段影响业务。
5.CMS 的调优参数(常用)
-XX:+UseConcMarkSweepGC:启用 CMS 回收器(JDK 9 后需额外配置,JDK 14 废弃)。-XX:ConcGCThreads:设置并发 GC 线程数(默认约为 CPU 核心数的 1/4)。-XX:CMSInitiatingOccupancyFraction:老年代使用率达到该阈值时触发 CMS(默认 68%,可降低该值减少并发模式失败风险)。-XX:+UseCMSCompactAtFullCollection:Full GC 时进行内存整理(解决碎片,默认开启)。-XX:CMSFullGCsBeforeCompaction:多少次 Full GC 后进行一次整理(默认 0,即每次 Full GC 都整理)。
6.CMS 的历史地位
- 诞生:JDK 6 正式引入,解决了传统回收器 STW 过长的问题,成为低延迟场景的首选。
- 衰落:因内存碎片、CPU 消耗高、维护成本大,JDK 14 被标记为废弃,JDK 17 完全移除。
- 替代者:被 G1、ZGC、Shenandoah 等回收器取代(这些回收器兼顾低延迟和无碎片,支持更大堆)。