与 C/C++ 不同,Go 有 GC,所以我们不需要手动处理内存的分配和释放。不过,我们仍然应该谨慎对待内存泄漏问题。
来看一个由 slice
引起的内存泄漏案例。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package main import ( "fmt" ) type Object struct {} func main() { var a []*Object for i := 0; i < 8; i++ { a = append(a, new(Object)) } fmt.Println(cap(a), len(a)) a = remove(a, 5) fmt.Println(cap(a), len(a)) } func remove(s []*Object, i int) []*Object { return append(s[:i], s[i+1:]...) }
|
我们可以看到,即使有一个对象被删除,a 的容量仍然是8,这意味着remove
函数可能导致潜在的内存泄漏。
为什么会发生这种情况?
来看一个例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main import ( "fmt" ) func main() { a := []int{1,2} b := a[0:1] fmt.Println(a, b) b = append(b, 3) fmt.Println(a, b) b = append(b, 4) b[0] = 0 fmt.Println(a, b) fmt.Println(cap(a), cap(b)) }
|
如何避免内存泄漏?
在这种情况下,有两种内存泄漏。
1. 底层数组
底层数组的容量只会增加,但不会减少,第一个例子已经证明了这一点。
如果我们认为容量太大,我们可以创建一个新的 slice,并将原 slice 中的所有元素复制到新 slice 中。这是一个复制操作(时间)和内存使用(空间)之间的权衡。
1 2 3 4 5 6
| func remove(s []*Object, i int) []*Object { s = append(s[:i], s[i+1:]...) a := make([]*Object, len(s)) copy(a, s) return a }
|
2. 指向数组元素的内存,其类型为指针
解决方法:将未使用的元素设置为nil,它将会被 GC 释放。
1 2 3 4 5 6
| func remove(s []*Object, i int) []*Object { old := s s = append(s[:i], s[i+1:]...) old[len(old)-1] = nil return s }
|