【问题标题】:How to release memory allocated by a slice? [duplicate]如何释放切片分配的内存? [复制]
【发布时间】:2018-07-15 01:13:53
【问题描述】:
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,不,它首先不起作用,其次不是我想要的。切片被清空,内存没有。

【问题讨论】:

  • 不,这不是我要找的。我可以清空切片,但我需要它来释放内存。
  • @Filmzy,谢谢。我已经更新了我的问题。
  • “释放内存”是什么意思?如果您的意思是将内存交还给通常未完成的操作系统。如果您的意思是让内存可用于程序中的其他分配,那么就完成了。
  • 我运行你的程序大约 5..10 分钟,内存被释放回操作系统。当我在最后一个for 循环之前插入debug.FreeOSMemory() 调用时,内存再次释放回操作系统而无需等待。所以这是Cannot free memory once occupied by bytes.Buffer的副本。

标签: go


【解决方案1】:

这里发生了几件事。

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

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

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

这意味着两件事:

  • 您应该知道 GC 的工作原理。 This 解释了它是如何工作的 从 v1.5 到现在(现在是 v1.10)。
  • 你应该构建你的算法 以降低内存压力的方式占用大量内存。

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

  • 预分配,当您对分配多少有一个明智的想法时。

    在您的示例中,您从长度为 0 的切片开始, 然后附加很多。现在,几乎所有处理的库代码 随着内存缓冲区的增长——包括 Go 运行时——处理这些 分配方式 1) 分配两次请求的内存——希望 防止未来多次分配,以及 2) 复制“旧”内容 结束,当它不得不重新分配时。这一点很重要:当重新分配 发生,这意味着现在有 两个 内存区域:旧的和新的 一个。

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

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

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

【讨论】:

  • 这是令人难以置信的信息,谢谢。
  • @AleksandrMakov,预分配示例中出现错误——应该是make([]T, 0, N),抱歉。添加了更多关于切片如何工作的文档的指针。
【解决方案2】:

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

要建立一个有效的案例,你必须请求一个新的内存并在循环内释放它。然后你会观察到内存消耗会在某个时候稳定下来。

【讨论】:

  • 嗯,是的,这当然不是实际的程序。这是实际代码的一部分:func(ctx *fasthttp.RequestCtx) { uris = append(uris, string(ctx.RequestURI()[:])) } 然后在 goroutine go func() { for { time.Sleep(10 * time.Second) if len(uris) &gt; 0 { metrics &lt;- uris uris = uris[:0] } } }() 在基准测试中,我总是很快耗尽内存。
  • 我更新了我的问题,使其更加基于现实。
  • @AleksandrMakov 现在答案毫无意义。请添加另一个问题。
  • 好的,谢谢。关闭这个。
猜你喜欢
  • 1970-01-01
  • 2019-08-09
  • 2017-02-02
  • 2020-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-09
相关资源
最近更新 更多