package main
import (
"fmt"
"time"
)
func main() {
storage := []string{}
for i := 0; i < 50000000; i++ {
storage = append(storage, "string string string string string string string string string string string string")
}
fmt.Println("done allocating, emptying")
storage = storage[:0]
storage = nil
for {
time.Sleep(1 * time.Second)
}
}
上面的代码将分配大约30mb的内存,然后将不会释放它。这是为什么?如何强制去释放此片使用的内存?我将那片切成薄片,然后将其切碎。
我正在调试的程序是一个简单的HTTP输入缓冲区:它将所有请求附加到大块中,然后通过通道将这些块发送到goroutine进行处理。但是问题在上面已说明-我无法获得存储来释放内存,然后最终耗尽内存。
编辑:一些人指出,类似的问题,不,先不工作,第二个是不是我要求的。切片会清空,而内存不会清空。
这里发生了几件事。
首先需要理解的是Go是一种垃圾收集语言。它的GC的实际算法基本上无关紧要,但是要理解它的一个方面至关重要:它不使用引用计数,因此无法以某种方式使GC立即回收任何给定值的内存,该给定值的存储分配在堆。用更简单的话概括一下,这样做是徒劳的
s := make([]string, 10*100*100)
s = nil
因为第二条语句确实将删除对切片的基础内存的唯一引用,但不会使GC继续运行并将该内存“标记”为可重用。
这意味着两件事:
后者可以通过几种方式完成:
当您对要多少钱有一个明智的想法时,请进行预分配。
在您的示例中,您从长度为0的切片开始,然后对其进行大量附加。现在,几乎所有处理增长的内存缓冲区(包括Go运行时)的库代码都通过以下方式处理这些分配:1)分配两倍的请求内存–希望防止将来再分配一些内存;以及2)在以下情况下复制 “旧”内容:它不得不重新分配。这一点很重要:当发生重新分配时,这意味着现在有两个内存区域:旧的和新的。
如果您可以估计N
平均需要保留元素,请使用make([]T, 0, N)
- 在此处和此处的更多信息为它们预先分配。如果您需要容纳少于个N
元素,那么该缓冲区的尾部将不被使用,并且如果您需要容纳大于个元素,则N
需要重新分配,但是平均而言,您不需要任何重新分配。
重新使用您的切片。假设您的情况是,可以通过将切片切片为零长度来“重置”切片,然后将其再次用于下一个请求。这称为“池”,在大规模并行访问此类池的情况下,您可以sync.Pool
用来保存缓冲区。
限制系统上的负载,以使GC能够应付持续的负载。两个很好的概述接近这样的限制是这个。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句