Go内存泄漏场景及解决方案…
1.字符串截取
1 2 3 4
| func main() { var str0 = "12345678901234567890" str1 := str0[:10] }
|
以上代码,会有10字节的内存泄漏,我们知道,str0和str1底层共享内存,只要str1一直活跃,str0
就不会被回收,10字节的内存被使用,剩下的10字节内存就造成了临时性的内存泄漏,直到str1不再活跃
如果str0足够大,str1截取足够小,或者在高并发场景中频繁使用,那么可想而知,会造成临时性内存泄漏,对性能产生极大影响。
解决方法:
将需要截取的部分先转换成[]byte,再转换成string,但是这种方式会产生两个10字节的临时变量,string转换[]
byte时产生一个10字节临时变量,[]byte转换string时产生一个10字节的临时变量
1 2 3 4
| func main() { var str0 = "12345678901234567890" str1 := string([]byte(str0[:10])) }
|
2.切片截取(和第一个类似)
1 2 3 4
| func main() { var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := s0[:5] }
|
解决方法:append
append函数是向 slice里面追加一个或多个元素,然后返回一个和slice一样类型的slice(复制一份)
1 2 3 4 5
| func main() { var s0 = []int{1, 2, 3, 4, 5, 6, 7, 8, 9} s1 := append(s0[0:0], s0[:5]...) fmt.Println(s1) }
|
3.没有重置丢失的子切片元素中的指针
原切片元素为指针类型,原切片被截取后,丢失的子切片元素中的指针元素未被置空,导致内存泄漏
1 2 3 4
| func main() { var s0 = []*int{new(int), new(int), new(int), new(int), new(int)} s1 := s0[1:3] }
|
解决方案:元素置空
1 2 3 4 5
| func main() { var s0 = []*int{new(int), new(int), new(int), new(int), new(int)} s0[0], s0[3], s0[4] = nil, nil, nil s1 := s0[1:3] }
|
4.数组传参
Go数组是值类型,赋值和函数传参都会复制整个数组
如果我们在函数传参的时候用到了数组传参,且这个数组够大(我们假设数组大小为100万,64位机上消耗的内存约为800w字节,即8MB内存),或者该函数短时间内被调用N次,那么可想而知,会消耗大量内存,对性能产生极大的影响,如果短时间内分配大量内存,而又来不及GC,那么就会产生临时性的内存泄漏,对于高并发场景相当可怕。
1 2 3 4 5 6 7 8 9 10 11 12
| func main() { var arrayA = [3]int{1, 2, 3} var arrayB = [3]int{} arrayB = arrayA fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA) fmt.Printf("arrayB address: %p, arrayB value: %+v\n", &arrayB, arrayB) array(arrayA) }
func array(array [3]int) { fmt.Printf("array address: %p, array value: %+v\n", &array, array) }
|
解决方案:指针传参或者利用切片
1 2 3 4 5 6 7 8 9 10 11
| func main() { var arrayA = [3]int{1, 2, 3} var arrayB = &arrayA fmt.Printf("arrayA address: %p, arrayA value: %+v\n", &arrayA, arrayA) fmt.Printf("arrayB address: %p, arrayB value: %+v\n", arrayB, *arrayB) arrayP(&arrayA) }
func arrayP(array *[3]int) { fmt.Printf("array address: %p, array value: %+v\n", array, *array) }
|
5.goroutine
“Go里面10次内存泄漏有9次都是goroutine泄漏引起的”
有些编码不当的情况下,goroutine被长期挂住,导致该协程中的内存也无法被释放,就会造成永久性的内存泄漏。例如协程结束时协程中的channel没有关闭,导致一直阻塞;例如协程中有死循环;等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| func main() { ticker := time.NewTicker(time.Second * 1) for { <-ticker.C ch := make(chan int) go func() { for i := 0; i < 100; i++ { ch <- i } }()
for v := range ch { if v == 50 { break } } } }
|
解决方案:(涉及到goroutine之间的通信)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func main() { ticker := time.NewTicker(time.Second * 1) for { <-ticker.C cxt, cancel := context.WithCancel(context.Background()) ch := make(chan int) go func(cxt context.Context) { for i := 0; i < 100; i++ { select { case <-cxt.Done(): return case ch <- i: } } }(cxt)
for v := range ch { if v == 50 { cancel() break } } } }
|