如何释放切片分配的内存?

时间:2018-02-05 08:13:21

标签: go

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,不,它首先不起作用,第二个不是我要求的。切片被清空,内存没有。

2 个答案:

答案 0 :(得分:2)

在您编写的程序中,释放内存是没有意义的,因为代码的任何部分都不再请求它。

要创建有效案例,您必须请求新内存并在循环内释放它。然后你会发现内存消耗会在某个时候稳定下来。

答案 1 :(得分:1)

这里有几件事情。

第一个需要被吸收的是Go 垃圾收集语言; GC的实际算法 基本上是不相关的,但它的一个方面是至关重要的: 它不使用引用计数,因此没有办法 以某种方式使GC立即回收任何给定的内存 存储在堆上分配的值。 用更简单的话来概括它,这样做是徒劳的

s := make([]string, 10*100*100)
s = nil

因为第二个陈述确实会删除唯一的参考 切片的底层内存,但不会让GC去 并“标记”该内存可以重复使用。

这意味着两件事:

  • 您应该知道GC的工作原理。 This解释了它的工作原理 从v1.5到现在(这几天是v1.10)。
  • 你应该构建你的algorythms的那些 以减少记忆压力的方式记忆密集。

后者可以通过多种方式完成:

  • 预先分配,当你有一个明智的想法时。

    在您的示例中,您从一个长度为0的切片开始, 然后附加很多。现在,几乎所有图书馆代码都处理 随着内存缓冲区的增长 - 包含Go运行时 - 处理这些缓冲区 分配1)分配两次请求的内存 - 希望 防止以后的几个分配,以及2)复制“旧”内容 当它必须重新分配时。这个很重要:重新分配时 碰巧,这意味着现在有两个内存区域:旧的和新的 之一。

    如果您可以估计可能需要保留N个元素 平均值,使用make([]T, 0, N)预分配给他们 - 更多信息herehere。 如果你需要保持少于N个元素,那个缓冲区的尾部 将不会使用,如果您需要保留超过N,则需要 重新分配,但平均而言,您不需要任何重新分配。

  • 重复使用切片。比方说,在您的情况下,您可以“重置”切片 通过将其重新设置为零长度,然后再次使用它 请求。这称为“池化”,在大规模并行访问的情况下 对于这样的池,您可以使用sync.Pool来保存缓冲区。

  • 限制系统负载以使GC能够应对 持续的负荷。很好地概述了这两种方法 限制是this