新生代和老年代的 GC 算法有哪些?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 基础掌握度:面试官不仅仅是想知道你背了几种算法名字,更是想看你能否准确说出新生代和老年代各自使用的 GC 算法,以及背后的设计考量。
  2. 原理理解深度:能否解释清楚每种算法的工作原理、优缺点,以及为什么 HotSpot 选择"分代收集"这个策略,而不是所有区域用同一种算法。
  3. 实践关联:是否了解不同垃圾收集器(Serial、ParNew、Parallel Scavenge、CMS、G1 等)分别用了哪种算法,能否将理论和实际联系起来。

核心答案

直接上结论,用一张表说清楚:

分代 GC 算法 核心思路 对应的垃圾收集器
新生代 复制算法(Copying) 将存活对象复制到另一块空间 Serial、ParNew、Parallel Scavenge
老年代 标记-清除(Mark-Sweep) 标记垃圾,直接清除 CMS
老年代 标记-整理(Mark-Compact) 标记存活对象,整理到一端 Serial Old、Parallel Old
整堆 分区复制 + 标记-整理 分 Region 灵活处理 G1、ZGC

注意:新生代基本只用复制算法,而老年代根据收集器的不同,会选用标记-清除或标记-整理。

深度解析

一、为什么需要分代?

在讲具体算法之前,得先理解一个前提——为什么要把堆分成新生代和老年代?

研究发现,Java 系统中绝大多数对象都是"朝生夕死"的,活不过一轮 GC。但也有少部分对象是长时间存活的(比如缓存、连接池)。这两类对象的生存特征完全不同,如果用同一种算法处理,效率会很低。

所以 HotSpot 把堆分成了新生代和老年代,让不同区域各用最合适的算法,这就是 "分代收集" 的核心思想。

上图展示了 JVM 堆内存的经典布局:

  • 新生代:分为 Eden 区和两个 Survivor 区(S0、S1),默认比例 8:1:1。新对象优先在 Eden 区分配。
  • 老年代:存放经过多次 GC 仍然存活的长寿对象,默认占堆内存的 2/3

二、新生代——复制算法

新生代的 GC 叫 Minor GC(也叫 Young GC),使用的是 复制算法

工作流程

  1. 新对象分配在 Eden
  2. Eden 区满了,触发 Minor GC
  3. GC 从 GC Roots 开始,标记所有存活对象
  4. Eden 和当前使用的 Survivor(From)中的存活对象,复制 到另一个空的 Survivor(To)
  5. 清空 Eden 和 From 区
  6. From 和 To 角色互换

上面的图示展示了复制算法的核心过程:

  • GC 前Eden 区和 From 区中有大量对象,其中只有少部分是存活的(用 表示),其余都是垃圾
  • GC 后:所有存活对象被复制到 To 区,EdenFrom 被整体清空,然后 FromTo 的角色互换
  • 关键点:不需要逐个清理垃圾,直接把存活对象搬走,原来的空间一把清空,效率非常高

为什么新生代适合复制算法?

因为新生代有个特点——98% 的对象都是朝生夕死的。每次 GC 只有极少量对象存活,需要复制的量很小。而且复制算法不需要考虑碎片问题,存活对象被集中放到 Survivor 区,空间天然连续。

代价是什么?就是空间浪费。Eden:S0:S1 = 8:1:1,始终有一块 Survivor 是空的,也就是浪费了大约 10% 的新生代空间。但这笔账很划算,因为避免了内存碎片。

对象什么时候晋升老年代?

不是所有对象都永远待在新生代,以下情况会晋升到老年代:

  • 年龄达到阈值:每经历一次 Minor GC 且存活,年龄 +1,默认达到 15 岁(-XX:MaxTenuringThreshold)就晋升
  • 大对象直接进入老年代:超过 -XX:PretenureSizeThreshold 的对象直接在老年代分配
  • 动态年龄判断:如果 Survivor 区中相同年龄的所有对象大小总和超过 Survivor 空间的一半,年龄 >= 该年龄的对象直接晋升

三、老年代——标记-清除与标记-整理

老年代的 GC 叫 Major GC(有时也叫 Full GC,严格来说 Full GC 是清理整个堆),根据垃圾收集器的不同,会使用不同的算法。

1. 标记-清除算法(Mark-Sweep)

这是最基础的算法,CMS 收集器用的就是它。

上图的流程分两步:

  • 标记阶段:从 GC Roots 开始遍历,标记所有可达的存活对象。图中 表示存活对象, 表示已被标记为垃圾的对象
  • 清除阶段:直接回收垃圾对象占用的空间。注意看清除后的内存布局——存活对象之间出现了大量不连续的空闲区域,这就是 内存碎片 问题

优点:速度快,不需要移动对象。缺点:会产生内存碎片,后续分配大对象时可能找不到足够的连续空间,触发提前 GC。

2. 标记-整理算法(Mark-Compact)

Serial Old、Parallel Old 用的是这个算法。

  • 标记阶段:和标记-清除一样,先标记所有存活对象
  • 整理阶段:将所有存活对象向内存的一端移动,然后直接清理边界以外的空间。整理后的内存非常整齐,没有碎片

优点:没有内存碎片。缺点:移动对象需要更新所有引用,耗时比标记-清除更长。老年代对象多,移动的开销不小。

3. 两种算法怎么选?

对比维度 标记-清除 标记-整理
是否移动对象 不移动 移动
内存碎片
GC 停顿时间 较短 较长
代表收集器 CMS Serial Old、Parallel Old

CMS 选择标记-清除是为了追求低延迟,用碎片问题换响应时间。但碎片积攒到一定程度还是得做一次 Compact,这就是 CMS 的 Concurrent Mode Failure 触发 Full GC 的原因之一,代价很大。

四、G1 和 ZGC 的思路

到了 JDK 9 之后,G1 成为默认收集器,它的思路变了——不再严格区分新生代和老年代的物理边界,而是把堆分成一个个等大的 Region(默认约 2048 个)。

  • G1 对新生代 Region 用复制算法,对老年代 Region 用标记-整理
  • ZGC 更激进,用染色指针和读屏障实现了几乎无碎片的整理,停顿时间控制在亚毫秒级

这块展开就多了,面试官如果追问,说明对你的回答很满意,想看看你的深度。

面试高频追问

  1. 为什么新生代不用标记-清除?

    因为新生代对象存活率极低(通常不到 2%),用复制算法只需要复制少量存活对象,效率极高。如果用标记-清除,每次要扫描整个新生代标记大量垃圾,反而更慢。

  2. CMS 为什么选标记-清除而不是标记-整理?

    CMS 的设计目标是低延迟。标记-整理需要移动老年代中的大量对象并更新引用,这会造成很长的 STW 停顿,违背了 CMS 的设计初衷。所以 CMS 选择不移动对象的标记-清除,代价是内存碎片。

  3. 什么是 STW?为什么不可避免?

    Stop-The-World,GC 时暂停所有用户线程。因为 GC 需要确保一致性快照(类似数据库的快照隔离),如果一边回收一边有线程修改对象引用,就会出问题。CMS 和 G1 通过并发标记来尽量缩短 STW 时间,但完全消除目前还做不到。

常见面试变体

  • "说说 JVM 有哪些垃圾收集算法?各自的特点?"
  • "为什么 HotSpot 要采用分代收集?"
  • "CMS 和 G1 用的分别是什么算法?"
  • "复制算法的缺点是什么?"

记忆口诀

新生代复制、老年代标记——新生代对象死得快,复制少量活的就行;老年代对象活得久,标记完要么清要么整。碎片换速度选清除,稳定无碎选整理。

总结

JVM 的分代 GC 算法选择,本质上是一个 "对症下药" 的过程:新生代对象存活率低,用复制算法效率最高;老年代存活率高,复制算法代价太大,所以选择标记-清除或标记-整理,两者在"碎片"和"延迟"之间做取舍。面试的时候,把"为什么这么分"讲清楚,比单纯背算法名字加分多了。