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.
Memory leaks are very common in almost any language, including garbage collected languages. Go is not an exception. A reference to an object, if not properly managed, may be left assigned even if unused. This usually happens on an application logic level, but can also be an issue inside of an imported package.
pprof and flame graphs are pretty useful to analyze application memory leaks. A continuous profiler can really help you look at multiple snapshots of the profile and quickly figure out the cause of leaks. Cloud profiler is definitely a handy tool for GCP workloads.
In computer science, a memory leak is a type of resource leak that occurs when a computer program incorrectly manages memory allocations in a way that memory which is no longer needed is not released. A memory leak may also happen when an object is stored in memory but cannot be accessed by the running code.
Pointers in Go programming language or Golang is a variable that is used to store the memory address of another variable. Pointers in Golang is also termed as the special variables. The variables are used to store some data at a particular memory address in the system.
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).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With