golang 的内存泄漏分析
什么是内存泄漏
内存泄漏说白了就是分配的内存(或者变量)不再使用,但是并没有被 gc 回收,而是继续占用内存;
内存泄漏通常是由于程序中存在长时间存在的对象引用而未被垃圾回收机制回收导致的
内存泄漏场景
substring
-
案例
var s0 string // 包级别变量 // A demo purpose function. func f(s1 string) { s0 = s1[:50] //s0 和 s1 共用相同的底层memory block // 尽管 s1 不再使用,但是 s0仍然存活状态 // 所以s1 的内存不会被回收,尽管只有 50 byte内存 // 被使用,但是其他内存都泄漏了。 } func demo() { s := createStringWithLengthOnHeap(1 << 20) // 1M bytes f(s) } // 可以用如下方式来避免 func f(s1 string) { s0 = string([]byte(s1[:50])) }
卡住的或没结束的 goroutine
没用且没 stop 的定时器
time.Ticker 应该被 stop,当不再使用时候
未关闭的资源(io 读写流未 close)
-
案例
func main() { num := 6 for index := 0; index < num; index++ { resp, _ := http.Get("https://www.baidu.com") _, _ = ioutil.ReadAll(resp.Body) } fmt.Printf("此时goroutine个数= %d\n", runtime.NumGoroutine()) }
没有执行 resp.Body.Close(),内存是肯定会泄漏,而且一共泄漏三个 goroutine,一读一写一主(main)
循环引用
如果存在两个对象彼此引用,那么它们将无法被垃圾回收器回收。
排查方法
-
使用代码检查(golangcli-lint)
检查代码规范:如循环引用对象,未关闭资源等不规范代码写法
-
使用 pprof 工具
pprof 可以帮助我们收集应用程序的 CPU、内存、堆栈等性能数据,并进行可视化分析
-
限制 goroutine 数量
在并发程序中,如果并发度过高,可能会导致内存使用过高,因此应该适当限制并发度
可以通过代码控制:sem=make(chan struct{},5), 每次执行 goroutine 就往 sem 写入,满了会阻塞,执行完再读取即可
也可通过调用
runtime.GOMAXPROCS(maxThreads)
函数来设置最大线程数为 5。因此,同时执行的 goroutine 数量也不会超过 5。 -
使用缓冲池
对于频繁创建和销毁的对象,可以使用缓冲池来减少内存分配和释放的次数,从而降低内存使用量。