sync.Pool对象生命周期由GC控制,非开发者决定;Put后对象存入池中但存留时间不可预测,最多存活至下下次GC;New仅兜底创建不参与管理;每次Get需手动重置状态,不可依赖GC或New。

Go 语言中 sync.Pool 对象的生命周期由 Go 运行时的垃圾回收器(GC)控制,不是由开发者、Put() 或 Get() 调用直接决定。
GC 决定对象是否还“活着”
你调用 Put() 后,对象只是被放入本地 P 的私有池或共享池中;它是否保留、何时消失,完全取决于 GC 的调度:
- 每次 GC 执行时,当前 Pool 中未被引用的对象可能被清理
- Go 1.13+ 引入了
victim机制:第一次 GC 不直接删,而是移到 victim 缓存;第二次 GC 才真正丢弃 - 所以一个对象最多存活到“下下一次 GC”之前,但具体哪次被清,无法预测
Pool.New 不是构造钩子,也不延长生命周期
New 字段只在 Get() 返回 nil 时兜底调用,它不参与生命周期管理:
- 它返回的对象不会被自动加入 Pool —— 是直接返回给调用方的
- 它不保证每次
Get()都触发,更不负责清理旧对象 - 误以为设了
New就能自动初始化字段,结果拿到脏数据(比如残留的buf.Bytes()),是高频 bug 来源
对象状态必须手动重置,不能依赖 GC 或 New
因为 GC 不会调用任何析构逻辑,sync.Pool 也不提供“释放前回调”,所以:
- 每次从
Get()拿到的对象,字段值是任意的(可能是上次用完没清零的) - 必须在使用前显式重置:比如
buf.Reset()、req.Header = make(http.Header)、ctx.ts = 0 - 如果对象持有外部资源(文件描述符、锁、channel),
sync.Pool完全不适合 —— 它不会帮你关、解锁或关闭
真正容易被忽略的是:你以为自己在“复用对象”,其实只是在和 GC 赌运气 —— 它可能刚放进去就被收走,也可能滞留多个 GC 周期。别把它当缓存用,也别指望它保活;它的唯一价值,是把“分配→用→丢”这个链条里最贵的那一步(堆分配)省掉几次。


















