victim cache 是 GC 前将各 P 的 local 池整体迁移形成的“上一轮快照”,仅提供一次延迟淘汰机会,下轮 GC 才清除;Get() 在本地及远程 shared 均失败后才查 victim,属 slow path,耗时高 3–5 倍。

GC 时 sync.Pool 的 victim cache 是什么
Victim cache 是 Go 运行时在每次 GC 前为每个 P(逻辑处理器)临时保留的一份“上一轮”本地池快照,不是新分配的缓存区,而是把当前 local 池整体移动到 victim,再新建空 local。它不延长对象生命周期,只提供一次“延迟淘汰”机会:本轮 GC 不清掉,下轮 GC 才真正丢弃。
Get() 在 victim cache 中查找的时机和代价
当 Get() 在当前 P 的 local.private 和 local.shared 都没拿到对象,且跨 P 查 shared 也失败后,才会查 victim。这个路径属于 slow path,比本地 fast path 多出至少两次原子操作和一次指针跳转,实际耗时可能高 3–5 倍。
- Victim 查找只发生在
getSlow()末尾,且仅限当前 P 的 victim slot(pid ) - Victim 中的对象仍是“未被引用”的,如果 Get 后不立刻使用并 Put 回去,大概率在下次 GC 时被永久清理
- 不能依赖 victim 提供稳定复用——它只是 GC 机制下的副产品,不是设计用来提升命中率的
为什么 New 函数在 victim 清理后才可能被调用
New 只在 Get() 走完全部路径(local → shared → remote → victim)都返回 nil 时触发。而 victim 是最后一道防线,所以只有当 victim 也被清空或当前 P 没分配 victim slot 时,New 才会被调用。这意味着:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- GC 后首次
Get()很可能触发New,尤其在低并发或刚启动阶段 - 如果对象构造成本高(比如含大 slice 或 map),应确保
New返回的是预分配好容量的干净实例,而非每次都make([]byte, 0) - 别在
New里做任何阻塞操作(如http.Get),因为 GC 会同步等待它返回
Victim cache 对内存 RSS 的真实影响
Victim cache 本身不增加额外内存占用——它只是把原 local 池的指针搬了个位置。但它的存在会让对象“多活一轮 GC”,可能导致 RSS 短暂升高,尤其当大量 goroutine 在 GC 前集中 Put 对象时。
- 观察
runtime.ReadMemStats中的HeapAlloc和HeapInuse,能发现 GC 后它们下降得比预期慢,这就是 victim 滞留效应 - 若服务有明显流量波峰,随后进入低谷,victim 中残留的对象会在下轮 GC 才释放,造成“内存回落延迟”
- 无法通过代码控制 victim 行为,也不能手动清空;唯一可控点是确保
Put()前对象已重置,避免脏数据借 victim 多传一轮
Victim cache 的设计意图很务实:不追求零延迟回收,而是用极小运行时开销换一次“软性缓冲”。真正容易被忽略的是,它让 GC 清理不再是原子事件——对象的消亡被拆成了两步,而这一步之差,足以让未重置字段、未截断切片、未 close 的 channel 在生产环境里安静地埋伏一整个 GC 周期。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










