Java 中有哪些垃圾回收算法?
- 标签:JVM、Java
🏛 业务架构师
基础算法三种 + 分代组合:
- 标记-清除:标记存活、直接清除——简单,但内存碎片(分配大对象要找连续空间,触发提前 GC)与效率一般;
- 标记-复制:存活对象复制到另一半空间,旧空间整体清空——无碎片、分配快(指针碰撞),但牺牲一半空间;适合"存活率低"的场景——这正是新生代(大部分对象朝生夕死,存活率极低),Eden + 2 个 Survivor 就是优化版(8:1:1,只浪费 10%);
- 标记-整理:存活对象向一端移动再清理边界——无碎片且不浪费空间,但移动对象成本高;适合"存活率高"的老年代;
- 分代收集:不是算法而是策略——按对象生命周期统计规律(绝大多数对象活不过一次 YGC)分代施策:新生代用复制、老年代用整理/并发标记。各种收集器(CMS/G1/ZGC)是这些算法在停顿目标下的工程实现。
业务视角的两个启示:
- 分代是"业务规律驱动的工程优化":JVM 设计者观察到对象生命周期的强统计规律(Web 请求产生的临时对象即用即弃),按数据特征定制策略——业务架构同理:热点数据进缓存、冷数据归档、订单按时间分区,都是"按访问规律分层"的同构思想。没有银弹算法,只有按特征组合的策略;
- 空间与时间的定价无处不在:复制算法用空间换无碎片,整理算法用 CPU 换空间——GC 的每个选择都是资源权衡,和业务里"用冗余换可用性、用延迟换一致性"是同一套经济学。
一句话:GC 算法史 = 按对象生命周期特征不断精细化定价的历史——理解权衡比记住算法名更有价值。
🏗 系统架构师
按"思想 → 算法 → 适用"讲四个基础算法:
- 标记-清除(Mark-Sweep):从 GC Roots 标记存活对象,清收未标记的。简单通用,但产生碎片(分配大对象可能触发提前 Full GC)且效率一般;CMS 老年代采用;
- 复制(Copying):空间分两块,存活对象复制到另一块后整体清空。无碎片、效率高(存活少时),代价是浪费一半空间;新生代优化为 Eden + 2 Survivor(8:1:1),因为朝生夕死对象存活率极低,实际只浪费 10%;
- 标记-整理(Mark-Compact):标记后把存活对象向一端移动,再清边界外内存。无碎片且不浪费空间,但移动对象成本高(且移动期间要停顿或并发屏障);Parallel Old / G1 整理阶段采用;
- 分代收集:不是具体算法而是策略——按对象生命周期分代,各代配最优算法:新生代复制、老年代标记清除/整理。建立在这个经验法则上:绝大多数对象朝生夕死。
演进主线(架构视角):Serial(停顿换简单)→ Parallel(多线程缩短停顿)→ CMS(并发标记,停顿大幅下降但碎片化)→ G1(Region 化 + 可预测停顿)→ ZGC/Shenandoah(并发整理 + 读屏障,停顿与堆大小解耦,亚毫秒级)。每一代都在回答同一个问题:如何在"移动对象保无碎片"与"不停顿应用"之间更进一步——理解这条主线,比背算法更能答好 GC 类问题。
💻 全栈开发专家
四种基础算法 + 分代理论 + 并发标记问题:
1. 标记-清除:标记存活对象后统一清除。简单,但内存碎片(后续大对象放不下提前触发 GC)、效率一般。CMS 用的它。
2. 标记-复制:空间分两块,存活对象复制到另一块后整体清空。无碎片、吞吐高,但浪费一半空间——配合"朝生夕死"对象特征用于新生代:Eden + 2 个 Survivor(8:1:1),每次只浪费 10%。
3. 标记-整理(压缩):标记后把存活对象向一端移动,再清边界外。无碎片也不浪费空间,但移动对象成本高(且移动期间要更新引用),用于老年代(Parallel Old/G1 的 region 间整理)。
4. 分代收集(工程策略而非算法):按对象生命周期分区用不同算法——新生代复制、老年代整理/清除。
并发标记的三色标记 + 漏标问题(加分重点):
并发标记(CMS/G1)时用户线程在改引用,可能出现"黑对象指向白对象且灰到白的路径被删"的浮动垃圾/漏标:
- CMS 用增量更新(记录黑→白的新引用,重新标记阶段处理);
- G1 用SATB(原始快照)(记录被删除的旧引用,按快照处理)。
工程联系:新生代对象 98% 朝生夕死是复制算法成立的前提;-XX:PretenureSizeThreshold 让大对象直入老年代(避免 Survivor 来回倒腾),这两个数字背后都是算法选型的逻辑。