The point of garbage collected languages is that you don't have to worry about memory management, you don't have to worry about memory allocations and deallocations.
The general principle is: as long as you have a "reference" to some object, it will not be garbage collected; and objects that no one has a reference of, will be garbage collected.
This principle is of course "recursive". If you have an A
object holding a pointer to a B
object, and there's only a single reference to A
(and no one else has a reference to B
), once that reference "goes away", both A
and B
will be garbage collected.
So if you want some complex object hierarchy to be freed, just make sure you don't keep pointers to it, and it will be freed automatically. You do not have to traverse it recursively to zero pointers.
There is one important thing to keep in mind though: If you have a pointer to some "part" of an object, that will keep the whole object in memory. For example if you create a struct, and take the address of one of its fields and store it somewhere, that pointer will keep the whole struct in memory. Similarly, if you create a slice and you take the address of one of its elements (and store it), that will keep the whole slice (or more precisely its backing array) in memory. This also applies if you reslice the slice to "hide" elements, e.g.:
s := make([]*int, 10)
s[8] = new(int)
s = s[:2]
Here we created a slice of 10 int
pointers, we assigned a pointer to the index 8
, then resliced the slice to only hold the first 2 elements. There is a backing array in the background with storage for 10 pointers, which is not cleared by the above slicing operation, so the pointed int
(at index 8
in the original slice) will remain in memory (as long as you have a reference to the slice or its backing array). For details, see Does go garbage collect parts of slices?
See related questions:
Freeing unused memory?
Cannot free memory once occupied by bytes.Buffer