发布于2026-07-03 阅读(0)
扫一扫,手机访问
很多人都以为 sync.Pool 在 GC 时会一股脑儿把所有对象都清空,其实不然。它的清理分两轮:第一轮 GC 把对象从本地池挪到 victim 缓存里“缓刑”一轮,第二轮 GC 才真正丢弃。换句话说,你放进池子的对象最多能活到“下下一次 GC”之前,但具体是哪一次被回收,你没法提前知道。

每次 GC 触发时,runtime 都会调用 poolCleanup,但它并不是直接释放所有对象——它会做以下几件事:
private)和共享队列(shared)被清空,里面的对象失去引用,等待 GC 回收。p.local)被整体“降级”为 victim 缓存(p.victim),而新分配的 p.local 则初始化为空。举个例子:你在第 N 次 GC 前 Put 进去的对象,可能在第 N+1 次 GC 后还留在 victim 里,直到第 N+2 次 GC 才消失。但只要你的 goroutine 在第 N+1 次 GC 后立刻调用 Get(),它仍有可能从 victim 里捞出那个对象——这就给了对象一次“回光返照”的机会。
victim 不对外暴露,也不参与常规 Get() 的 fast path。它的唯一价值在于:缓解 GC 突刺性回收带来的复用率骤降。它是怎么做的呢?
Get() 走完本地 private → local shared → 其他 P 的 shared 之后,才会去查 victim。victim 是只读的:不接受 Put(),也不会被任何 goroutine 主动填充。假设你 Put 了一个含 *bytes.Buffer 字段的结构体,没有重置指针,它进入 victim 后仍然持有对底层字节数组的引用,那块内存就无法被归还给 OS。这是典型的“好心办坏事”。
很多人会误以为“victim 里的对象更新,字段应该更干净”,这是一个危险的错觉。
New 函数绝不会在 victim 命中时触发,所以你拿不到兜底初始化逻辑。Get() 结果一样,手动调用 Reset() 或清零关键字段。一个经典反例:buf := pool.Get().(*bytes.Buffer); buf.WriteString("hello")——如果没调用 buf.Reset() 就直接 Put(),下次从 victim 拿出来的 buf 里可能还残留 "hello\000\000...",导致解析错误甚至 panic。真正关键的从来不是 victim 存多久,而是你是否在每次 Get() 后、使用之前就重置状态——GC 和 victim 都不关心你的业务逻辑,它们只管内存能不能回收。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8