Current state of garbage collection for slice

I’m curious about the current state of garbage collection for slices in Go. Is the Go garbage collector still unable to collect objects that are no longer accessible but are referenced by the underlying array of a slice?

For instance, in the following code, is it possible for the garbage collector to collect a at line 5?

a := 1
s := make([]*int, 2)
s[0] = &a
s = s[1:]
// GC at this point

Are there any tools to detect this? I checked Staticcheck and go vet and it seemed that none of them can detect the issue.

1 Like

No, you’re actually referencing the memory of the underlying array, so GC won’t reclaim this part of the memory.
You can use the functions of the runtime package to trigger and view the memory usage in imitation of active GC.

Thanks. But what I pay attention to is the a object, which is referenced by inaccessible elements s[0] of slice s.

If I use s[0] = nil before assignment s = s[1:], a can be reclaimed by GC at the last line, right? But if a programmer forgets to write s[0] = nil and the GC is unable to recognize the inaccessible a and reclaim it, much memory may not be reclaimed.

im still a go noob but from what i understand using nil to deference isnt standard practice as the GC is smart enough to handle such cases on its own

I think you’re oversimplifying and don’t know if you know about the unsafe package, which can be similar to the offset point of C (this is just an example), and simply reclaiming memory will cause unexpected problems.

s ->    s[0] -> a
        s[1]
// s=s[1:]
        s[0] -> a
s ->    s[1]

When you use s, you’re actually referencing the entire underlying array, and GC doesn’t recycle the memory of the underlying array. At the same time, because the underlying array references the memory that A points to, the memory of A will not gc.
The memory pointed to by a will only be freed if there is no reference to the underlying array in your context. It’s not hard to see why.

As for why GC doesn’t handle this kind of thing for you, I think you’re putting the cart before the horse: the ease of use of Golang doesn’t mean it stops you from writing bad code at will.
Awareness of memory allocation is a basic quality for developers (I admit that golang is easy to get started with, but I’ve encountered a lot of developers who write bad code).

Thanks for your detailed reply. I know the unsafe package and its possible effect to GC(I’m not so sure, so I’m asking here). What about set nil to s[0] (manually or semi-automatically) to make a not referenced by the underlying array of s? I think it’s a good practice if a can be reclaimed immediately instead of being reclaimed at the same time of reclaiming underlying array of s, i.e. shortening the lifespan of a to its actual lifespan.

As the above reply mentions, can GC handle nil smartly? Is that true? Is there any source code or blog that I can reference? Or somewhere else to negate the predicate “GC is smart enough to handle such cases on its own”?

New generic slices package has Delete function. It zero/nil out the obsolete elements, so they can be collected by GC. But at the same time underline array will not change its capacity. Thus, the memory already allocated for it will stay the same.

I don’t think you understand what I’m talking about.
As I said above, it’s essentially because the underlying array of s has a pointer to A, so A can’t be reclaimed by GC memory, which is the simplest and clearest way to put it. So if the underlying array of s does not have a pointer to A, then A will be reclaimed by the GC memory, note that this is not an immediate collection, the GC has its own set of logic.
If you don’t set nil and S is used all the time, then A memory will never be reclaimed. (On this note, you can write a runtime to see the memory footprint of the sample code, it might be more intuitive to turn a into a long string)

	var m runtime.MemStats
	var a string
	a = uuid.NewIdn(1024 * 1024)
	fmt.Println(len(a)) //len 1024 * 1024
	s := make([]*string, 2)
	s[0] = &a

	for i := 0; i < 10; i++ {
		time.Sleep(100 * time.Millisecond)
		runtime.ReadMemStats(&m)
		fmt.Println(m.Alloc)
		runtime.GC()
	}
	s[0] = nil //Please comment on this line repeatedly for comparison
	s = s[1:] //Please comment on this line repeatedly for comparison
	for i := 0; i < 10; i++ {
		time.Sleep(100 * time.Millisecond)
		runtime.ReadMemStats(&m)
		fmt.Println(m.Alloc)
		runtime.GC()
	}
	fmt.Println(s[0]) //Please comment on this line repeatedly for comparison

As for automation, no! I repeat, no!
Golang’s GC doesn’t automatically set up nil for you, and if it does, it will lead to a host of other problems, so the best thing to do is to do nothing and let the developers solve the problem themselves.