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.
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:
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:
This prevent from leaving old references, and thus prevent from memory leaks.
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:
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).