一、遇到的问题
某次刷力扣题时,我在算法中使用切片来充当队列,毕竟利用切片的可伸缩性质很容易实现一个队列,
简易的切片实现队列代码如下所示:
//入队
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
}
由切片的结构可知,切片由三个信息组成:
- 指针Data,指向底层数组中切片指定的开始位置
- 长度Len,即切片的长度
- 容量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处理前后内存占用相同。