Go程序被OOM Kill是因RSS超cgroup限制遭内核oom_killer终止,需区分Go堆爆满与RSS虚高;用GOMEMLIMIT设RSS硬限、监控/proc/pid/status的RSS、排查net/http连接池/CGO/未关闭资源等非堆泄漏。

Go 程序被 OOM Kill 的典型现象和确认方式
看到 Killed by signal 9 或 OOMKilled: true(Kubernetes 中),基本就是容器内存超限被系统干掉了。这不是 Go 自身 panic,而是 Linux 内核的 oom_killer 主动杀进程。关键要区分:是 Go 堆内存真爆了?还是 RSS(常驻集)虚高被误杀?runtime.ReadMemStats 报的 Alloc 和 TotalAlloc 只反映 Go 堆分配量,不包括 CGO 分配、mmap 映射、内核缓冲区等——这些都算进 RSS,但 Go runtime 不管。
- 检查容器实际内存上限:
cat /sys/fs/cgroup/memory.max(cgroup v2)或/sys/fs/cgroup/memory/memory.limit_in_bytes(v1) - 查看进程 RSS:
ps -o pid,rss,comm -p <pid>,对比是否接近限制值 - 开启 Go 的内存统计日志:启动时加
GODEBUG=gctrace=1,观察 GC 是否频繁且heap_alloc持续上涨
限制 Go runtime 内存使用:GOMEMLIMIT vs GOGC
Go 1.19+ 引入 GOMEMLIMIT,它才是真正面向 RSS 的硬性水位线;而 GOGC 只控制 GC 触发时机(基于堆增长比例),对 mmap、CGO、栈内存完全无效。
-
GOMEMLIMIT设为略低于容器 limit(比如 limit=1Gi →GOMEMLIMIT=858993459即 0.8Gi),Go runtime 会主动触发 GC,甚至提前拒绝分配,避免触达 OOM 边界 -
GOGC=10(默认 100)能更早回收,但治标不治本;单独调低GOGC可能导致 GC 频繁,CPU 上升,RSS 却没降——因为对象还在被引用,或用了unsafe/syscall.Mmap绕过 GC - 不要同时设
GOMEMLIMIT和极低GOGC,前者已隐含更激进的回收策略,后者反而干扰判断
排查非堆内存泄漏的常见来源
很多 OOM 杀掉 Go 进程,根本不是 make([]byte, N) 导致的,而是这几类“隐身”内存:
-
net/http默认的DefaultTransport会复用连接并缓存 TLS 会话,大量短连接 + HTTPS 可能堆积tls.Conn和底层 socket 缓冲区;建议显式配置MaxIdleConns、MaxIdleConnsPerHost - 使用
database/sql但没调SetMaxOpenConns或SetMaxIdleConns,连接池无节制增长,每个连接背后有 net.Conn + TLS + driver buffer -
io.Copy或http.Request.Body没关闭,底层 socket 缓冲区持续占用;尤其注意defer resp.Body.Close()在 error 分支是否遗漏 - CGO 调用 C 库(如 SQLite、OpenSSL)分配的内存不受 Go GC 管理,必须手动
C.free;漏掉就等于 RSS 持续上涨
验证与压测时的关键观测点
上线前光看本地 go tool pprof 不够,得在接近生产环境的 cgroup 限制下实测:
立即学习“go语言免费学习笔记(深入)”;
- 用
stress-ng --vm 1 --vm-bytes 512M模拟其他进程争内存,观察你的 Go 进程是否提前被 kill - 在容器中运行
watch -n 1 'cat /sys/fs/cgroup/memory.current',对比/sys/fs/cgroup/memory.max - 采集
runtime.MemStats时,重点盯StackInuse(goroutine 栈总占用)、MSpanInuse(GC 元数据)、HeapSys - HeapAlloc(未释放但可回收的堆内存),三者异常升高往往指向 goroutine 泄漏或 sync.Pool 误用 -
pprof -http=:8080 <binary> <profile>中,top -cum看不到问题?试试alloc_space类型,它记录所有分配点,包括已被 GC 回收但曾大量分配的位置
Go 的内存模型里,“限制”从来不是只靠语言层参数就能兜住的事;cgroup 是第一道墙,GOMEMLIMIT 是第二道,而真正决定你能不能活下来的,是那些没被 GC 管到的角落——比如一个忘了 Close() 的 os.File,或者一段裸奔的 C.malloc。










