Reputation: 37694
Does go's GC keep whole objects in memory, while interior pointer to a field remains?
Can this cause memory leaks, in a code like this? Or is go's GC "smart enough" to notice that the rest of the object is no longer needed and clear it from memory?
package main
import (
"fmt"
)
const aLot = 500000000
func getInteriorPointer() *int {
type bigStruct struct {
someBigThing [aLot]int
smallThing int
}
b := bigStruct{smallThing: 3}
return &b.smallThing
}
func main() {
p := getInteriorPointer()
// keep using p in the rest of the app,
// never using someBigThing from the struct
fmt.Println(*p)
}
Upvotes: 1
Views: 516
Reputation: 166764
Yes. Memory will be kept until there is no longer any reference to any part of the allocation.
The usual example is given in slice tricks
Delete without preserving order
a[i] = a[len(a)-1] a = a[:len(a)-1]
NOTE If the type of the element is a pointer or a struct with pointer fields, which need to be garbage collected, the above implementations of Cut and Delete have a potential memory leak problem: some elements with values are still referenced by slice a and thus can not be collected.
Upvotes: 3
Reputation: 37694
Looking at profiler, it seems to indeed be a memory leak (go1.11.5 darwin/amd64)
Let's look at profiler.
I found an online example for debugging memory. By adapting to my example, I did this:
package main
import (
"fmt"
"os"
"runtime"
"runtime/debug"
"runtime/pprof"
)
const aLot = 5000000
func getInteriorPointer() *int {
type bigStruct struct {
someBigThing [aLot]int
smallThing int
}
b := bigStruct{smallThing: 3}
return &b.smallThing
}
func main() {
p := getInteriorPointer()
runtime.GC()
debug.FreeOSMemory()
fmem, _ := os.Create("prof.prof")
pprof.WriteHeapProfile(fmem)
// keep using p in the rest of the app,
// never using someBigThing from the struct
fmt.Println(*p)
}
Now I run go tool pprof prof.prof
and list main
ROUTINE ======================== main.getInteriorPointer in /Users/karel/exp/exp.go
38.15MB 38.15MB (flat, cum) 100% of Total
. . 13:func getInteriorPointer() *int {
. . 14: type bigStruct struct {
. . 15: someBigThing [aLot]int
. . 16: smallThing int
. . 17: }
38.15MB 38.15MB 18: b := bigStruct{smallThing: 3}
. . 19: return &b.smallThing
. . 20:}
. . 21:
. . 22:func main() {
. . 23: p := getInteriorPointer()
ROUTINE ======================== main.main in /Users/karel/exp/exp.go
0 38.15MB (flat, cum) 100% of Total
. . 18: b := bigStruct{smallThing: 3}
. . 19: return &b.smallThing
. . 20:}
. . 21:
. . 22:func main() {
. 38.15MB 23: p := getInteriorPointer()
. . 24: runtime.GC()
. . 25: debug.FreeOSMemory()
. . 26: fmem, _ := os.Create("prof.prof")
. . 27: pprof.WriteHeapProfile(fmem)
. . 28:
It seems garbage collector indeed doesn't remove the unnecessary object from memory. If I understand the format correctly. (I might not, since there is almost no documentation.)
Upvotes: 0