将指针设置为零以防止Golang中的内存泄漏 [英] Setting pointers to nil to prevent memory leak in Golang
问题描述
我在学Go,作为一个练习,我想实现一个链表。作为参考,我看了官方的Go代码(
- 我们创建一个指向Node2的外部指针
- 我们从列表中删除节点2-4
- 在这一点上,您只能期望节点1,2&
5是活着的,其余的则是GC-ed。但是,由于Node2仍然
指向Node3&等等,整个链仍然未收集。 你的假设是正确的。如果有一组指针指向对方,但没有引用/指向此组的任何成员,则该组将被垃圾收集器检测为无法访问,并且将被正确释放。
但是对内存泄漏的解释很简单。我们可以获取
list.Element
来自列表的包含未导出的Element.next
和Element.prev
指向下一个和上一个元素的指针在列表中。
如果这些指针不会被设置为
nil
,当从列表中删除一个元素时,它们将持有对下一个和前一个元素包装的引用,包括与这些元素相关的值。
请参阅此示例:
var e2 * list.Element
func main(){
listTest()
fmt.Println(e2.Value)
//此时我们希望列表中的所有内容都是
// //随时收集的垃圾,我们只能参考e2。
//如果e2.prev和e2.next不会设置为零,
// 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个元素构建一个列表,并且将第2个元素存储在全局变量e2
。然后我们删除这个元素。现在我们可以预料,除了e2
(以及其中包含的值)之外,其他所有内容都会在listTest()
返回,因为列表不能在listTest()
函数之外访问。是的,我们有一个指向元素的e2
,但是e2
has(应该有
如果
prev
和<$ c $e2
中的指针不会被设置为nil
,由他们指出永远不会被释放,递归。但自List.Remove()
在上面的例子中e1
和e3
- 随着包含在它们中的值 - 将被释放(在下一次垃圾收集运行时)。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.
- We create an external pointer pointing to Node2
- We remove nodes 2-4 from the list
- 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.
解决方案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 unexportedElement.next
andElement.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 variablee2
. Then we remove this element. Now we would expect that excepte2
(and the value wrapped in it) everything else gets garbage collected whenlistTest()
returns, because the list is not accessible outside thelistTest()
function. Yes, we have a pointer ine2
to an element, bute2
has (should have) nothing to do with the list anymore as we removed it.If the
prev
andnext
pointers ine2
would not be set tonil
, values wrapped in elements pointed by them could never be freed, recursively. But sinceList.Remove()
properly sets those tonil
, in the above examplee1
ande3
–along with the values wrapped in them– will be freed (on next garbage collection run).这篇关于将指针设置为零以防止Golang中的内存泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!