finalizer 在 gc 发现带 runtime.setfinalizer 的对象不可达、准备回收其内存前异步触发一次,不保证调用时机与是否执行;必须传指针且函数签名为 func(*t),仅适用于兜底清理外部资源,不可替代显式 close/free。

finalizer 什么时候会被调用?
它不是“对象销毁时回调”,而是 GC 发现某个带 runtime.SetFinalizer 的对象不可达、且准备回收其内存前,**异步触发一次**。这意味着:你无法预测调用时机,甚至可能完全不被调用(比如程序提前退出、对象在 GC 前又被引用回活状态)。
常见错误现象:finalizer 里打印日志但没输出;资源没释放;以为能靠它做“析构”结果泄漏。
- 必须传入指针类型给
runtime.SetFinalizer,传值会报错:SetFinalizer: pointer required - finalizer 函数签名必须是
func(*T),不能是func(T)或func(interface{}) - 如果对象在 finalizer 执行前又被赋值给全局变量或闭包,GC 就不会回收它,finalizer 永远不触发
为什么给 *int 设 finalizer 后,int 值还能被修改?
因为 runtime.SetFinalizer 绑定的是指针指向的**堆上对象的生命周期**,不是该指针变量本身的生命周期。只要那个 *int 还活着(比如被局部变量持有),finalizer 就不会跑;而 finalizer 内部拿到的 *int,依然可以读写其指向的值。
使用场景:适合清理与指针强绑定的外部资源(如 C 分配的内存、文件描述符、网络连接),而不是管理 Go 原生值语义。
立即学习“go语言免费学习笔记(深入)”;
- 不要指望 finalizer 修改结构体字段来“标记已清理”——GC 可能根本没运行
- finalizer 中禁止调用阻塞操作(如
time.Sleep、net.Conn.Read),会拖慢整个 GC 线程 - 同一对象多次调用
runtime.SetFinalizer,只会保留最后一次设置的函数
finalizer 和 GC 触发时机的关系
Go 的 GC 是并发、增量式的,finalizer 不在 STW 阶段执行,而是在后台 goroutine 中统一处理。所以即使你手动调用 runtime.GC(),finalizer 也可能延迟几十毫秒甚至更久才执行。
性能影响:每个带 finalizer 的对象都会被加入一个全局链表,GC 需额外扫描;大量使用会增加 GC 压力和延迟。
- finalizer 不保证执行顺序,多个对象之间无依赖关系假设
- 如果 finalizer panic,会被捕获并记录到
stderr,但不会中断 GC 流程 - 交叉引用(A 持有 B 的指针并设 finalizer,B 也持有 A)可能导致两者都延迟回收,甚至泄露
替代 finalizer 的更可靠方案
绝大多数情况下,你应该用显式资源管理(Close()、Free())代替 finalizer。Go 生态中成熟的模式是:实现 io.Closer、用 defer 调用、配合 context 控制生命周期。
只有当资源生命周期确实无法由业务逻辑掌控(例如 Cgo 返回的裸指针、底层驱动句柄),finalizer 才是兜底手段,且必须配合超时/重试机制。
- 永远在 finalizer 外层加
recover(),防止 panic 影响 GC - 避免在 finalizer 中访问其他 Go 对象(尤其是 map、channel、mutex),它们可能已被回收或处于不一致状态
- 测试 finalizer 行为要用
runtime.GC()+runtime.Gosched()多次轮询,不能只靠一次GC()
finalizer 的本质是 GC 的副产品,不是控制流工具。把它当“尽力而为”的清理钩子,而不是“一定会发生的析构”。真正关键的资源释放,从来不该交给 GC 来决定。










