钩子:C 语言程序员的"忘 free 恐惧"
写 C 语言的人有一条铁律:malloc 申请的内存,用完必须自己 free。忘了?内存泄漏,程序越跑越肥,直到哪天崩给你看。
而 Java 从第一篇到现在,你 new 了成千上万个对象,一次 free 都没写过,程序也没见越跑越肥。
凭什么?因为 JVM 雇了一位"保洁阿姨"——GC(垃圾回收器,Garbage Collector)。你只管 new,她负责在你不知道的时候,把没用的对象清走。
今天的任务就三件事:她怎么知道谁是垃圾?她什么时候来打扫?以及一个反直觉的真相——Java 也有内存泄漏。
核心:GC 怎么判断谁是垃圾
很多人以为 GC 数引用:一个对象被引用 1 次、2 次……归零就回收。不是这样的。
Java 用的是可达性分析:从一群"根"(GC Roots)出发——
- 栈里的局部变量
- 静态变量
- 常量池引用……
顺着引用链一路爬。能爬到的对象是活人;爬不到的,管你引用数是几,都是垃圾。
为什么不用引用计数?因为"你引用我、我引用你"的两个废弃对象,引用数永远不是 0,但它们已经没有任何用处了。可达性分析一爬,两个都到不了根,双双回收——没有漏洞。

实验:我造了 30MB 垃圾,看她来不来收
demo 里做了两组对照实验:
实验 1:断开引用,请求 GC
static void heapTrash() {
byte[] trash = new byte[1024 * 1024]; // 1MB 局部变量
}
方法一结束,trash 这个引用就出栈了,1MB 对象失去"根"。连造 30 次再调 System.gc():
堆里现有活对象约: 88 MB
System.gc() 之后 : 6 MB
释放了约 81 MB —— 没根的对象被清走了
保洁阿姨真的来收了。
⚠️ 这几个 MB 数每次跑都会浮动(堆里还躺着别的活对象,GC 什么时候来也不固定):你在自己机器上跑,可能是 80 → 10,也可能是别的数。**只要看方向——大幅回落,结论就成立。**下面第二组实验同理。
实验 2:被 static 抓住的对象,她也收不走
把同样的 30MB 塞进一个 static 集合(静态变量是 GC Root),再 gc:
static 集合抓着 30MB,gc 后仍约: 66 MB(降不下来)
只要还有根引用着,对象就不是垃圾。 static 集合只进不出,就是 Java 版内存泄漏——你以为是垃圾,GC 认为它是活人。
关于 System.gc() 还要泼一盆冷水:它只是"建议",JVM 听不听随缘。生产代码里不要指望它,也不要乱调它。
速查表
| 要点 | 内容 |
|---|---|
| GC 是什么 | JVM 的自动内存保洁,new 完不用 free |
| 回收标准 | 可达性分析:从 GC Roots 顺藤摸瓜 |
| 易错点 | 回收标准不是引用计数,互指的垃圾照收 |
| System.gc() | 只是建议,不保证立即执行 |
| Java 内存泄漏 | 该断的引用不断(static 集合只进不出) |
| 下篇 | 36 篇全景地图,全系列收官 |
本文示例代码均已本地实测通过,可直接运行。
对应文件:GcDemo.java
橙码酱,分享更有用的技术知识,助你成为更优秀的开发者。