全局map内存泄漏问题的排查和解决 | go 技术论坛-金年会app官方网

  1. pods表现为内存持续增长
    一次典型内存泄漏的排查

  2. 使用pprof定位到具体函数
    一次典型内存泄漏的排查

  3. 找到对应代码

    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)
    }
  4. 分析问题
    根据pprof能看出内存泄漏在sub函数
    一次典型内存泄漏的排查
    sub里面会分配内存的就只有p.subscribers
    subscribers是一个map
    可以看到unsub、unsuball都是删除map对应key的方法
    首先怀疑是不是每正常调用这两个函数取消订阅,但是读完业务代码后发现是正常unsuball了的
    最后想起来,map,delete key后是会释放value的空间,但是map自身的空间是不会被释放的
    这是常见的全局map出现内存泄漏的问题

  5. 验证问题

    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改为函数内map

    func 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)
     }
    ... ...

    一次典型内存泄漏的排查
    可以看到修改后的内存明显下降了

  6. 怎么修复
    全局map的内存泄漏是典型的问题
    修复这个问题只能想办法让这个map被回收
    我这里是加了个定时器,每分钟重置下map
    全局map内存泄漏问题的排查和解决

本作品采用《cc 协议》,转载必须注明作者和本文链接
讨论数量: 5

感觉用sync.pool来解决更好。我看你使用的是重建策略。

1周前
ezreal_rao (作者) 1周前
1周前

经实测 把map定义放到函数内并没减少内存:

import (
"fmt" 
"runtime"
)
func printmemusage() {
 var m runtime.memstats
 runtime.readmemstats(&m)
 fmt.println("memory allocation:", m.alloc/1024/1024, "mb")
}
func testmap() {
 var mymap = make(map[int]int)
 for i := 0; i < 1000000; i {
     mymap[i] = i
 }
 for i := 0; i < 1000000; i {
     delete(mymap, i)
 }
 // 触发垃圾收集并获取内存统计信息前的内存分配量
 printmemusage()
 runtime.gc()
 // 打印对象创建后的内存分配量
 printmemusage()
}

以下是测试结果:

gomacro> testmap()
memory allocation: 73 mb
memory allocation: 41 mb
1周前

虽然 map delete 后内存不会被释放,但是在后续继续写map时,这些内存是会被复用的。全局 map 泄露只能解释内存居高不下,但是不能解释持续增长。

1周前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!
网站地图