13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Linkbal(リンクバル)Advent Calendar 2020

Day 9

【Ruby】Objectのメモリっていつ解放されるの?

Posted at

僕はRubyからプログラミングを書くようになった現代っ子エンジニアですが、Rubyについて学べば学ぶほど、裏でこんなことが起きてるんだと驚く日々です!

ふと思ったんですが
image.png
もちろん、ブロック内でしか参照できないので、ブロックの実行が終わるごとに消えそうです
するとじゃあ、消えるって何なの?ってなります
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以降) などに採用されている

ガーベージコレクションは実行されると、使わなくなったオブジェクト(メモリ領域?)を見つけて、メモリを解放してくれる仕組み
オブジェクトはyoungoldのどっちかに判定されて、主に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によって回収されないので、解放されないことです

test.rb
# ruby test.rb で実行
p GC.stat
a = Array.new(3000) do
  'x'
end
GC.start(full_mark: false) # マイナーGC実行
p GC.stat

↓一部抜粋

後 
image.png image.png

↓各項目の参考 : https://gist.github.com/sonots/71277ef3f9b53fa87862

{
  :heap_live_slots=>, # 生きてるスロット(オブジェクト)の数
  :heap_free_slots=>, # 空いてるスロットの数
  :minor_gc_count=>, # マイナーGCの実行回数
  :old_objects=>, # マイナーGCで解放されないオブジェクトの数
}

今回は3000個ほどのオブジェクトが解放されない(oldに判定される)ので、old_objectsが3000件ぐらい残ってるのがわかりますね。(実際には内部で色々実行されるので、値は毎回違います)
内部の何かしらが解放されて、生きてるスロットが減って、空いてるスロットが増えてますね

代入しないパターンもやってみましょう

test.rb
p GC.stat
Array.new(3000) do
  'x'
end
GC.start(full_mark: false) # マイナーGC実行
p GC.stat
後 
image.png image.png

今回は3000個解放されるので、old_objectsがさっきほど増えてないですね
また、空いてるスロットも代入の時より3000件ほど多くて、生きてるスロットは3000件ほど少ないです

所感

本題がちょっとミスリードっぽくなってしまいましたね、、メモリ解放じゃなくてオブジェクト解放になりました(つまり同じかもですが、何とも😒)
これが理解できると、なぜRailsのメモリは肥大化し続けるのか以下のquora回答の意味もわかると思います。
プロダクションの Rails サーバーの利用メモリがひたすら増加していくような挙動を観測したとき、どう対応するのがよいですか?

まあでもここまで書いておいて何ですが、こんなこと考えて開発したくないと思いました😈
C言語時代にメモリ解放を頑張って書いてた人には勤労感謝したい

13
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?