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进行处理。但问题如上所示 - 我无法获得存储空间来释放内存然后最终耗尽内存。
编辑:有些人指出类似的question,不,它首先不起作用,第二个不是我要求的。切片被清空,内存没有。
答案 0 :(得分:2)
在您编写的程序中,释放内存是没有意义的,因为代码的任何部分都不再请求它。
要创建有效案例,您必须请求新内存并在循环内释放它。然后你会发现内存消耗会在某个时候稳定下来。
答案 1 :(得分:1)
这里有几件事情。
第一个需要被吸收的是Go 垃圾收集语言; GC的实际算法 基本上是不相关的,但它的一个方面是至关重要的: 它不使用引用计数,因此没有办法 以某种方式使GC立即回收任何给定的内存 存储在堆上分配的值。 用更简单的话来概括它,这样做是徒劳的
s := make([]string, 10*100*100)
s = nil
因为第二个陈述确实会删除唯一的参考 切片的底层内存,但不会让GC去 并“标记”该内存可以重复使用。
这意味着两件事:
后者可以通过多种方式完成:
预先分配,当你有一个明智的想法时。
在您的示例中,您从一个长度为0的切片开始, 然后附加很多。现在,几乎所有图书馆代码都处理 随着内存缓冲区的增长 - 包含Go运行时 - 处理这些缓冲区 分配1)分配两次请求的内存 - 希望 防止以后的几个分配,以及2)复制“旧”内容 当它必须重新分配时。这个很重要:重新分配时 碰巧,这意味着现在有两个内存区域:旧的和新的 之一。
如果您可以估计可能需要保留N
个元素
平均值,使用make([]T, 0, N)
预分配给他们 -
更多信息here
和here。
如果你需要保持少于N
个元素,那个缓冲区的尾部
将不会使用,如果您需要保留超过N
,则需要
重新分配,但平均而言,您不需要任何重新分配。
重复使用切片。比方说,在您的情况下,您可以“重置”切片
通过将其重新设置为零长度,然后再次使用它
请求。这称为“池化”,在大规模并行访问的情况下
对于这样的池,您可以使用sync.Pool
来保存缓冲区。
限制系统负载以使GC能够应对 持续的负荷。很好地概述了这两种方法 限制是this。