Если вы скомпилируете с включенным анализом escape, вы увидите, что bs не экранирует и поэтому размещается в стеке, а не в куче
go run -gcflags '-m -l' gc.go
# command-line-arguments
./gc.go:13:12: new(B) escapes to heap
./gc.go:20:18: make([]int, 1000000) escapes to heap
./gc.go:17:15: main make([]*B, 10) does not escape
, так что, хотя вы ничего не сделали bs
срез, на который указывал bs, все еще считается gc активным благодаря наличию в стеке.Если вы поместите свой код в свой собственный функционал, а затем в GC после его возвращения, вы увидите, что GC действительно освобождает всю память.
func main() {
alloc()
runtime.GC()
time.Sleep(time.Second * 2)
}
func alloc() {
var bs = make([]*B, 10)
for i := 0; i < 10; i++ {
bs[i] = NewB()
bs[i].bb = make([]int, 1000000)
}
time.Sleep(time.Second)
println("begin gc")
bs = nil
runtime.GC()
}
begin gc
gc 5 @1.003s 0%: 0.003+0.052+0.021 ms clock, 0.026+0/0.036/0.055+0.17 ms cpu, 76->76->76 MB, 137 MB goal, 8 P (forced)
gc 6 @1.003s 0%: 0.001+0.037+0.018 ms clock, 0.010+0/0.036/0.023+0.15 ms cpu, 76->76->0 MB, 152 MB goal, 8 P (forced)