代码之家  ›  专栏  ›  技术社区  ›  Aleksandr Makov

如何释放片分配的内存?[副本]

go
  •  4
  • Aleksandr Makov  · 技术社区  · 7 年前
    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的内存,然后不会释放它。为什么会这样?如何强制go释放该片使用的内存?我把那片切成薄片,然后把它变成零。

    我正在调试的程序是一个简单的HTTP输入缓冲区:它将所有请求附加到大数据块中,并通过通道将这些数据块发送给goroutine进行处理。但问题如上所示——我无法获得存储来释放内存,然后最终耗尽内存。

    编辑:正如一些人指出的类似 question 不,第一个不行,第二个不是我想要的。片会被清空,内存不会被清空。

    2 回复  |  直到 7 年前
        1
  •  10
  •   kostix    7 年前

    这里发生了几件事。

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

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

    因为第二句话确实会删除唯一的引用 到片的底层内存,但不会使GC运行 并“标记”该内存可供重用。

    这意味着两件事:

    • 您应该知道GC是如何工作的。 This 解释其工作原理 从v1.5到现在(现在是v1.10)。
    • 你应该构建那些 内存密集型,可减少内存压力。

    后者可以通过几种方式实现:

    • 预先分配,当你对分配多少有一个合理的想法时。

      在您的示例中,从长度为0的切片开始, 然后再加上很多。现在,几乎所有处理 随着内存缓冲区的增长,Go runtime Included可以处理这些问题 按1)分配分配 两次 请求的内存希望 防止将来多次分配,以及2) 副本 “旧”内容 当它不得不重新分配的时候。这一点很重要:当重新分配时 一旦发生,就意味着 现在的内存区域:旧的和新的 一

      如果您可以估计,您可能需要 N 上的元素 平均值,使用 make([]T, 0, N) — 更多信息 here here . 如果你需要持有少于 N 元素,缓冲区的尾部 将不使用,如果您需要持有超过 N ,您需要 重新分配,但平均而言,您不需要任何重新分配。

    • 重新使用您的切片。比如,在你的情况下,你可以“重置”切片 将其重新选择为零长度,然后再次用于下一个 要求这称为“池”,在大规模并行访问的情况下 对于这样一个游泳池,你可以使用 sync.Pool 保留缓冲区。

    • 限制系统负载 使总承包商能够应对 持续荷载。对这两种方法的良好概述 限制是 this .

        2
  •  2
  •   Jonathan Hall    7 年前

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

    要生成有效的案例,您必须请求一个新内存并在循环中释放它。然后您将观察到内存消耗将稳定在某个点上。