一、GC基础概念与原理

1.1 垃圾回收到底在干嘛?

简单来说,垃圾回收(Garbage Collection,GC)就是自动帮我们管理内存的机制。你想想看,以前写 C/C++ 的时候,malloc 出来的内存如果忘了 free,就会泄漏。Java 不一样,JVM 会自己盯着堆内存,发现哪些对象没人用了,就悄悄把它们回收掉。

我个人习惯把 GC 理解为「JVM 内部的保洁阿姨」——她定期巡视,把废弃的东西清走,腾出空间给新对象。但保洁阿姨清扫的时候,其他人得停下手里的活(这就是 Stop-The-World),所以 GC 不是免费的。

💡 避坑指南
我曾经遇到过一线上系统,每 10 秒就出现一次长暂停。排查发现是一个 HashMap 缓存只增不减,导致 Full GC 频繁触发。后来改成弱引用 + 过期策略,问题就解决了。

1.2 GC 到底解决了什么问题?

GC 的核心作用其实就三个:

  • 自动内存管理——你只管 new 对象,JVM 帮你管生死。
  • 防止内存泄漏——至少能兜住大部分显性泄漏,但隐性的(比如长生命周期对象引用短生命周期对象)还是需要开发者注意。
  • 提高开发效率——不用手动 free,代码出 bug 的概率低很多。

但这不代表你可以完全不管内存。我见过一个项目,因为循环创建大量临时对象,导致 Young GC 每秒触发几十次,CPU 都耗在 GC 上了。所以了解 GC 原理,是为了更好地驾驭它。

1.3 JVM 里的 GC 是怎么实现的?

JVM 的 GC 实现,说白了就是一套「分代收集」策略。为什么分代?因为大部分对象活不长,朝生夕死。把堆分成几个区域,用不同的策略处理,效率更高。

区域 作用 GC 频率 常用算法
年轻代(Young) 存放新对象 高(Minor GC 非常频繁) 复制算法
老年代(Old) 存放长期存活对象 低(Major/Full GC 较少) 标记-整理 / 标记-清除
元空间(Metaspace) 存放类元数据 极少(类卸载时触发) 具体实现依赖 GC 收集器

常用的垃圾收集器也有不少,我列举几个你大概率会碰到的:

收集器 特点 适用场景
Serial 单线程,暂停时间短但集中 单核、客户端模式
Parallel 多线程并行,追求吞吐量 后台批处理、科学计算
CMS 低延迟,但浮动垃圾多 互联网应用(已被 G1 取代)
G1 分区化,可预测暂停时间 多核大内存服务器(默认 JDK 9+)
ZGC 超低延迟(亚毫秒级) 超大堆、低延迟要求高

想观察 GC 行为,最简单的方式就是在启动参数里加上:

-Xms2G -Xmx2G -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log

然后看 gc.log 文件,Minor GC、Full GC 的耗时和频率一目了然。
我曾经用这个日志定位过一个诡异问题——每次 Full GC 都要 5 秒,后来发现是因为堆分配了 16G,但 JVM 没有启用 G1 的 -XX:G1HeapRegionSize 调优,导致 region 数量过多。调小 region 后,暂停时间降到了 1 秒以内。

⚠️ 注意
GC 日志本身也会带来性能开销,线上环境建议使用 -XX:+UnlockDiagnosticVMOptions -XX:+PrintGCApplicationStoppedTime 来精确记录暂停时间,不要开太多 Detail 日志。

1.4 启动阶段的 GC:为什么会影响速度?

应用启动的时候,大量对象被创建——类加载、框架初始化、连接池预热……年轻代很快就满了。此时 Minor GC 会很频繁。如果堆配置不当,甚至可能在启动过程中触发 Full GC,把启动时间拖长好几倍。

这就是我们做「GC 抑制优化」的切入点——不是要禁止 GC,而是在启动阶段尽量减少 GC 的触发次数和暂停时长,让应用尽快完成初始化。

举个真实案例:某个微服务启动需要 30 秒,其中 GC 暂停就占了 8 秒。我们把初始堆大小调到和老年代预估大小相等,同时调整晋升阈值,启动时间缩短到了 18 秒。说白了,就是让 GC 在启动阶段「少干活」。

GC 核心概念与原理框架 垃圾回收定义 自动内存管理 防止内存泄漏 提高开发效率 JVM 中的 GC 实现 分代收集模型 Young / Old / Metaspace 常用垃圾收集器 G1 / ZGC / Parallel / CMS 启动阶段影响 频繁 Minor / 可能 Full GC GC 抑制优化(启动加速) 理解 GC 原理是优化启动的基础,后续章节将逐一拆解各收集器的抑制策略。
📌 核心要点
  • GC 是自动内存管理工具,但需要付出暂停代价。
  • JVM 采用分代收集 + 多收集器选择。
  • 启动阶段 GC 频繁会拖慢初始化,这就是我们需要优化的地方。