僕はRubyからプログラミングを書くようになった現代っ子エンジニアですが、Rubyについて学べば学ぶほど、裏でこんなことが起きてるんだと驚く日々です!
ふと思ったんですが
もちろん、ブロック内でしか参照できないので、ブロックの実行が終わるごとに消えそうです
するとじゃあ、消える
って何なの?ってなります
OSには詳しくないですが、多分メモリ上から消えるってことですよね
ってことは多分、メモリに書き込む
とメモリから消す
という動作があるのかなーという予想🪐
Rubyの代入がobject_id
渡しってのはご存知の通りとして、
ってことは多分オブジェクトの生成の中でobject_idの確定
とメモリの確保(書き込み)
があるんだろうなーという予想🪐🪐
一方で、メモリの解放
はいつ行われてるんだろう???ブロック終了時に毎回行ってるんかな?
ちょっとRubyのメモリ管理について調べます
半信半疑で書いてるので、半信半疑で読んでね😈
CRuby前提で書くのでご了承
ruby -v
# => ruby 2.5.1p57 (2018-03-29 revision 63029) [x86_64-linux]
Rubyのメモリ解放の仕組み(Garbage Collection)
Rubyでは世代別ガーベージコレクションという仕組みが使われているらしいです
Ruby (2.1以降) などに採用されている
ガーベージコレクションは実行されると、使わなくなったオブジェクト(メモリ領域?)を見つけて、メモリを解放してくれる仕組み
オブジェクトはyoung
とold
のどっちかに判定されて、主にyoung
のオブジェクトを解放する
Rubyではyoungの解放
をマイナーGC
って呼び、young/oldの解放
をフルGC
って呼びます
GC(ガーベージコレクション実行のモジュール)を見てみると
マイナーGC - 新しいオブジェクト(前回のGCの後に生成されたオブジェクト) のみを対象としたGC
マイナーGCでGCされなかったオブジェクトはoldobjectと呼ぶ
つまり、前回のマイナーGCから今回のマイナーGCまでに作成されたオブジェクトが、oldとして残る
or 解放される
ってことなんですね
RubyのフルGCは oldobject の個数が閾値 old_objects_limit を越えるごとに実行される
malloc_increase_bytes が malloc_increase_bytes_limits を越えるとマイナーGCが起動される
oldmalloc_increase_bytes が oldmalloc_increase_bytes_limits を越えるとフルGCが起動される
なるほど、ブロックごとにメモリ解放ではなくて、オブジェクトの個数や、メモリ使用量(byte)が閾値を超えたら解放してるのか
やってみよう
GC.statでオブジェクトの解放を確認
自動でGCが行われない程度に、3000行の配列を生成して、マイナーGCを実行
ポイントは、a
に代入してることで、GCによって回収されないので、解放されないことです
# ruby test.rb で実行
p GC.stat
a = Array.new(3000) do
'x'
end
GC.start(full_mark: false) # マイナーGC実行
p GC.stat
↓一部抜粋
前 | 後 |
---|---|
↓各項目の参考 : https://gist.github.com/sonots/71277ef3f9b53fa87862
{
:heap_live_slots=>, # 生きてるスロット(オブジェクト)の数
:heap_free_slots=>, # 空いてるスロットの数
:minor_gc_count=>, # マイナーGCの実行回数
:old_objects=>, # マイナーGCで解放されないオブジェクトの数
}
今回は3000個ほどのオブジェクトが解放されない(old
に判定される)ので、old_objects
が3000件ぐらい残ってるのがわかりますね。(実際には内部で色々実行されるので、値は毎回違います)
内部の何かしらが解放されて、生きてるスロットが減って、空いてるスロットが増えてますね
代入しないパターンもやってみましょう
p GC.stat
Array.new(3000) do
'x'
end
GC.start(full_mark: false) # マイナーGC実行
p GC.stat
前 | 後 |
---|---|
今回は3000個解放されるので、old_objects
がさっきほど増えてないですね
また、空いてるスロットも代入の時より3000件ほど多くて、生きてるスロットは3000件ほど少ないです
所感
本題がちょっとミスリードっぽくなってしまいましたね、、メモリ解放
じゃなくてオブジェクト解放
になりました(つまり同じかもですが、何とも😒)
これが理解できると、なぜRailsのメモリは肥大化し続けるのか以下のquora回答の意味もわかると思います。
プロダクションの Rails サーバーの利用メモリがひたすら増加していくような挙動を観測したとき、どう対応するのがよいですか?
まあでもここまで書いておいて何ですが、こんなこと考えて開発したくないと思いました😈
C言語時代にメモリ解放を頑張って書いてた人には勤労感謝したい