关于GC和分代是堆内存管理的两个核心概念,但是实际上JVM规范并没有明确的规定需要采取什么算法,以及何种分代策略进行垃圾回收。不同的JVM实现是不一致的,只考虑最常用的情况: 下面的讨论是以HotSpotVM模型的实现为基础的讨论
如果你去查相关的概念,会看到如下的解释:
GC的基本定义是:JVM对堆内存的垃圾回收操作。常见的算法有标记-清除,标记-复制,标记整理三类。
然后是GC的分代,常规的解释里会说堆内存被分为新生代和老年代,其中新生代又被分为eden和survivor。
然后会告诉你,不同分代对应的垃圾回收算法都有哪些,例如Serial GC, Parallel GC, CMS GC,ZGC。
但实际上,这种描述的因果关系是有问题的。
在GC中,分代的情况不是固定的,不是不同的分代选择了不同的算法,而是不同的算法划定了不同的分代模型。
以分代算法的划分中,以G1算法的出现作为分界点,之前的额Serial,Parallel,CMS算法被称为传统分代算法,他们对于堆内存的划分是我们前文提到的新生代和老年代的划分,不同分代在物理上是连续的。而在G1之后的算法,我们通常称为现代回收算法,他们打破了原有的分代模型设计。
G1算法也不是完全不分代。G1将整个堆划分为多个大小相等的独立Region,每个Region被动态的标记为Eden,Survivor,Old或者Humongous(即大对象)。在逻辑上保留了分代,但是实际物理内存中已经没有前面几种算法的分代模型。
发展到ZGC和Shenandoah,已经完全弱化掉了分代的概念,采用大页面 / Region 化设计。ZGC将堆划分为小页(2MB)、中页(32MB)、大页(按需),通过 “染色指针” 标记对象状态,不严格分代。Shenandoah:堆划分为多个连续 Region,支持并发移动对象,通过 “连接矩阵” 跟踪 Region 间引用,无固定分代。
对于传统GC算法来说,由于分代基本一致,他们其实是可以组合使用的,但这种组合是由JVM给定的组合选项,我们并不能自由选择。
| 组合名称(官方称呼) | 新生代算法 | 老年代算法 | 核心特点 | 适用场景 |
|---|---|---|---|---|
| Serial GC | Serial Copying(串行复制) | Serial Mark-Compact(串行标记 - 整理) | 单线程回收,STW 时间长,内存开销小 | 小型应用、客户端程序(如桌面软件) |
| Parallel GC(吞吐量优先) | Parallel Scavenge(并行复制) | Parallel Mark-Compact(并行标记 - 整理) | 多线程回收,优先提升吞吐量,STW 时间较 Serial 短 | 服务器端、CPU 密集型应用(如数据分析) |
| CMS GC(低延迟优先) | Parallel Scavenge(并行复制) | CMS(并发标记 - 清除) | 新生代并行回收,老年代并发回收(低 STW),但老年代不压缩(有碎片) | 互联网应用、低延迟需求(如电商接口) |
| G1 GC(平衡延迟与吞吐量) | G1 Young GC(Region 复制) | G1 Mixed GC(Region 混合回收) | 打破连续分代,堆划分为 Region;新生代 / 老年代共用 Region 机制,可动态调整回收区域 | 中大型堆(4GB+)、需平衡延迟与吞吐量(如微服务) |
| ZGC / Shenandoah(超低延迟) | 无单独新生代算法(弱化分代) | ZGC/Shenandoah 全局算法 | 完全弱化分代,堆按大页面 / Region 划分;全阶段低并发 STW,支持 TB 级堆 | 超大堆、超低延迟需求(如金融交易、实时计算) |