Go内存泄漏

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
}
}
}
}

Go内存泄漏
https://zhyyao.me/2021/02/03/technology/golang/memory_leak/
作者
zhyyao
发布于
2021年2月3日
许可协议