Goで循環参照なオブジェクトはGCはされるのか
要約
Goでオブジェクトの循環参照を作ったときGCはされるのか.
マークアンドスイープなのでされる.
実験で確かめた.
こんにちは
この記事はKMCアドベントカレンダーの14日目の記事です.
adventar.org
昨日はdefinedさんの,素人が1から音ゲー曲を作る話【KMCアドベントカレンダー2021】 - でぃふぁいんどの空間でした.
ローパスフィルタっぽいところとか音ゲーっぽくてよかった.
defined.hatenadiary.com
この記事では,Goでオブジェクトの循環参照を作ったときGCで回収されるのかを書きます.
GCの細かいアルゴリズムの話はしません(できない).
実験してGCの動作を確かめたよ,という話です.
循環参照しているコード
Goではパッケージの循環参照はできません.
ですがオブジェクト同士の循環参照はできます.
package main type S1 struct { s2 *S2 } type S2 struct { s1 *S1 } func main(){ s1 := S1{} s2 := S2{} s1.s2 = &s2 s2.s1 = &s1 }
s1,s2のオブジェクトがお互いを参照しています.
GoのGC
上記のコードですが,仮にs1,s2がヒープにいて,参照カウントでGCしてたら回収されなさそうです.*1
実際にはGoのGCの実装方式はmark-and-sweep方式で,上記のようなコードでも問題なく回収されます.
goのレポジトリのコメントにmark-and-sweep方式であることが書かれています.
mark-and-sweep方式の解説は調べてください.
簡単な理解だと「ルートから探索して到達できなくなったオブジェクトはGCで回収される」という方式で,
循環参照していてもルートから到達不可能であれば回収されます.
実験
もうわかったようなものですが,実際に動かして確認してみます.
実験に使ったコードです.
package main import ( "fmt" "runtime" ) type S1 struct { s2 *S2 } type S2 struct { s1 *S1 } func main() { m1 := runtime.MemStats{} runtime.ReadMemStats(&m1) for i := 0; i < 10000; i++ { Func() } // runtime.GC() m2 := runtime.MemStats{} runtime.ReadMemStats(&m2) fmt.Println("before GC: ", m1.HeapObjects) fmt.Println("after GC: ", m2.HeapObjects) return } func Func() { s1 := S1{} s2 := S2{} s1.s2 = &s2 s2.s1 = &s1 fmt.Println(s1.s2) }
GCに回収されたかどうかはGC前後のヒープサイズを比較して確かめることにしました.
メモリに関する情報はruntime.ReadMemStatsで取得することができます.
MemStats.HeapObjectsはヒープに割り当てられたオブジェクトの数です.
今回はこの数値を比較します.
さてまず循環参照しているオブジェクトをヒープに置く必要があります.
go build -gcflags '-m' main.goとするとヒープとスタックどちらに置かれるか確かめることができます.
./main.go:32:2: moved to heap: s1 ./main.go:33:2: moved to heap: s2
ちゃんとヒープに配置されています.
ちなみにfmt.Printlnにs1を渡さないとスタックに配置されます.
あとはGC前後のヒープサイズを見れば,GCで回収されたのか分かりそうです.
ループでFuncを繰り返し実行しています.
GC前のヒープサイズ-GC後のヒープサイズが実行するたびに若干ブレるため,変化が分かりやすいようにそうしています.
GCを実行しなかった場合の結果
... before GC: 151 after GC: 20156
GCを実行した場合の結果
before GC: 146 after GC: 148
GCを実行したときはヒープサイズが小さくなっています.
これで無事循環参照もGCによって回収されることが確認できました.
めでたし.
まとめ
Goでは循環参照なオブジェクトもGCされることを実験して確かめました.
runtimeパッケージが便利ですね.
プログラムを計測する際に色々使えそうです.
これ書いてる途中で気になることも出たのでもうちょっと色々調べたかったけど,時間が来たので終わりです.
明日以降のKMCアドベントカレンダーもお楽しみに.