有人以为 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 ,让你无感知地过日子。
你写的这段代码:
java
public void foo() {
String s = new String("hello");
}
方法执行完后,s
引用就失效了,GC 老哥就会把这个 "hello" 给回收了(如果没被缓存)。
你要是写:
java
List<String> list = new ArrayList<>();
while (true) {
list.add("hello");
}
GC 可能崩溃哭泣:"你是来拉屎的吧?"

GC 的恨:你总把锅甩给我
很多线上事故,看似是 GC 引发的,实则是你"喂的太多"。
常见锅甩场景:
内存泄漏
对象不再使用却仍被引用,GC 无法清理。
java
static List<String> cache = new ArrayList<>();
public void doSomething() {
cache.add("user_data");
}
你以为是缓存,其实是内存炸弹💣。
创建太多临时对象
java
for (int i = 0; i < 1000000; i++) {
String str = new String("test"); // 内存压力++
}
内存频繁抖动,GC 疯狂启动,服务不稳定。

大对象直接进老年代
超过 PretenureSizeThreshold
的对象会跳过新生代,直接进老年。
GC:你一个刚出生的孩子,就住进养老院,这合理吗?
如何优雅地和 GC 相处?
合理配置 JVM 参数,别全靠默认
根据应用需求,设置如下参数:
bash
# 启用 G1 垃圾回收器(适合大多数服务端应用)
-XX:+UseG1GC
# 设置最大 GC 停顿时间为 200 毫秒,GC 会尽量满足这个目标
-XX:MaxGCPauseMillis=200
# 在老年代使用率达到 45% 时,触发混合 GC(Mixed GC)
-XX:InitiatingHeapOccupancyPercent=45
# 初始堆大小为 2GB(防止服务启动时频繁扩容)
-Xms2g
# 最大堆大小为 2GB(限制内存上限,避免吃光系统资源)
-Xmx2g
你总不能连房子多大都不告诉 GC,让它瞎猜。
定期打 GC 日志,而不是遇事就重启
bash
# 开启 GC 日志,打印每次 GC 的基本信息(JDK 8 及以下)
-verbose:gc
# 更详细的 GC 日志记录(适用于 JDK 9+)
-Xlog:gc*:file=gc.log:time,uptime,level,tags
监控堆内存变化,别等 OOM 再说爱你
接入 Prometheus + Grafana,把:
- GC 次数
- GC 停顿时间
- 堆使用率
- Full GC 频率
统统接入监控,一眼识破"GC 幕后黑手"。
了解内存布局,别乱调 -Xmx
不懂 JVM 内存模型,只知道"Xmx越大越好"是误区。
大内存虽然延迟 GC,但 GC 一来就得很久。
总结:GC 不是你妈,它只是个管家
它管不了你乱丢垃圾的习惯,它只能负责尽可能的善后。
如果你希望 GC 真正做到"无感、安全、高效",记住以下几点:
正确做法 | 说明 |
---|---|
合理使用对象引用 | 用完就释放,别 static 到天荒地老 |
避免频繁创建对象 | 能重用就重用,尤其是在循环里 |
选对 GC 算法 | G1 是目前主力,低延迟可用 ZGC |
配置合适 JVM 参数 | 根据服务特点精调,不要复制粘贴 |
监控 GC 行为 | 日志 + 指标,一个都不能少 |
GC 不可怕,可怕的是你啥也不管还怨它干得不够好。
