ObjectSpace.reachable_objects_from()の活用の続き - GC Advent Calendar
Garbage Collection Advent Calendarの3日目の記事です。
2日目の@nari3さんの記事を読んで、これまだ続きが書けるよとtwitterでつぶやいたら書くことになってしまいました。口は災いの元。
さて、@nari3さんの記事ですが面白いですね。メモリリークしたところが分かって便利ですね。でも、ちょっと考えてみてください。はたしてメモリリークしたオブジェクトに都合よくどこに所属しているか書いてあるでしょうか?そういうことで、どこの変数に格納されたオブジェクトかも探すようにしました。
これです。
#!ruby -W0 require 'objspace' def find_pilferer(leak_id, bind) eval("local_variables", bind).each do |nm| val = eval(nm.to_s, bind) reaches = ObjectSpace.reachable_objects_from(val) if (reaches and reaches.map(&:object_id).include?(leak_id)) or val.object_id == leak_id then p [nm, val] end end global_variables.each do |nm| val = eval(nm.to_s) reaches = ObjectSpace.reachable_objects_from(val) if (reaches and reaches.map(&:object_id).include?(leak_id)) or val.object_id == leak_id then p [nm, val] end end ObjectSpace.each_object do |o| o.instance_variables.each do |nm| val = o.instance_variable_get(nm) reaches = ObjectSpace.reachable_objects_from(val) if (reaches and reaches.map(&:object_id).include?(leak_id)) or val.object_id == leak_id then p [o.object_id, nm, val] end end if o.is_a?(Module) then o.class_variables.each do |nm| val = o.class_variable_get(nm) reaches = ObjectSpace.reachable_objects_from(val) if (reaches and reaches.map(&:object_id).include?(leak_id)) or val.object_id == leak_id then p [o.object_id, nm, val] end end end end end # Test def foo @bar = ["I am bar"] @baz = ["I am baz"] leak_obj = "I am leaky..(T_T)" @bar << leak_obj @baz << leak_obj return leak_obj.object_id end leak_id = foo @baz.pop a = [[]] b = a a_id = a[0].object_id a = nil find_pilferer(leak_id, binding) find_pilferer(a_id, binding) class Foo @@h = {:a => []} h_id = @@h[:a].object_id @@a = @@h @@h = nil find_pilferer(h_id, binding) end
実行するとこんな感じです
[268671270, :@bar, ["I am bar", "I am leaky..(T_T)"]] [:b, [[]]] [269218790, :@@a, {:a=>[]}]
さすがにレシーバーが分かりませんが、インスタンス変数とかローカル変数とかの名前はわかります。find_pilfererに探したいobject idとbindingを渡すと変数名も探してくれます。
1行目に-W0なんて物騒なオプションが付いてますがこれが無いとグローバル変数を探すときに五月蠅いので黙らせてます。
いやー、GC Advent CalendarというよりRuby黒魔術Calendarって感じですが、マイルドです。マイルドな分制限が多くて、明示的にbindingを渡さないといけないところはその1つです。あと、callerのメソッドのローカル変数に格納されていると探せないとかクロージャーで閉じ込められた変数だとだめかもしれないとかいろいろあります。
これらの制限は多分乗り越えられるのですが、真っ黒な魔術を使うことになるのでこの辺でやめておきます。
それでは、他人のふんどしで相撲を取るmiura1729でした。