一、遇到的问题

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

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

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

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

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
}
ba
abaa
a

原因探析

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

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

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

  1. 指针Data,指向底层数组中切片指定的开始位置
  2. 长度Len,即切片的长度
  3. 容量Cap,也就是最大长度,即切片开始位置到数组的最后位置的长度
ba
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

ba
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

ba
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处理前后内存占用相同。