This was often a question asked in Java interviews as well.
In Java heap fragmentation is usually considered a separate issue but I understand go has a non-moving garbage collector so you can lose memory due to pathological allocations that overly fragment memory and require constantly allocating new pages. I could be wrong about this since I don't know a lot about go, but heap fragmentation can cause troubles for long running programs with certain types of memory allocation.
Beside that, applications can leak memory by stuffing things into a collection (map or list) and then not cleaning it up despite becoming "stale". The references are live from the perspective of the garbage collector but are dead from the application perspective. Weak references exist to solve this problem when you expose an API that stores something but won't be able to know when something goes out of scope. I wouldn't consider this to be common, but if you are building a framework or any kind of platform code you might need to reach for this at some point. Some crazy folks also intern every string they encounter "for performance reasons" and that can obviously lead to what less crazy folk would consider a memory leak. Other folk stick a cache around every client and might not tune the cache parameters leading to unnecessary memory pressure...
Golang has a feature that I love in general but that makes it very easy to keep unintended allocations around. If you have a struct with a simple int field, and you store that somewhere as an *int, the entire struct and anything it points to will be kept alive. This is super useful for short-lived pointers, and super dangerous for long-lived pointers.
Most other widely used GCed languages don’t allow the use of arbitrary interior pointers (though most GCs can actually handle them at the register level).
> If you have a struct with a simple int field, and you store that somewhere as an *int, the entire struct and anything it points to will be kept alive.
While Go allows interior pointers, I don't think what you say is true. runtime.KeepAlive was added exactly to prevent GC from collecting the struct when only a field pointer is stored. Take a look at this blog post, for example: https://victoriametrics.com/blog/go-runtime-finalizer-keepal...
I don’t believe that’s the case based on the example in the blog post. The fd field in that struct was passed into the later function by value (i.e. as an int, not an *int), so there was no interior pointer in play at all.
A GC only deallocates unreferenced memory, if you keep unused references, that's a leak the GC won't catch, as it has no way to know that you won't need it later.
It can happen when your variables have too long a lifespan, or when you have a cache where the entries are not properly evicted.
But how would Valgrind know more than the GC? Of course a program in a GCed language can leak memory, but it’s not clear to me how Valgrind would detect the kinds of memory leaks that you can create in pure Go code (without calling into C or using unsafe functions to allocate memory directly).
Valgrind can tell you what is still in use when the program exits, along with useful information, like where it comes from. You can then assess to situation and see if it is normal or not.
In addition, Valgrind is actually a complete toolsuite, not just a memory leak detector. Among these tools is "massif", a memory profiler, so you will have a graph of memory use over time and it can tell you from where these allocations come from.
Not, if your language is fully GCed you can have a debug GC that does the job more efficiently than Valgrind on this task, but I don't know if it is the case for Go.
A common one I see fairly often is opening a big file, creating a “new slice” on a subset of the file and then using the “new slice” and expecting the old large object to be dropped.
Except, the “new slice” is just a reference into the larger slice, so its never marked unused.
No, python slice syntax on lists returns a new list. Of course, the slice syntax on your own classes can do just about anything using operator overloading.
A go slice is a wrapper around a normal array. When you take sub-slices those also point to the original array. There's a possible optimization to avoid this footgun where they could reallocate a smaller array if only subslices are reachable (similar to how they reallocate if a slice grows beyond the size of the underlying array).
Most sublicing is just the 2-arg kind so it would not be safe to truncate the allocation even if the subslice is the only living slice because the capacity still allows indirect access to the trailing elements of the original slice. This optimization would only be truly safe for strings (which have no capacity) or the much less common 3-arg slicing (and Go would need to have a compacting GC).
Of course, the language could also be changed to make 2-arg slice operations trim the capacity by default, which might not be a bad idea anyway.
Yeah I understand why they can't do it for backwards compat reasons. Considering they had the forethought to randomize map iteration order it's a big foot gun they've baked into the language.
- you can create deadlocks
- spawn goroutines while not making sure they have proper exit criteria
- use slices of large objects in memory and pass them around (e.g. read files in a loop and pass only slice from whole buffer)
- and so on
not to mention cachegrind, callgrind and other things it bundles.
sorry, i guess when i say leaks i mean a bit more broad stuff :'). my own words are a bit leaky hah
still doesnt mean i am wrong. GC doesnt clean up memory when its released but when it wants to, effectively offering opportunities to get that data after a program dont need it anymore. until some point in time u can usually not specify, just hint at.
that in light of things like bad memory ordering between threads etc..can have nasty bugs... (raii has similar bugs but since its more determenistic you can program your way around lot of it more easily and reliably)