golang 切片中的内存泄漏

Posted

技术标签:

【中文标题】golang 切片中的内存泄漏【英文标题】:Memory leak in golang slice 【发布时间】:2019-07-29 10:25:58 【问题描述】:

我刚开始学习围棋,在学习切片技巧时,有几点非常令人困惑。谁能帮我澄清一下。

在给定的切片中切割元素

方法一:

a = append(a[:i], a[j:]...)

但有一个注意事项,如果使用指针可能会导致内存泄漏,推荐的方法是

方法二:

copy(a[i:], a[j:])
for k, n := len(a)-j+i, len(a); k < n; k++ 
    a[k] = nil // or the zero value of T

a = a[:len(a)-j+i]

谁能帮助我了解内存泄漏是如何发生的。 我知道子切片将由主数组支持。我的想法与指针无关,我们必须始终遵循方法 2。

@icza 和 @Volker 回答后更新..

假设你有一个结构

type Books struct 
    title   string
    author  string


var Book1 Books
var Book2 Books 

    /* book 1 specification */
    Book1.title = "Go Programming"
    Book1.author = "Mahesh Kumar"

    Book2.title = "Go Programming"
    Book2.author = "Mahesh Kumar"

    var bkSlice = []BooksBook1, Book2
    var bkprtSlice = []*Books&Book1, &Book2

现在做

bkSlice = bkSlice[:1]

bkSlice 仍然将 Book2 保存在支持数组中,该支持数组仍在内存中,并且不需要。 我们需要这样做吗

bkSlice[1] = Books

这样它就会被 GCed。我知道指针必须是 nil-ed,因为切片将包含对支持数组之外的对象的不必要引用。

【问题讨论】:

这不是传统意义上的内存泄漏:一旦后备数组因为没有切片引用而被垃圾收集,所有内存都被占用,并且不会发生泄漏。只是只有在所有切片都消失后才声称内存。使用推荐的代码,可以更早地声明指向的内存。 见:Does go garbage collect parts of slices? @icza 我看到了这个答案,即使是说如果我们使用指针它可能会泄漏内存..我的理解是即使我们使用直接对象也会导致内存问题。为什么它会专门针对指针。 @Volker 我同意你的观点,但为什么在指针的情况下特别提到它。它应该是天气它的指针的情况吗? 再次:永远不会有任何内存泄漏。这是关于允许更早地声明内存。这个早期的声明可以是切片包含指针(或切片或映射或通道),可以在切片之前在后备数组中被 nil-ed。如果您有一个整数切片,则无法对整数进行 GC,因为它们是后备数组的一部分。 【参考方案1】:

最简单的可以通过一个简单的切片表达式来演示。

让我们从*int 指针的切片开始:

s := []*intnew(int), new(int)

这个 slice 有一个长度为 2 的后备数组,它包含 2 个非nil 指针,指向分配的整数(在后备数组之外)。

现在如果我们重新切片这个切片:

s = s[:1]

长度将变为1。后备数组(包含 2 个指针)没有被触及,它仍然包含 2 个有效指针。即使我们现在不使用第二个指针,因为它在内存中(它是支持数组),指向的对象(这是一个用于存储int 值的内存空间)不能被垃圾收集器释放。

如果从中间“剪切”多个元素,也会发生同样的事情。如果原始切片(及其支持数组)填充了非nil 指针,并且如果您不将它们归零(使用nil),它们将保存在内存中。

为什么这不是非指针的问题?

实际上,这是所有指针和“标题”类型(如切片和字符串)的问题,而不仅仅是指针。

如果你有一个 []int 类型的切片而不是 []*int,那么切片它只会“隐藏”int 类型的元素,无论是否有一个切片是否包含它。 元素不是对存储在数组外的对象的引用,而指针是指在数组外的对象。

如果切片包含指针并且你在切片操作之前nil它们,如果没有其他对指向对象的引用(如果数组是唯一持有指针的对象),它们可以被释放,它们不会由于仍然有切片(因此还有后备数组)而被保留。

更新:

当你有一片结构时:

var bkSlice = []BooksBook1, Book2

如果你像这样切片:

bkSlice = bkSlice[:1]

Book2 将无法通过bkSlice 访问,但仍会在内存中(作为后备数组的一部分)。

你不能nil 它,因为nil 不是结构的有效值。但是,您可以像这样将其 zero value 分配给它:

bkSlice[1] = Book
bkSlice = bkSlice[:1]

请注意,Books struct 值仍将在内存中,作为后备数组的第二个元素,但该结构将是零值,因此不会保存字符串引用,因此是原书作者和标题字符串可以被垃圾收集(如果没有其他人引用它们;更准确地说是从字符串头引用的字节片)。

一般规则是“递归”:您只需要将引用位于后备数组之外的内存的元素归零。因此,如果您有一个结构切片,只有例如int 字段,您不需要将其归零,实际上这只是不必要的额外工作。如果结构具有指针或切片的字段,或者例如其他具有指针或切片等的结构类型,则应将其归零以删除对后备数组外部内存的引用。

【讨论】:

谢谢您的回答,我已经更新了问题,请您检查一次。

以上是关于golang 切片中的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

golang读取XML内存泄漏?

Golang内存泄漏问题和处理方法

怎么排查这些内存泄漏

手撸golang GO与微服务 ChatServer之4 内存泄漏

排查内存泄漏最简单和直观的方法

前端vue项目内存泄漏排查总结