&MyStruct{} 可能栈分配而 new(MyStruct) 总堆分配,因编译器对前者更易做逃逸分析优化;复用指针对象、预分配切片、谨慎使用 sync.Pool 等可显著降低 GC 压力。

为什么 new 和 &struct{} 在 GC 上表现不同
Go 的 GC 主要压力来自堆上频繁分配小对象,尤其是短生命周期的结构体。用 new(MyStruct) 或 &MyStruct{} 看似一样,但编译器对后者更可能做逃逸分析优化——如果能证明该指针不会逃逸到函数外,就直接分配在栈上,根本不上堆。new 则总是返回堆地址,强制触发分配。
- 逃逸分析不是万能的:只要把
&MyStruct{}赋值给全局变量、传入 interface{}、或作为返回值(且接收方类型不明确),它就大概率逃逸 -
go build -gcflags="-m" main.go可以看具体哪行逃逸了,重点关注... escapes to heap提示 - 结构体字段含指针(比如
name *string)会显著提高逃逸概率,哪怕结构体本身很小
复用指针对象比反复 new 更有效
对高频调用路径(如 HTTP 中间件、数据库扫描循环),预先分配好指针对象并重置字段,比每次 &MyStruct{} 更省 GC。这不是“对象池”的替代方案,而是更轻量、无同步开销的选择。
- 适合固定大小、字段可安全重置的结构体,比如
type RequestCtx struct { ID int; Path string; Reset() } - 避免在重置时漏掉字段(尤其嵌套结构体或指针字段),否则残留引用会阻止 GC 回收关联内存
- 不要为单次使用的结构体搞复用——编译器栈分配比你手动管理更高效,反而增加心智负担
切片和 map 的指针传递容易误增 GC 压力
传递 *[]T 或 *map[K]V 本身不减少分配,但常被误认为“避免拷贝”而滥用。真正影响 GC 的是底层数组和哈希桶的分配行为。
-
make([]T, 0, 1024)预分配容量后,再用append扩容,比每次make([]T, len)少得多的重新分配次数 - map 不支持预分配桶数,但可复用已初始化的 map:清空用
for k := range m { delete(m, k) },而不是m = make(map[K]V) - 接收参数用
func f(data []byte)而非func f(data *[]byte)—— 切片头是小结构体,传值成本低,且更利于内联和逃逸分析
sync.Pool 不是银弹,用错反而加重 GC
sync.Pool 适合缓存临时对象(如 *bytes.Buffer、*json.Decoder),但它本身维护一个 per-P 的本地池 + 全局池,GC 时会清空所有池子。如果对象生命周期长于一次 GC 周期,或者池子中对象没被及时 Get/Reuse,就会白占内存。
立即学习“go语言免费学习笔记(深入)”;
- Put 前必须确保对象字段已重置,否则上次使用残留的数据可能污染下次使用
- Pool 对象没有析构回调,无法自动释放 C 资源或关闭文件描述符,得靠使用者显式清理
- 高并发下 Pool 的 Get/Put 有原子操作开销,若对象创建本身很轻(比如
&struct{}),用 Pool 可能得不偿失
最常被忽略的是:GC 压力不仅来自分配频次,更来自存活对象总量。减少指针深度、缩短引用链、让结构体字段尽量紧凑,比纠结某一行用不用指针影响更大。










