JVM垃圾回收圈的“诸神之战”:从CMS的垂死挣扎,到ZGC的毫秒级封神,谁才是你的真命天子?

举报
喵手 发表于 2025/12/08 20:34:35 2025/12/08
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言:那个让服务器“脑梗”的瞬间

兄弟们,咱们搞 Java 的,谁还没经历过几次 STW(Stop The World)的毒打?🌍💥

那种感觉就像是你正开着法拉利在高速上狂飙(处理高并发请求),突然发动机熄火了,全车人(所有线程)都得停下来等着修车师傅(GC线程)把垃圾清理干净才能继续跑。如果是几十毫秒也就算了,用户也就是觉得页面卡了一下;但如果是几秒钟甚至更久……呵呵,你就等着老板在周会上把你挂在墙上“表扬”吧!🌚

Java 发展了这么多年,GC 算法也从当年的“单车变摩托”,进化到了现在的“歼-20”水平。今天咱们就扒一扒 GC 家族里的四位大佬,看看它们是怎么一步步把“停顿时间”这个紧箍咒给摘掉的。

一、 CMS:昔日的王者,如今的“老赖” 👴

提起 CMS(Concurrent Mark Sweep),老 Java 程序员估计都要抹一把辛酸泪。在 JDK 8 那个年代,它可是追求低延迟的首选。

它的必杀技: 它是第一个敢在“大部分时间”里跟应用线程一起干活的 GC。它把垃圾回收分成了好几个阶段(初始标记、并发标记、重新标记、并发清除),尽量把 STW 的时间压缩到最短。

实际体验:
  刚上线的时候,你会觉得:“哇,丝般顺滑!”✨
  但是跑着跑着,你就会发现这货有个致命的毛病——内存碎片。CMS 就像个只会扫地不会整理的保洁阿姨,地是扫干净了,但那是东一块西一块的空地。等到哪天你突然要分配一个大对象(比如一个超大的数组),它发现找不到连续空间了,瞬间傻眼。😳

这时候,它就会触发那个让所有人闻风丧胆的 Full GC,并且退化成单线程整理内存。那一刻,你的服务器就像被冻住了一样,停顿个几秒那是常事。这就是著名的 “Concurrent Mode Failure”

现在的地位: 官方已经弃用了。如果你还在用 JDK 8 并且饱受 CMS 折磨,听哥一句劝,赶紧跑!🏃‍♂️

二、 G1:现任的大管家,平衡术大师 ⚖️

到了 JDK 9 以后(虽然 JDK 8 也能用),G1(Garbage-First)上位成了默认 GC。这一位,那可是彻底颠覆了以前的玩法。

核心逻辑:
  以前的堆内存就像一整块大蛋糕,分什么新生代、老年代。G1 不这么干,它把内存切成了几千个小格格(Region),像棋盘一样。♟️
  G1 最牛的地方在于它能**“预测”**。你可以给它下达指标:“喂,G1,我要求每次 GC 停顿不能超过 200ms。” G1 就会在那算:“行,为了满足你的要求,我这次只回收这 50 个垃圾最多的小格格,剩下的下次再说。”

代码里的配置长这样:

# 开启 G1
-XX:+UseG1GC 
# 设置最大停顿时间目标(这可是 G1 的精髓)
-XX:MaxGCPauseMillis=200 

实际体验:
  G1 是个多面手,它不追求极致的低延迟,也不追求极致的吞吐量,它追求的是可控。对于大部分 4GB - 32GB 堆内存的微服务来说,G1 是最省心的选择。它就像个成熟的中年人,情绪稳定,不会像 CMS 那样突然崩溃。

三、 ZGC & Shenandoah:来自未来的黑科技 👽

如果说 G1 是改良派,那 ZGC(Oracle 家的)和 Shenandoah(RedHat 家的)就是彻底的革命派。这俩货的目标简直狂妄——不管你堆内存有多大(哪怕是 TB 级别),我的停顿时间都要控制在 10ms 以内(ZGC 后来更是进化到了亚毫秒级)! 🤯

怎么做到的?
  这俩兄弟用上了读屏障(Load Barriers)染色指针(Colored Pointers,ZGC特有)这种听起来就很科幻的技术。
  简单说,以前 GC 移动对象的时候,必须让所有线程停下来,不然你刚把对象 A 搬到新地址,线程 B 还在往旧地址写数据,那不就乱套了吗?
  但 ZGC 和 Shenandoah 能做到
并发移动
。就像是给赛车换轮胎,车还在赛道上跑着呢,技师就把轮胎给换了!🏎️💨

配置起来简单到令人发指(以 ZGC 为例,JDK 17+):

# 开启 ZGC,就这一行,完事儿
-XX:+UseZGC
# 如果想看详细日志
-Xlog:gc*

实际体验:
  我第一次切到 ZGC 的时候,看着监控里的 GC 停顿时间,我以为监控坏了。那条线平得就像心电图停了一样,基本都在 1ms 以下。那种“丝般顺滑”的感觉,真的,用过就回不去了。😭

四、 诸神之战:到底该选谁?(含代码实战建议)

说了这么多,咱开发还得落地。这里有一份**《避坑指南》**,纯经验之谈,拿走不谢。🎁

场景 1:小内存单体应用(堆内存 < 4GB)
  别折腾了,直接用 Parallel GC 或者 Serial GC
  为什么? 杀鸡焉用牛刀?在小内存下,复杂的并发 GC 反而会有额外的 CPU 开销。简单的 STW 也就几毫秒,根本感觉不到。

场景 2:主流微服务(堆内存 4GB - 32GB)
  首选 G1
  现在的 Spring Boot 版本默认基本都是 G1 了。它的吞吐量和延迟平衡得最好,不需要太多的调优,设置个 -Xmx-Xms 就行了。

场景 3:超大内存 Or 极度敏感的延迟要求(游戏、金融、高频交易)
  无脑上 ZGC(前提是 JDK 版本要在 17 以上,最好 21)。
  如果你这边的业务要求请求响应必须在 20ms 内,或者你的堆内存有个 100G,G1 扫一遍可能都要卡顿好久,这时候 ZGC 就是救世主。

场景 4:Shenandoah 啥时候用?
  如果你用的是 OpenJDK 的某些下游发行版(比如 Amazon Corretto 或者 RedHat 自家的构建),且由于历史原因不能用 Oracle 的 ZGC,那 Shenandoah 是个非常棒的替代品,效果跟 ZGC 也就是伯仲之间。

💻 实战代码演示:如何在本地压测感受区别

咱们写个简单的内存炸弹程序,看看能不能把 GC 逼疯。

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;

// 模拟一个不断生成垃圾对象的场景
public class GCDrama {
    // 这里的 byte 数组模拟业务中的大对象
    private static final int OneMB = 1024 * 1024;
    // 缓存池,模拟老年代常驻对象
    private static List<byte[]> cache = new ArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("🔥 GC 压力测试开始...");
        
        while (true) {
            // 疯狂创建 1MB 的垃圾对象
            byte[] garbage = new byte[OneMB];
            
            // 随机保留一些对象进入老年代,模拟内存泄漏或缓存
            if (ThreadLocalRandom.current().nextInt(10) == 0) {
                cache.add(garbage);
                // 防止 OOM,模拟缓存清理
                if (cache.size() > 500) { 
                    cache.clear(); 
                    System.out.println("🧹 清理缓存池,释放老年代空间");
                }
            }
            
            // 稍微睡一丢丢,给 CPU 喘口气
            Thread.sleep(2);
        }
    }
}

怎么玩?
  你把这段代码编译了,然后用不同的启动参数去跑,同时打开 VisualVM 或者 JConsole 观察 GC 图表。

  1. 体验 CMS 的痛苦(如果你有 JDK 8):
    java -Xmx1G -XX:+UseConcMarkSweepGC GCDrama
    预警: 你会看到锯齿状的内存波形,偶尔会突然卡一下大的。

  2. 体验 G1 的稳健:
    java -Xmx1G -XX:+UseG1GC GCDrama
    现象: 波形会规律很多,虽然也有波动,但很难出现那种长达几秒的卡死。

  3. 体验 ZGC 的魔法(JDK 17+):
    java -Xmx1G -XX:+UseZGC GCDrama
    现象: 你会怀疑程序是不是没在跑。内存占用虽然高(因为并发回收要预留空间),但那个 GC Pause Time 简直低到尘埃里。

五、 总结:大人的时代变了

说到底,技术没有银弹,只有最适合的子弹。🔫

如果你的系统还在跑 JDK 8,那我敬你是条汉子,还是老老实实跟 CMS 还有 G1 磨合吧。但凡有机会,我强烈建议大家升级到 JDK 17 甚至 JDK 21。不仅是因为语法糖甜,更是因为 ZGC 这种神器的存在,真的能让你的运维生活少一点报警,多一点睡眠。😴

别抱着老旧的“CMS调优秘籍”不放了,现在的 JVM 真的已经进化到不需要咱们在那抠 SurvivorRatio 的地步了。把时间省下来,去研究研究业务,或者……去谈个恋爱不好吗?😉

好了,今天的瓜就吃到这。要是觉得这文章帮你看清了 GC 的真面目,别忘了点个赞!咱们下回见!👋

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。