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

有人以为 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

再用 GCEasy.ioGCViewer 可视化分析。

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

接入 Prometheus + Grafana,把:

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

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

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

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

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

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

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

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

正确做法 说明
合理使用对象引用 用完就释放,别 static 到天荒地老
避免频繁创建对象 能重用就重用,尤其是在循环里
选对 GC 算法 G1 是目前主力,低延迟可用 ZGC
配置合适 JVM 参数 根据服务特点精调,不要复制粘贴
监控 GC 行为 日志 + 指标,一个都不能少

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

相关推荐
小莫分享11 分钟前
2023年最新总结,阿里,腾讯,百度,美团,头条等技术面试题目,以及答案,专家出题人分析汇总。
java·后端·面试·职场和发展
Brookty12 分钟前
【操作系统】线程
java·linux·服务器·后端·学习·java-ee·操作系统
Dovis(誓平步青云)14 分钟前
探索飞算 JavaAI 进阶:解锁高效Java开发的新维度
java·开发语言·飞算java
源码云商15 分钟前
基于 SpringBoot + Vue 的 IT 技术交流和分享平台的设计与实现
vue.js·spring boot·后端
小雪_Snow20 分钟前
多态 使用场景
java
byte轻骑兵21 分钟前
BLE低功耗设计:从广播模式到连接参数优化的全链路分析与真题解析
面试·职场和发展
程序员爱钓鱼1 小时前
Go语言实战案例-简易计算器(加减乘除)
后端
DoraBigHead1 小时前
小哆啦解题记——两数失踪事件
前端·算法·面试
学不会就看1 小时前
Django--01基本请求与响应流程
后端·python·django
胚芽鞘6815 小时前
关于java项目中maven的理解
java·数据库·maven