Go中真正内存泄漏少见,多数是goroutine泄漏或资源未释放所致:NumGoroutine持续上涨、tcp.inuse居高不下、heap_inuse不回落;pprof可见大量chan receive/select卡死,阻塞时间长达数小时,堆栈指向net/http.readLoop等;主因包括nil channel操作、无缓冲channel收发失配、for range未close、context未传递超时、http client/server超时缺失、io.Closer未Close、context传递断裂等。

Go 里几乎不存在传统意义上的“内存泄漏”,真正拖垮服务的,是 goroutine 卡死、channel 阻塞、io.Closer 没 close、context 超时缺失这四类问题。
pprof 看到大量 chan receive 或 select 卡死,说明 goroutine 已泄漏
这不是 GC 失效,而是协程永远停在 channel 操作上,栈和堆内存都跟着挂住不释放。典型现象包括:
-
/debug/pprof/goroutine?debug=2返回里反复出现状态为chan receive,阻塞时间动辄几万秒(比如432000s) - 堆栈指向
net/http.readLoop、golang.org/x/crypto/ssh,或你自己的for range ch循环 - 同一类调用栈数量随请求量线性增长
常见触发点:
- 声明了
var c chan int但没初始化,后续直接c <- 1或<-c—— 永久阻塞 - 无缓冲 channel 写入后,接收端没启、已退出、也没
close(c) -
for range ch循环,但没人关ch;或select所有case都不可达且没default - HTTP handler 里起
go func() { ... }(),但既没监听req.Context().Done(),也没传入 context
http.Client 和 http.Server 超时缺失,导致连接堆积成协程海
这不是报错,是静默吃资源:每个空闲连接背后都挂着至少一个 readLoop + writeLoop goroutine,持续占内存和文件句柄。
立即学习“go语言免费学习笔记(深入)”;
服务端修复方式:
- 显式配置超时:
srv := &http.Server{Addr: ":8080", ReadTimeout: 5 * time.Second, IdleTimeout: 30 * time.Second} - 避免用
http.ListenAndServe默认启动,它等价于没设任何超时
客户端修复方式:
- 绝不依赖
http.DefaultClient—— 它没超时、没连接池限制、keepalive 无限续命 - 始终用
http.NewRequestWithContext(ctx, ...),并自定义http.Transport:tr := &http.Transport{ MaxIdleConns: 100, MaxIdleConnsPerHost: 32, IdleConnTimeout: 30 * time.Second, }然后绑定到 client:
client := &http.Client{Transport: tr}
slice 浅拷贝导致底层数组长期滞留
这不是 GC 不工作,而是你无意中让全局变量持有了大底层数组的引用。
典型代码:
var a []int
func test(b []int) {
a = b[:3] // ← 关键:a 和 b 共享同一底层数组
}如果 b 原来是 10MB 的切片,只取前 3 个元素赋给 a,那整个 10MB 数组只要 a 还活着就无法回收。
安全做法:
- 用
append强制新分配:a = append([]int(nil), b[:3]...) - 或用
copy到新 slice:a := make([]int, 3); copy(a, b[:3]) - 避免把局部大 slice 的子切片直接赋给包级变量
io.Closer 类型漏 close,文件描述符/连接/数据库连接持续累积
所有实现了 io.Closer 接口的对象——os.File、http.Response.Body、sql.Rows、sql.Conn——都必须显式 Close()。漏一个,可能拖垮整个进程。
高频踩坑点:
-
resp, err := http.Get(...)后忘了defer resp.Body.Close() - 用
os.Open打开文件,但异常路径没Close(建议统一用defer f.Close()放在Open后立刻写) - DB 查询用了
db.Query返回*sql.Rows,但没循环完就 return,也没rows.Close() - 第三方库返回的可关闭对象(比如某些 SDK 的
Stream),文档没强调但接口含Close方法
最危险的是:这类问题不 panic、不报错,只在 lsof -p PID 里看到 tcp 或 pipe 数量缓慢上涨,直到达到系统上限。


















