1.闭包导致的内存泄漏,在内部函数中使用外部函数的值,导致该变量由栈空间逃逸到堆空间
2.子字符串导致:
var s0 string// package-level变量 func f(s string){ s0 = s[:50] // s0与s共享相同的底层内存块。 // 虽然s现在不是活动的,但是s0仍然是活动的 // 所以无法收集它们共享的内存块,虽然这个块中只使用了50个字节 // 并且这个块中的所有其他字节都不可用。 } func demo(){ s :=createStringWithLengthOnHeap(1<<20)// 1M bytes f(s) } 为了避免这种类型的内存泄漏,可以将子字符串转换为[]字节值,然后将[]字节值转换回字符串。 func f(s string){ s0 =string([]byte(s[:50])) }
这段代码会导致1M空间的内存泄漏,在获取子字符串时应该避免使用这种直接切的方式进行获取,除了上述方法外,在Go1.12(将于2019年初发布)开始,可以在strings标准包中调用Repeat函数来克隆字符串。
3.切片引起:
和子字符串导致的内存泄漏相似
var s0 []int func g(s1 []int){ // 假设s1的长度远大于30 s0 = s1[len(s1)-30:] } 调用此方法后,可能导致s1的前30个值无法被回收,进而引起内存泄漏 如果我们想避免这种类型的内存泄漏,我们必须为s0复制30个元素,这样存活的s0就不会妨碍存储s1元素的内存块被收集。 func g(s1 []int){ s0 =append([]int(nil), s1[len(s1)-30:]...) // 现在如果没有其他值引用内存块,那么可以收集包含s1元素的内存块。 }
还有一种情况是切片元素是指针
func h()[]*int{ s :=[]*int{new(int),new(int),new(int),new(int)} return s[1:3] // 对s进行手动释放操作... s[0], s[len(s)-1]=nil,nil }
在上述情况中,可能引起指针指向的对象永远无法被释放,因此需要代码去手动释放
4.挂起Goroutines导致的内存泄漏:
有时Go程序中的一些goroutine可能永远处于阻塞状态。这样的goroutines叫做挂起的goroutine。Go运行时不会杀死挂起的goroutines,因此分配给挂起goroutines的资源(以及所引用的内存块)永远不会被垃圾回收。
5.time.Ticker导致的内存泄漏:
当一个time.Timer值不再使用,一段时间后将被垃圾回收。但对于time.Ticker值并非如此。应该直接停止它当其不再使用的时候。