本文转载自微信公众号《我的脑子是炸鱼》,作者陈建宇。转载本文请联系脑筋急转弯公众号。大家好,我是炸鱼。前段时间,在我的Go读者群里,有小伙伴正在纠结切片的问题。写了这篇?,引起了很多讨论,还是挺欣慰的。这不,有个小伙伴给我提出了一个新的话题:微信上一个读者的问题,问的是Go中容易踩到的slice内存泄露问题。作为一个宠粉,炸鱼肯定不会放过,力求让大家避开这个“坑”。在今天的文章中,剑鱼就带大家了解一下这个问题:Goslices是如何被泄露的?切片的泄露可能是在业务代码的编写上。我们往往会接受外部接口数据,并将其插入到Go中对应的数据结构中,然后进行下一步的业务聚合、裁剪、打包、处理。就像在PHP语言中一样,它通常被放在一个数组中。在Go语言中,它会被放在一个切片中。因此,在Go的切片处理逻辑中,往往会涉及到类似如下的动作。示例代码如下:vara[]intfuncf(b[]int)[]int{a=b[:2]returna}funcmain(){...}仔细想想这个程序有没有问题,是否存在内存泄漏风险?答案是:是的。肯定存在切片内存泄漏的可能性和风险。有些朋友可能对切片的底层结构感到困惑。为什么有问题?哪里有问题?这里不得不回顾下切片底层的基本数据结构。切片在运行时的表现是SliceHeader结构体,其定义如下:typeSliceHeaderstruct{DatauintptrLenintCapint}Data:指向具体的底层数组。Len:表示切片的长度。Cap:表示切片的容量。重点是:slice真正存放数据的地方是一个数组。切片的Data属性存储指向引用数组的指针的地址。其背后的原因在上面的例子中,我们有一个包全局变量a,它有两个切片a和b,截取b的一部分赋值给a,两者是相关的。从程序直接看,截取了b的一部分赋值给a。结构好像如下图:但是如果我们进一步打开程序的底层,应该如下图所示:切片a和b共享同一个底层Array(共享内存块),sliceB包含所有引用的字符。sliceA只包含了[:2],也就是0和1这两个索引字符。那么它们到底是从哪里泄露的呢?泄漏点泄漏点是虽然分片b在函数中结束了它的使命,但是已经不再使用了。但是slicea还在使用中,slicea和sliceb指的是同一个底层数组(共享内存块)。关键点:切片a指的是底层数组中的一段。虽然只使用了切片a底层数组中的两个索引位0和1,但其余未使用的底层数组空间是无用的。但由于它们被引用,它们也不会被GC,从而导致泄漏。解决方案解决方案是利用切片的特性。当切片的容量空间不足时,会重新申请一个新的底层数组进行存储,从而使两者完全分离。示例代码如下:vara[]intvarc[]int//第三方funcf(b[]int)[]int{a=b[:2]//newsliceappendleadssliceexpansionc=append(c,b[:2]...)fmt.Printf("a:%p\nc:%p\nb:%p\n",&a[0],&c[0],&b[0])返回}输出结果:a:0xc000102060c:0xc000124010b:0xc000102060这段程序新增了一个变量c,其容量为0,这时将需要的数据添加到过去。自然会遇到容量空间不足的情况,他就能申请新的底层数据。然后我们把原来的slice置为nil,就可以顺利的达到打散两者的目的了。小结在今天的文章中,我们介绍了Goslices内存泄漏的一种常见方式。虽然我们在日常使用中可能不会去关注它。主要原因是切片的使用场景大多比较小。或者在不知不觉中膨胀,成为暂时的漏气。这还是有风险的,在写Go代码的时候需要谨慎。毕竟这是Go语言官方踩过的“坑”。参考AninterestingwaytoleakmemorywithGoslicesinternal/poll:avoidmemoryleakinWritevslicetypememoryleaklogicgolangslicememoryleakrecovery
