在Golang中,将指针设置为nil可以防止内存泄漏。

huangapple go评论104阅读模式
英文:

Setting pointers to nil to prevent memory leak in Golang

问题

我正在学习Go语言,作为练习,我想要实现一个链表。为了参考,我查看了官方的Go代码(https://golang.org/src/container/list/list.go)。其中有一段代码让我印象深刻:

   108	// remove removes e from its list, decrements l.len, and returns e.
   109	func (l *List) remove(e *Element) *Element {
   110		e.prev.next = e.next
   111		e.next.prev = e.prev
   112		e.next = nil // 避免内存泄漏
   113		e.prev = nil // 避免内存泄漏
   114		e.list = nil
   115		l.len--
   116		return e
   117	}

我很好奇在这种情况下将指针设置为nil如何防止内存泄漏?如果可能的话,我想构建一个具有这个缺陷的程序,并在使用pprof进行分析时观察它(我将使用一个修改过的list.go版本,不包含这个将指针设置为nil的部分)。


为了更清楚地回答:如果其中一个节点有外部指针指向它,那么所有相邻的被移除的节点将通过该指针保持活动引用,不会被移除。
在Golang中,将指针设置为nil可以防止内存泄漏。

  1. 我们创建一个指向Node2的外部指针
  2. 我们从链表中移除节点2-4
  3. 在这一点上,你期望只有Node 1、2和5仍然存在,其余的节点应该被垃圾回收。然而,由于Node2仍然指向Node3等,整个链表仍然没有被回收。
英文:

I'm learning Go, and as an exercise I wanted to implement a linked list. For reference I looked at the official Go code (https://golang.org/src/container/list/list.go) . One thing that stuck with me are these lines:

   108	// remove removes e from its list, decrements l.len, and returns e.
   109	func (l *List) remove(e *Element) *Element {
   110		e.prev.next = e.next
   111		e.next.prev = e.prev
   112		e.next = nil // avoid memory leaks
   113		e.prev = nil // avoid memory leaks
   114		e.list = nil
   115		l.len--
   116		return e
   117	} 

I am curious as to how does setting pointers to nil in this case prevent memory leaks? If possible I would like to construct a program which has this flaw and see it while profiling with pprof (I would use a modified verion of the list.go without this nil pointer setting).


For clarity of answer: If one of the nodes has an external pointer to it, then all of the adjacent removed nodes will have an active reference through that pointer and won't be removed.
在Golang中,将指针设置为nil可以防止内存泄漏。

  1. We create an external pointer pointing to Node2
  2. We remove nodes 2-4 from the list
  3. You would expect at this point only for the Node 1,2 &
    5 to be alive and the rest to be GC-ed. However, due to Node2 still
    pointing to Node3 & etc., the entire chain remains uncollected.

答案1

得分: 13

你的假设是正确的。如果有一组指针彼此相互指向,但没有任何指向该组成员的引用/指针,垃圾回收器将检测到该组为不可达,并将正确释放该组。

但是,关于内存泄漏的解释很简单。我们可以从列表中获取包含未导出的Element.nextElement.prev指针的list.Element包装器,这些指针指向列表中的下一个和上一个元素。

当从列表中移除一个元素时,如果这些指针不被设置为nil,它们将保留对下一个和上一个元素包装器的引用,包括与这些元素相关联的值。

看下面的例子:

var e2 *list.Element

func main() {
	listTest()
	fmt.Println(e2.Value)
	// 在这一点上,我们期望列表中的所有内容随时都可以被垃圾回收,我们只有对e2的引用。
	// 如果e2.prev和e2.next不被设置为nil,
	// e1和e3将无法被释放!
}

func listTest() {
	l := list.New()
	e1 := l.PushBack(1)
	e2 = l.PushBack(2)
	e3 := l.PushBack(3)
	// 现在列表为[1, 2, 3]
	fmt.Println(e1.Value, e2.Value, e3.Value)
	l.Remove(e2)
	// 现在列表为[1, 3],不包含e2
}

listTest()函数中,我们构建了一个包含3个元素的列表,并将第二个元素存储在全局变量e2中。然后我们移除了这个元素。现在我们期望除了e2(以及其中包装的值)之外的所有内容在listTest()返回时被垃圾回收,因为列表在listTest()函数外部是不可访问的。是的,我们在e2中有一个指向元素的指针,但是e2(应该)与列表不再有任何关联,因为我们已经将其移除。

如果e2中的prevnext指针不被设置为nil,那么它们指向的元素中包装的值将永远无法被释放,递归地。但是由于List.Remove()正确地将它们设置为nil,在上面的例子中,e1e3以及它们包装的值将在下一次垃圾回收运行时被释放。

英文:

Your assumptions are correct. If there is a group of pointers pointing to each other, but there is no reference / pointer to any member of this group, the group will be detected as unreachable by the garbage collector and will be freed properly.

But the explanation to memory leak is simple. We can get the list.Element wrappers from the list which contain the unexported Element.next and Element.prev pointers to the next and previous elements in the list.

When removing an element from the list if these pointers would not be set to nil, they would held references to the next and previous element wrappers, including the values associated with those elements.

See this example:

var e2 *list.Element

func main() {
	listTest()
	fmt.Println(e2.Value)
	// At this point we expect everything from the list to be
	// garbage collected at any time, we only have reference to e2.
	// If e2.prev and e2.next would not be set to nil,
	// e1 and e3 could not be freed!
}

func listTest() {
	l := list.New()
	e1 := l.PushBack(1)
	e2 = l.PushBack(2)
	e3 := l.PushBack(3)
	// List is now [1, 2, 3]
	fmt.Println(e1.Value, e2.Value, e3.Value)
	l.Remove(e2)
	// Now list is [1, 3], it does not contain e2
}

In listTest() we build a list with 3 elements, and we store the 2nd element in a global variable e2. Then we remove this element. Now we would expect that except e2 (and the value wrapped in it) everything else gets garbage collected when listTest() returns, because the list is not accessible outside the listTest() function. Yes, we have a pointer in e2 to an element, but e2 has (should have) nothing to do with the list anymore as we removed it.

If the prev and next pointers in e2 would not be set to nil, values wrapped in elements pointed by them could never be freed, recursively. But since List.Remove() properly sets those to nil, in the above example e1 and e3 –along with the values wrapped in them– will be freed (on next garbage collection run).

答案2

得分: 0

Golang的垃圾回收器基于三色标记-清除算法。
简而言之,你的程序使用的每个内存都与一个颜色相关联。颜色决定了内存是否应该被回收。

如果某个内存没有被直接或间接引用,该算法将标记该内存以便释放。但是,如果我们看一下代码:

e.prev.next = e.next
e.next.prev = e.prev

这将把e.next中的指针复制到e.prev.next中。现在,假设你想通过一个新创建的完整元素来更新e.prev.next。

之前移除的元素不会被回收,因为它仍然被e.next引用。

这就是为什么存在这些代码行的原因:

e.next = nil // 避免内存泄漏
e.prev = nil // 避免内存泄漏

这样可以防止旧引用的存在,从而防止内存泄漏。

英文:

Golang garbage collector is based on the tri-color mark-and-sweep algorithm.
In short, every memory you're program use is associated to a color. The color determine if the memory shall be garbaged or not.

This algorithm will flag a memory to be freed if this memory is not referenced somewhere (directly and indirectly). But if we take a look at the code:

e.prev.next = e.next
e.next.prev = e.prev

This copy the pointer in e.next into e.prev.next. Now, let's say you want to update e.prev.next by a new fully created element.

The previously removed element won't be garbaged because it is still referenced by e.next.

This is why those lines exist:

e.next = nil // avoid memory leaks
e.prev = nil // avoid memory leaks

This prevent from leaving old references, and thus prevent from memory leaks.

huangapple
  • 本文由 发表于 2016年8月20日 18:16:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/39052967.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定