一个长驻的消费协程跑了三天,RSS 从 80M 涨到 900M,heap profile 里 NewTimer 相关对象占了大部分,代码里却没几处显式创建定时器,看着像内存凭空多出来的。第一次遇到会往复杂处想,其实漏掉的多是个小前提。
go tool pprof -http=:8080 heap.out grep -n 'time.After' -r . go test -bench=. -benchmem -run=^$ ./...
time.After 每次都新建一个 Timer,在到期前不会被回收,循环里每轮都建一个,且 select 往往在别的分支提前返回,这些定时器要等超时才释放,堆积起来就是内存。这一点先确认清楚,后面就不用反复试。
碰到「time.After 泄漏」这类问题,我习惯先把当前配置原样备份一份,改坏了立刻换回来,不用现场推。临时改动用注释写上日期和原因,过两周自己都记不清为什么这么写。
动手前把原文件复制一份带时间戳,出问题立刻换回去,比现场分析快得多。
具体做法是,把循环内每次新建的定时器提到循环外面复用;需要重新设置的时候先停止计时器,然后再去重置;热路径上的等待,改用带缓冲的周期触发器实现。
改完照着核一遍,跑同样时长后内存曲线走平,堆里定时器对象数量不再增长。
