RSS持续增长但HeapInuse稳定,说明泄漏不在Go堆上,而是CGO分配的C堆内存未释放、goroutine栈持续增长或runtime.Sys中mmap/栈等非堆内存膨胀。

为什么 RSS 持续增长但 HeapInuse 却很稳?
这不是 GC 没起作用,而是泄漏不在 Go 堆上。常见原因有三:CGO 分配的 C 堆内存未 free、goroutine 栈持续增长(比如阻塞在 channel 上)、或 runtime.MemStats.Sys 中的其他非堆内存(如 mmap 映射、栈空间、代码段)膨胀。直接调大 GOMEMLIMIT 只会让 OOM 来得更晚,不解决根本问题。
- 用
go tool pprof -alloc_space看累计分配量,定位高频分配点 - 用
go tool pprof -inuse_space看当前驻留内存,确认是否真有对象堆积 - 对比
runtime.ReadMemStats中的HeapSys和HeapInuse:差值大说明堆外内存占用高
sync.Pool 该什么时候用、怎么用才安全?
sync.Pool 不是万能缓存,它只适合生命周期短、结构干净、无外部引用的临时对象。用错反而导致泄漏或数据污染。
- 必须实现
New函数,并在Put前重置状态(比如bytes.Buffer.Reset()、entry.next = nil) - 禁止存放含指针字段的结构体(除非你能确保所有指针都已清空),否则可能延长本该回收的对象生命周期
- 不要跨 goroutine 归还:每个 P 有自己的本地池,
Get和Put应尽量在同一个 goroutine 完成 - 高频小对象(如
[]byte、json.Decoder、LRU 的Entry)收益最大;大对象(>32KB)走mheap,Pool 效果有限
预分配切片和 map 有哪些坑?
make([]T, 0, n) 和 make(map[K]V, n) 看似简单,但容量设不准或后续操作不当,反而浪费更多内存。
-
make([]T, 0, n)只预留底层数组容量,不初始化元素,物理页不提交——适合逐步append场景;而make([]T, n)立即清零全部元素,强制占用内存页 - map 预分配容量后,若 key 类型是 struct 或指针,哈希冲突仍可能导致 bucket 扩容,实际内存可能远超预期
- 避免对已预分配的切片反复
append后又[:0]复用:如果中间发生扩容,底层数组可能被遗弃,造成内存泄漏 - HTTP handler 中推荐复用切片:传入一个预先
make好的result参数,用result[:0]清空再填,比每次都make更省
字段排布和逃逸分析怎么影响内存?
结构体字段顺序不只影响大小,还决定变量是否逃逸到堆——而逃逸是高频分配的起点。
立即学习“go语言免费学习笔记(深入)”;
- 用
go build -gcflags="-m"查看变量是否 escape,重点关注函数返回值、闭包捕获、传入 interface{} 的场景 - 字段按大小降序排列:
int64、*T、[32]byte放前面,bool、int8集中放后面,可减少 padding ——unsafe.Sizeof验证前后差异 - 避免小结构体嵌套大数组:比如
struct{ header [16]byte; data []byte },header被迫对齐到 8 字节边界,浪费空间 - HTTP 请求中,把
http.Request的指针传进 handler,而不是复制整个 struct;同理,日志字段优先用string而非fmt.Sprintf构造的临时string


















