关于GC和分代

68 阅读3分钟

关于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 GCSerial 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 级堆超大堆、超低延迟需求(如金融交易、实时计算)