QA@IT

Ruby のオブジェクトが GC されるタイミング

7114 PV

Ruby でオブジェクトが GC されるタイミングを調べています。

実験のため、http://www.ruby-lang.org/ja/old-man/html/ObjectSpace.html に記載されている ObjectSpace.define_finalizer の良い例の最後に sleep 100 を追加した次のようなコードを書いて実行してみたのですが、sleep 100 が終わるまで Bar.callback が実行されません。Bar.new で Bar のオブジェクトが生成され、どこからも参照されていないのですぐに GC 対象になると思うのですが、何故 GC.start で GC されないのでしょうか。

#!/usr/bin/env ruby

class Bar
  def Bar.callback
    proc {
      puts "bar"
    }
  end
  def initialize
    ObjectSpace.define_finalizer(self, Bar.callback)
  end
end
Bar.new
GC.start
sleep 100

実験した Ruby は以下の 2 つで、結果は同じでした。

$ ruby1.8 -v
ruby 1.8.7 (2012-02-08 patchlevel 358) [x86_64-linux]
$ ruby1.9.1 -v
ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]

回答

一般的に,Ruby の GC では「人間が見て GC されてもいいんじゃないの」と思うタイミングではなく,「処理系によって,確実に GC しても良さそうだ」,と理解したタイミングで GC されます.なので,運 (*1) とかが絡みます.

まぁ,そういう原理原則のを踏まえて,手元でやってみたところ,Ruby の開発版(ruby 2.0.0dev (2012-06-20 trunk 36148) [i386-mswin32_100])では,ちゃんと sleep の前に GC されました.

で,この例は,Shu さんは消えると不味い,と仰っていますが,このタイミングでは外からアクセスする方法はないので(たとえば,ローカル変数に代入している場合は参照される可能性がありますが,この場合はどこにも参照されません.これは,トップレベルなスコープも,メソッドローカルなスコープも関係ありません),GC されても問題無いです.kenn さんの「TOPLEVEL_BINDING と関係あるか?」という話ですが,この場合は関係ありません.多分.

*1: 運と書きましたが,厳密には OS やらコンパイラやらの実装などが絡みます.あと,もちろん Ruby のバージョンも大いに関係します.その日の運にも依存する場合がありますので,星占いなども参考になるかもしれません.

こんなわけで,オブジェクトが GC されるタイミングは運によってしまうので,オブジェクトの後始末を特定のタイミングで行いたいときは,ファイナライザなどに頼らず,きちんと後始末処理を ensure 節などで書くことをお勧めします.

編集 履歴 (2)

Rubyに限った話ではないと思うのですが、
ブロック内で作成されたインスタンスならブロックを抜けた時点で不要なインスタンスと
分かるのですが、提示された場所はグローバルな場所で閉じられたブロック内ではない為
一番息の長いところにインスタンスが出来てしまっているのでGCは不要なインスタンスであることを
知ることは出来ません。インスタンスを作ってそれが代入されていないからといって必要ないとは限らないからです。例えばclass Barの中でnew後何らかの処理が別スレッドで実行されているような作りであった場合、GCがインスタンスを片付けてしまっては大変なことになります。

編集 履歴 (1)

ちょっと追試してみましたが、面白い結果になりました。

#!/usr/bin/env ruby

class Bar
  def self.finalize
    proc { puts 'bar' }
  end

  def initialize
    ObjectSpace.define_finalizer(self, self.class.finalize)
  end
end

at_exit { puts 'at_exit' }

3.times.each do
  Bar.new
end

GC.start

で結果が

bar
bar
at_exit
bar

最後の一個だけ、GCで消されずに残っていました。回数を変えても同じで、最後の一個だけ残ります。なんとなく、意図的にそういう挙動をしているように見えます。(これがTOPLEVEL_BINDING固有なのかどうかまでは追ってないのでわかりません)

回答になってなくて申し訳ないですが、どなたか詳しい人の意見を聞いてみたいです。

編集 履歴 (1)
ウォッチ

この質問への回答やコメントをメールでお知らせします。