首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >垃圾回收不是回收站:JVM GC 背后的爱恨情仇

垃圾回收不是回收站:JVM GC 背后的爱恨情仇

作者头像
王中阳AI编程
发布2026-03-17 18:25:17
发布2026-03-17 18:25:17
2760
举报
文章被收录于专栏:Go语言学习专栏Go语言学习专栏

有人以为 GC 是自动的,有人以为 GC 是无敌的。

直到有一天,内存爆了、服务挂了、老板骂了,才发现 —— 垃圾回收不是想象中的“回收站”,而是一位脾气不太好的“管家”。

GC 是个啥?它真的“自动”吗?

JVM 中的垃圾回收(Garbage Collection,简称 GC)负责清理不再使用的对象,让内存“循环再利用”。

很多 Java 程序员初期对 GC 有个“甜美误会”:

“Java 有 GC,不用手动释放内存,太棒了!”

而现实是:

  • 它自动,但不一定及时。
  • 它聪明,但有点挑食。
  • 它帮你干活,但你不能躺平。

Java 对象去哪了?内存分代机制了解一下

GC 的核心是分代:把对象分阶段地管理,老少有别,优待长者。

新生代(Young Generation)

  • 包括 Eden 区、Survivor From、Survivor To。
  • 新生对象先出生在 Eden。
  • 幸存一次 GC 后进 Survivor。
  • 经历几次幸存后,升职去老年代。

新生代回收频率高,但速度快(Minor GC)。

老年代(Old Generation)

  • 经历多次 GC 还活着的对象,就会被晋升到“老年代”。
  • 老年代的 GC(Full GC / Major GC)代价很大,能不触发就不触发。

GC 算法百家争鸣:谁在替你搬砖?

JVM 提供了多种 GC 实现,它们各有脾气,常见的有:

1. Serial GC(单线程收垃圾)

适合单核小内存机器,用得少,用错哭得早

2. Parallel GC(多线程收垃圾)

适合吞吐量优先的后台服务,JDK8 默认。

优点:多线程干活,清得快,吞吐量高。 缺点:STW 停顿时间不稳定,对延迟敏感的场景不太友好。

3. CMS(Concurrent Mark Sweep)

曾是“老年代明星选手”,现在退休了,JDK9 开始废弃。

优点:STW 时间短。 缺点:容易碎片化、失败后还得 Full GC。

4. G1(Garbage First)

现在的主力选手,JDK 9+ 默认使用

把堆划分成多个小块(Region),优先回收垃圾最多的区域。 适合大内存场景,追求稳定的停顿时间

5. ZGC / Shenandoah

年轻一代的“超低延迟 GC”,JDK 11+ 开始支持。

优点:停顿时间控制在 10ms 以下,适合对延迟敏感的系统。 缺点:内存占用略高,配置略复杂。

GC 的爱:我真的很想帮你

GC 设计初衷是美好的:

  • 自动释放不用的对象,防止内存泄漏。
  • 尽量缩短 STW ,让你无感知地过日子。

你写的这段代码:

代码语言:javascript
复制
public void foo() {
    String s = new String("hello");
}

方法执行完后,s 引用就失效了,GC 老哥就会把这个 "hello" 给回收了(如果没被缓存)。

你要是写:

代码语言:javascript
复制
List<String> list = new ArrayList<>();
while (true) {
    list.add("hello");
}

GC 可能崩溃哭泣:“你是来拉屎的吧?”

GC 的恨:你总把锅甩给我

很多线上事故,看似是 GC 引发的,实则是你“喂的太多”。

常见锅甩场景:

内存泄漏

对象不再使用却仍被引用,GC 无法清理。

代码语言:javascript
复制
static List<String> cache = new ArrayList<>();
public void doSomething() {
    cache.add("user_data");
}

你以为是缓存,其实是内存炸弹💣。

创建太多临时对象

代码语言:javascript
复制
for (int i = 0; i < 1000000; i++) {
    String str = new String("test"); // 内存压力++
}

内存频繁抖动,GC 疯狂启动,服务不稳定。

大对象直接进老年代

超过 PretenureSizeThreshold 的对象会跳过新生代,直接进老年。

GC:你一个刚出生的孩子,就住进养老院,这合理吗?

如何优雅地和 GC 相处?

合理配置 JVM 参数,别全靠默认

根据应用需求,设置如下参数:

代码语言:javascript
复制
# 启用 G1 垃圾回收器(适合大多数服务端应用)
-XX:+UseG1GC 

# 设置最大 GC 停顿时间为 200 毫秒,GC 会尽量满足这个目标
-XX:MaxGCPauseMillis=200

# 在老年代使用率达到 45% 时,触发混合 GC(Mixed GC)
-XX:InitiatingHeapOccupancyPercent=45

# 初始堆大小为 2GB(防止服务启动时频繁扩容)
-Xms2g

# 最大堆大小为 2GB(限制内存上限,避免吃光系统资源)
-Xmx2g

你总不能连房子多大都不告诉 GC,让它瞎猜。

定期打 GC 日志,而不是遇事就重启

代码语言:javascript
复制
# 开启 GC 日志,打印每次 GC 的基本信息(JDK 8 及以下)
-verbose:gc

# 更详细的 GC 日志记录(适用于 JDK 9+)
-Xlog:gc*:file=gc.log:time,uptime,level,tags

再用 GCEasy.io、GCViewer 可视化分析。

监控堆内存变化,别等 OOM 再说爱你

接入 Prometheus + Grafana,把:

  • GC 次数
  • GC 停顿时间
  • 堆使用率
  • Full GC 频率

统统接入监控,一眼识破“GC 幕后黑手”。

了解内存布局,别乱调 -Xmx

不懂 JVM 内存模型,只知道“Xmx越大越好”是误区。

大内存虽然延迟 GC,但 GC 一来就得很久。

总结:GC 不是你妈,它只是个管家

它管不了你乱丢垃圾的习惯,它只能负责尽可能的善后。

如果你希望 GC 真正做到“无感、安全、高效”,记住以下几点:

GC 不可怕,可怕的是你啥也不管还怨它干得不够好。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-06-20,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 王中阳 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • GC 是个啥?它真的“自动”吗?
  • Java 对象去哪了?内存分代机制了解一下
    • 新生代(Young Generation)
    • 老年代(Old Generation)
  • GC 算法百家争鸣:谁在替你搬砖?
    • 1. Serial GC(单线程收垃圾)
    • 2. Parallel GC(多线程收垃圾)
    • 3. CMS(Concurrent Mark Sweep)
    • 4. G1(Garbage First)
    • 5. ZGC / Shenandoah
  • GC 的爱:我真的很想帮你
  • GC 的恨:你总把锅甩给我
    • 内存泄漏
    • 创建太多临时对象
    • 大对象直接进老年代
  • 如何优雅地和 GC 相处?
    • 合理配置 JVM 参数,别全靠默认
    • 定期打 GC 日志,而不是遇事就重启
    • 监控堆内存变化,别等 OOM 再说爱你
    • 了解内存布局,别乱调 -Xmx
  • 总结:GC 不是你妈,它只是个管家
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档