一句话定义:垃圾回收(Garbage Collection,GC)是程序运行时自动找出“以后再也用不到的内存”、并把它还给系统的机制,让写代码的人不必手动申请和释放每一块内存。
原理:从根出发做一次可达性点名
把进程内存想成一栋酒店。程序创建对象,相当于开房入住;对象不再被使用,相当于客人退房离开。GC 就是清洁工,它不靠“问客人还在不在”,而是从大堂前台(根集合:栈上的局部变量、全局变量等)拿着房卡名单逐层点名:顺着引用关系能走到的对象,就算活着;走不到的对象,就是垃圾,房间可以清空。这套做法叫可达性分析(reachability analysis),主流托管运行时(如 JVM、.NET、Go、CPython 的循环回收器)都基于它。
关键在“什么时候”:GC 不会在对象不用时立刻回收,而是由分配压力触发。新对象大多朝生夕死,堆里通常划出一块“新生代”,这块区域塞满就做一次小回收(minor GC),活下来的对象被搬到“老年代”;老年代也满了,才做一次全量回收(full GC)。所以回收时机对业务代码来说是“不可预测但一定会来”。
为什么 AI 服务会周期性卡一下
GC 干活时,为了拿到一致的对象图,通常要暂停业务线程,这叫 stop-the-world(STW)。哪怕只有几十毫秒,对在线推理也是硬伤。而推理服务恰好是分配最凶的场景之一:每个请求都要申请一批张量、中间结果、字符串,用完立刻丢。分配速率高,回收就频繁,延迟曲线(尤其是 P99)于是出现规律的尖刺。这时候先别急着怀疑模型慢,看一眼 GC 日志往往更省时间。常见的缓解手段是减少临时对象、复用缓冲区、错开批量任务的节奏,以及挑选更适合低延迟的收集器;具体有哪些选项,以所用运行时的官方文档为准。
“内存泄漏”在托管语言里长什么样
它往往表现为“忘记解除引用”,并非“忘记 free”:对象早已无用,却仍被某个长生命周期的容器持有,比如只增不减的全局缓存字典、注册后没注销的监听器、闭包顺手捕获的大对象。可达性分析认为它“还有人引用”,于是永远不回收。症状很典型:进程常驻内存单调上升,full GC 越来越频繁,但每次回收回来的越来越少,因为 GC 很努力,却收不动。
和相邻概念的区别
| 机制 | 何时回收 | 典型坑 |
|---|---|---|
| 手动管理(malloc/free) | 代码显式调用 | 忘记释放、重复释放 |
| 引用计数 | 计数归零立即回收 | 循环引用永不归零 |
| 追踪式 GC | 分配压力触发 | 停顿尖刺、内存迟迟不还 |
另外,GC 只管内存,不管其他资源。文件句柄、数据库连接、GPU 显存这类东西,通常需要显式释放,别指望靠 GC 顺带清理。
对从业者的意义:排查性能问题、规划容量时,要把内存和 GC 当作首要关注点,不要只看 CPU。对普通人来说,这解释了为什么有些服务“跑着跑着就慢一点、然后自己恢复”,也解释了为什么“内存越用越多”不一定是 bug,但“只增不减”一定是。
