虚拟机GC算法主要包含标记-清除、复制算法、标记-整理以及分代收集四大基础算法,现代JVM则普遍采用G1和ZGC等实现,没有绝对最好的算法,只有最适合特定业务场景的选择。不同算法在吞吐量、停顿时间、内存碎片和CPU开销上各有取舍,选错GC策略会直接导致线上OOM或频繁Full GC。
虚拟机gc算法有哪些:四大基础算法逐一拆解
GC(Garbage Collection,垃圾回收)的核心职责就两件事:找出不再存活的对象(可达性分析),然后回收它们占用的内存,基础算法是所有垃圾回收器的地基,搞清楚它们,后面理解G1和ZGC会轻松很多。
标记-清除算法
这是最朴素的方案,分两个阶段干活。标记阶段从GC Roots出发,把所有可达对象打个标记;清除阶段扫描整个堆,把没标记的对象直接清掉。
优点是实现简单,不需要移动对象,在存活对象多的场景下效率尚可,缺点也同样明显:会产生大量不连续的内存碎片想象一下你反复往一个箱子塞东西再拿出来,最后箱子里的空隙多到放不下一个大件,碎片积累到一定程度,即使总空间够用,分配大对象时也会提前触发Full GC。
行业共识认为,标记-清除更适合对象存活率低、内存碎片不敏感的早期应用场景,现在基本不会作为唯一回收策略单独使用。
复制算法
为了解决碎片问题,复制算法把可用内存按容量划分为大小相等的两块(比如From区和To区),每次只使用其中一块,回收时把存活对象复制到另一块,然后一次性清空原区域,然后交换两块的角色。
优点很突出:内存分配不担心碎片,只要移动堆顶指针即可,且回收效率高,存活对象少时性能极好,缺点也直白:内存利用率只有一半,特别浪费,更关键的是,如果对象存活率很高,复制操作会非常频繁,得不偿失。
实际中,虚拟机不会傻乎乎分两块等大的堆,而是把新生代分成较大的Eden区和两个较小的Survivor区,默认比例8:1:1,这样只有约10%的内存被浪费。
标记-整理算法
标记-整理是标记-清除的升级版。标记阶段一样,但后续不是直接清除,而是让所有存活对象向内存一端移动,然后直接清理掉边界以外的内存。
好处是没有内存碎片,内存利用率高;代价是需要移动对象,如果存活对象多,移动成本很高,而且移动过程中必须暂停用户线程(STW,Stop The World),所以标记-整理更常用于老年代,因为老年代对象存活率高,追求的是整体空间利用率和无碎片状态。
分代收集算法
分代收集不是一种具体算法,而是一种整合策略
,它把堆按对象存活周期分成新生代和老年代:
- 新生代:对象大多朝生夕死(存活率低),适合复制算法,Minor GC频率高但单次耗时短。
- 老年代:对象存活久(存活率高),适合标记-清除或标记-整理,Major GC/Full GC频率低但单次耗时长。
当前绝大多数商业虚拟机(如HotSpot)的垃圾回收器都采用分代设计,这是工程实践中的最优解之一。
gc算法优缺点对比:一张表看清核心差异
很多人在面试或选型时容易混淆,这里直接按关键维度整理成对比表,保存下来随时可查。
| 维度 | 标记-清除 | 复制算法 | 标记-整理 | 分代收集(组合策略) |
|---|---|---|---|---|
| 空间利用率 | 低(碎片多) | 极低(浪费一半) | 高(无碎片) | 高(分区域优化) |
| 执行效率 | 标记+扫描,较慢 | 存活对象少时极快 | 标记+移动+整理,较慢 | 综合最优 |
| STW停顿 | 较长(扫描全堆) | 较短(只动半区) | 最长(移动对象) | 可控(分区域、分策略) |
| 内存碎片 | 严重 | 无 | 无 | 新生代无、老年代无(配合整理) |
| 适用场景 | 对象存活率低、对碎片不敏感 | 对象存活率低、追求速度 | 对象存活率高、老年代回收 | 绝大多数通用Java应用 |
gc算法适用场景:不同业务该如何选择
搞懂基础算法后,真正的实战问题是:jvm gc算法怎么调优? 你可以通过JVM参数把堆分成不同区域,让各区域用适合自己的回收策略,具体操作步骤如下:
- 用
-Xms/-Xmx设置初始堆和最大堆,-Xms2g -Xmx2g。 - 用
-XX:NewRatio=2设置老年代:新生代比例为2:1,对象生命周期长的应用可以让老年代更大。 - 用
设置Eden:Survivor = 8:1:1,避免复制算法浪费太多空间。-XX:SurvivorRatio=8
- 配合
-XX:+PrintGCDetails或-Xlog:gc(JDK 9+)打印GC日志,观察新生代和老年代的回收频率与停顿。
在快速响应类场景比如在线交易系统、抢购系统,核心诉求是低延迟,尽量减少STW时间,这类系统通常优先考虑复制算法配合较小的新生代,以及并发收集器(如G1) 来缩短单次停顿,相反,在离线计算、批量处理场景如数仓ETL、大数据任务,吞吐量比响应时间更重要,允许较长的停顿,可以加大老年代区域、减少GC次数,用吞吐优先模式运行。
在嵌入式或内存受限环境下,复制算法的空间浪费不可接受,选用标记-整理或者Serial Old回收器会更稳妥。
现代JVM的GC进化:G1和ZGC谁更强
除了基础算法,实际开发中你面对的是具体垃圾回收器,目前HotSpot虚拟机主流的几款,按演进顺序大致是:Serial / Parallel / CMS(已废弃)/ G1(JDK 9+默认) / ZGC(JDK 15+转正)。
G1:区域化分代收集的集大成者
G1不再严格区分物理上的新生代和老年代,而是把堆划分为多个大小相等的Region(区域),每个Region都可以独立扮演Eden、Survivor或Old区,它同时维护一个优先级列表,每次回收优先处理回收价值最大的Region集合这被称为Garbage First。
G1的好处是可预测的停顿时间模型:你可以通过 -XX:MaxGCPauseMillis=200 告诉JVM希望GC停顿控制在200毫秒内,它会动态调整每个Region的回收量来满足目标,G1非常适合大堆(4GB以上)且对延迟有明确要求的通用服务,目前是绝大多数企业级Spring Boot应用的首选。
ZGC:追求亚毫秒级停顿
ZGC为代表的新一代回收器,核心思路是染色指针 + 读屏障,让GC在不停止用户线程的情况下完成绝大部分工作,把STW时间压缩到1毫秒以内,而且停顿时间不随堆大小增长,JDK 15之后ZGC终于支持分代收集,进一步提升了性价比。
ZGC适合的场景非常明确:超大堆(几十GB甚至TB级)、极低延迟要求、高并发在线业务,如果你的应用追求极致的响应时间,比如金融交易、实时推荐系统,ZGC是当前最优解之一。
g1和zgc对比来看,G1胜在成熟稳定、内存占用开销小,默认就能用;ZGC胜在停顿极低但需要更高的CPU开销和JDK版本要求,多数业务用G1已经足够,除非你的堆Size达到几十GB规模且严苛要求P99延迟,否则ZGC的优势感知并不明显。
新一代GC选型建议与调优思路
最终落到你自己的服务器上,可以按以下路径做决策:
- 堆大小 < 4GB
:默认的ParallelGC(JDK 8)或G1(JDK 9+)都行,如果追求吞吐且不敏感延迟,设置为
-XX:+UseParallelGC。 - 堆大小 4GB ~ 32GB:直接用G1,设置
-XX:+UseG1GC,并配合-XX:MaxGCPauseMillis调节停顿时长,不要随意修改Region大小。 - 堆大小 > 32GB 且延迟敏感:优先尝试ZGC,启动参数
-XX:+UseZGC,观察单次GC日志中的停顿是否显著低于G1。
调优实操建议按顺序做:
- 先确定目标:是降低Full GC频率,还是降低单次GC耗时。
- 采集数据:用
jstat -gcutil <pid> 1000观察各个代的使用率和GC时长,连续观察一周业务高峰。 - 定位瓶颈:如果Full GC频繁,优先检查老年代空间是否过小或是否存在大对象或内存泄漏;如果Minor GC频繁,调大新生代。
- 小步验证:一次只改一个参数,观察GC日志对比,避免同时改动多个参数导致无法定位效果来源。
在不少国内互联网大厂的实践里,将CMS切换到G1后,Full GC停顿时间比之前下降了一个量级,而换到ZGC后,响应时间进一步趋近于理想状态,对于Java开发者来说,掌握这几种gc算法优缺点对比和适用场景,不仅是面试的基础,更是线上排查内存问题的底气所在。
归根结底,GC调优没有银弹。 你要做的是理解业务对象的分配速率和存活特征,用工具看清GC日志,然后选一款合适的收集器,设定合理的堆内存和回收比例,让JVM以最舒服的姿态替你管理好那几GB的堆空间。
关于虚拟机gc算法的常见疑问
新生代为什么不用标记-清除算法?
因为新生代对象存活率很低,绝大多数对象都活不过第一轮GC,复制算法的复制成本极低,且天然没有内存碎片,对分配效率更友好,而标记-清除需要扫描全堆存活对象(操作成本高)且留下碎片,在新生代属于“用牛刀杀鸡”,性价比明显偏低。
G1会完全取代分代收集吗?
不会,分代收集是一种策略思想,而G1本身就是分代收集的现代实现,它只是把物理连续的分区改成了逻辑上的Region集合,本质上仍然区分年轻代和年老代,并分别采用复制和整理算法,所以更准确的说法是:G1是对传统分代收集算法在可预测停顿模型上的工程改进。
怎么确认我的Java应用用到了哪种GC算法?
直接使用JDK自带的命令查看:执行 jmap -heap <pid> 可以看到当前使用的垃圾回收器名称(如G1、ZGC等)以及堆内存配置;或者启动应用时加 -XX:+PrintCommandLineFlags -version 参数,输出内容里会列出默认的垃圾回收器选项,如果看到 -XX:+UseG1GC 则正在使用G1,-XX:+UseZGC 则正在使用ZGC。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/633521.html





