全局map内存泄漏问题的排查和解决
pods表现为内存持续增长
使用pprof定位到具体函数
找到对应代码
type EBus struct { eventChan chan *Event // 内部事件 eventHandlers map[int]func(*Event) subscribers map[string]map[Subscriber]struct{} // 订阅者 running bool // 记录服务是否正在运行 } func (p *EBus) pub(e *Event) { ... } // 订阅消息 func (p *EBus) sub(e *Event) { if _, ok := p.subscribers[e.name]; !ok { p.subscribers[e.name] = make(map[Subscriber]struct{}) p.subscribers[e.name][e.subscriber] = struct{}{} return } if _, ok := p.subscribers[e.name][e.subscriber]; ok { return } p.subscribers[e.name][e.subscriber] = struct{}{} } // 取消指定消息订阅 func (p *EBus) unSub(e *Event) { ... delete(p.subscribers[e.name], e.subscriber) } // 取消所有消息订阅 func (p *EBus) unSubAll(e *Event) { ... delete(p.subscribers, e.name) }
分析问题
根据pprof能看出内存泄漏在sub函数
sub里面会分配内存的就只有p.subscribers
subscribers是一个map
可以看到unSub、unSubAll都是删除map对应key的方法
首先怀疑是不是每正常调用这两个函数取消订阅,但是读完业务代码后发现是正常unSubAll了的
最后想起来,map,delete key后是会释放value的空间,但是map自身的空间是不会被释放的
这是常见的全局map出现内存泄漏的问题验证问题
func printMemUsage() { var m runtime.MemStats runtime.ReadMemStats(&m) fmt.Println("Memory Allocation:", m.Alloc/1024/1024, "MB") } var myMap = make(map[int]int) func TestMap(t *testing.T) { for i := 0; i < 1000000; i++ { myMap[i] = i } for i := 0; i < 1000000; i++ { delete(myMap, i) } // 触发垃圾收集并获取内存统计信息前的内存分配量 printMemUsage() // 强制进行垃圾收集并读取内存统计信息 debug.FreeOSMemory() runtime.GC() // 打印对象创建后的内存分配量 printMemUsage() }
修改代码:map放到函数内,即从全局map改为函数内mapfunc TestMap(t *testing.T) { // 创建一个对象 var myMap = make(map[int]int) for i := 0; i < 1000000; i++ { myMap[i] = i } for i := 0; i < 1000000; i++ { delete(myMap, i) } ... ...
可以看到修改后的内存明显下降了怎么修复
全局map的内存泄漏是典型的问题
修复这个问题只能想办法让这个map被回收
我这里是加了个定时器,每分钟重置下map
本作品采用《CC 协议》,转载必须注明作者和本文链接
感觉用
sync.Pool
来解决更好。我看你使用的是重建策略。经实测 把map定义放到函数内并没减少内存:
以下是测试结果:
虽然 map delete 后内存不会被释放,但是在后续继续写map时,这些内存是会被复用的。全局 map 泄露只能解释内存居高不下,但是不能解释持续增长。