Rubyでオブジェクトのメモリが解放されるところを見る
RubyにはObjectSpaceというモジュールがあって、Heap内のオブジェクトを見たりすることができます。デバッグ目的で使うものだと思いますが、過去にとあるライブラリの中で使われていてRubyがsegmentation faultで落ちたりして痛い目を見たことがあります。怖いやつです。
たとえばObjectSpace.count_objectsはHeap内のオブジェクトを種類ごとにカウントしてHashで返してくれます。
def diff_object
GC.disable
old = ObjectSpace.count_objects
yield if block_given?
ObjectSpace.count_objects.each do |k,v|
before = old[k] || 0
diff = v - before
puts "[#{k}] #{before} -> #{v} (#{diff})" if diff != 0
end
GC.enable
puts "---"
end
ブロックを渡すとそのブロック内の処理でどのくらいオブジェクトに変化があったのかがわかります。
class Tsu
end
diff_object
diff_object
diff_object do
100.times { Tsu.new }
end
の結果は
[FREE] 19855 -> 19827 (-28)
[T_STRING] 4687 -> 4714 (27)
[T_HASH] 31 -> 32 (1)
---
[FREE] 19825 -> 19824 (-1)
[T_HASH] 32 -> 33 (1)
---
[FREE] 19826 -> 19725 (-101)
[T_OBJECT] 42 -> 142 (100)
[T_HASH] 31 -> 32 (1)
---
Hashがひとつ増えているのは、ObjectSpace.count_objectsの返り値のHashだと思いますが、最初のブロックなしでの計測でStringオブジェクトが27増えているのはなんでしょうね。
どうして急にObjectSpaceの話をしだしたのかというと、とある処理でメモリが肥大化してしまう現象があって、GCが動いてるのかを調べたくなったからなのです。とある処理ってActiveRecord::Baseオブジェクトまわりなのですが、たとえばActiveRecord::Base.cacheブロック内で処理していると、オブジェクト自体がキャッシュとして保持されるのでGCの対象から外されているのではないかと疑ったりしたわけです。
オブジェクトが解放されたかどうかは、ObjectSpace.define_finalizerを使って調べることができます。
module GcChecker
def self.included(base)
base.extend ClassMethods
base.after_initialize :gc_hook
end
module ClassMethods
def finalize
proc { Rails.logger.info "#{name} gc!" }
end
end
def gc_hook
ObjectSpace.define_finalizer(self, self.class.finalize)
end
end
このブログのArticleモデルに組み込んでみます。
class Article < ActiveRecord::Base
include GcChecker
...
end
でいろいろ調べてみたのですが、まだ現象の原因はよくわかっていません。まだまだ修行が足りません。