おおいしつかさ


旅行とバイクとドライブと料理と宇宙が好き。
Ubie Discoveryのプログラマ。
Share:  このエントリーをはてなブックマークに追加

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  

でいろいろ調べてみたのですが、まだ現象の原因はよくわかっていません。まだまだ修行が足りません。