直接回答:SetFinalizer 给对象注册一个 GC 回收前的回调,典型用途是释放 Go 堆之外、GC 看不见的资源:C 分配的内存、mmap 区域、GPU 显存、文件句柄包装。对象不可达时 runtime 调用 finalizer(在一个专门的 goroutine 里),对象实际释放推迟到下一轮 GC。Go 1.24 引入更现代的 runtime.AddCleanup,修复了 finalizer 的多个缺陷,新代码应优先用它。
展开解析:坑清单:第一,finalizer 不保证执行——进程退出前 GC 未必运行,它不能替代显式 Close;第二,复活(resurrection)问题:finalizer 里若把对象重新挂到全局可达处,对象复活且 finalizer 已清除,行为微妙,这正是 AddCleanup 禁止引用原对象、改传独立清理数据的原因;第三,循环引用带 finalizer 的对象在旧实现下永不回收(1.24 已修复但仍应避免);第四,时序完全不确定,不能用于“及时”释放稀缺资源(连接池连接数、文件描述符上限),稀缺资源必须显式管理,finalizer 只兜底泄漏。性能上带 finalizer 的对象回收需要两轮 GC,大量小对象注册 finalizer 会显著拖慢回收。工程模式:对象持有外部资源时,暴露 Close 为契约,finalizer/cleanup 只做“忘记 Close”时的兜底加日志告警,帮助发现调用方 bug。
追问方向:AddCleanup 相比 SetFinalizer 改了什么?为什么 finalizer 不能依赖执行时机?
(约 470 字)