Golang切片reslice *** 作导致内存泄露问题以及解决方法

Golang切片reslice *** 作导致内存泄露问题以及解决方法,第1张

一、遇到的问题

某次刷力扣题时,我在算法中使用切片来充当队列,毕竟利用切片的可伸缩性质很容易实现一个队列,

简易的切片实现队列代码如下所示:

//入队
slice = append(slice, 1)
//出队
slice = slice[1:]

除此之外,还可以利用Golang提供的container包中的list来实现队列。

题目并不难,就是二叉树层序遍历,但提交代码后确出现如下错误:

runtime: out of memory: cannot allocate 134217728-byte block (360448000 in use)
fatal error: out of memory

错误结果表明内存溢出,通俗点说就是内存不够用。

然后我又自己的检查了一下代码感觉没问题,于是我在想是不是切片 *** 作不当会产生内存泄露问题。

(T_T),之后又看了看发现是代码写错了,产生了无限循环的情况。

但不影响,还是学习到了不少东西。= ̄ω ̄=

二、reslice内存泄漏问题 举例说明

golang代码的编写中,我们经常使用reslice *** 作,例如:

func reslice(a []int) []int {
	b := a[0:3]
	return b
}

在此函数中,切片b截取了切片a中的部分数据并返回。

但这存在一个问题,假如切片a在之后不再使用,即使切片b只使用了切片a中的前三个数据而已,a的整个底层数组都不会被GC回收。

可以想象,如果切片a中含有大量的数据,那这会极大的浪费内存。

原因探析

首先,需要了解切片的底层结构

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

由切片的结构可知,切片由三个信息组成:

指针Data,指向底层数组中切片指定的开始位置长度Len,即切片的长度容量Cap,也就是最大长度,即切片开始位置到数组的最后位置的长度

在上述例子中,切片b的底层数组与切片a相同,所指向的数组是同一个,因此,gc在进行检测时,不会将底层数组回收。

假设切片a=[1,2,3,4,5],那么b=[1,2,3]

对于底层数组中的[4,5],虽然已经不再需要,但仍然会保存在内存中。

测试:

func reslice(a []int) []int {
	b := a[0:3]
	return b
}
func main() {
	a := []int{1, 2, 3, 4, 5}
	fmt.Printf("%p\n", a) //0xc00000a450
	b := reslice(a)
	fmt.Printf("%p\n", b) //0xc00000a450
}
解决方法

解决方法就是创建一个新的切片,再依次将旧的切片中的值加入到新的切片。

新的reslice函数以及测试如下:

func reslice(a []int) []int {
   b := make([]int, 0)
   b = append(b, a[0:3]...)
   return b
}
func main() {
   a := []int{1, 2, 3, 4, 5}
   fmt.Printf("%p\n", a) //0xc00000a450
   b := reslice(a)
   fmt.Printf("%p\n", b) //0xc00000e120
}
三、最后补充

我注意到了网上的有人进行的一个测试,链接为:https://studygolang.com/topics/14844。

根据原帖内容,很明显,在手动执行GC *** 作后,程序的内存占用明显减少,我个人认为是因为代码中的切片并没有被使用,因此在手动调用gc后,所有的切片都被回收了。当然,原帖底下也有人给出了测试结果。

参照原帖作者的思路,也利用本文例子做一个测试,代码如下:

测试1:

切片b直接使用a的底层数组

func reslice(a []int) []int {
	b := a[0:3]
	return b
}

//打印占用的内存
func printMem() {
	var rtm runtime.MemStats
	runtime.ReadMemStats(&rtm)
	fmt.Printf("%f MB\n", float64(rtm.Alloc)/1024./1024.)
}
func main() {
	a := make([]int, 9999999)
	a = reslice(a)
	printMem() 		//76.516983 MB
	runtime.GC()
	fmt.Println(a)	//此方法会访问a,因此手动GC不会将a回收
	printMem() 		//76.516983 MB
}

测试2:

切片b新建,再添加切片a中的数据。

func reslice(a []int) []int {
   b := make([]int, 0)
   b = append(b, a[0:3]...)
   return b
}
//打印占用的内存
func printMem() {
   var rtm runtime.MemStats
   runtime.ReadMemStats(&rtm)
   fmt.Printf("%f MB\n", float64(rtm.Alloc)/1024./1024.)
}
func main() {
   a := make([]int, 9999999)
   a = reslice(a)
   printMem() //76.516983 MB
   runtime.GC()
   fmt.Println(a)
   printMem() //0.216637 MB
}

结论:
可以看出,测试2在经过gc处理之后,内存明显减少,而测试1在gc处理前后内存占用相同。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://www.outofmemory.cn/langs/995293.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存