29

Studentテーブルに 100 レコードで呼び出される単純な ActiveRecord モデルがあります。Rails コンソール セッションで次のことを行います。

ObjectSpace.each_object(ActiveRecord::Base).count
# => 0

x = Student.all

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100

x = nil
GC.start

ObjectSpace.each_object(ActiveRecord::Base).count
# => 0     # Good!

今、私は次のことを行います:

ObjectSpace.each_object(ActiveRecord::Base).count
# => 0

x = Student.all.group_by(&:last_name)

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100

x = nil
GC.start

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100     # Bad!

なぜこれが起こるのか、そして基礎となるハッシュ構造を知らなくてもこれを解決するスマートな方法があるかどうかを誰かが説明できますか? 私はこれを行うことができることを知っています:

x.keys.each{|k| x[k]=nil}
x = nil
GC.start

そして、すべての Student オブジェクトをメモリから正しく削除しますが、一般的な解決策があるかどうか疑問に思っています (私の現実の問題は広く広がっており、上記のハッシュよりも複雑なデータ構造を持っています)。

Ruby 1.9.3-p0 と Rails 3.1.0 を使用しています。

更新(解決済み)

以下の Oscar Del Ben の説明によると、いくつかの ActiveRecord::Relation オブジェクトが問題のあるコード スニペットで作成されます (実際には両方のコード スニペットで作成されますが、何らかの理由で 2 番目のコード スニペットでのみ「誤動作」します。誰かが光を当てることができますか?どうして?)。これらは、@records というインスタンス変数を介して ActiveRecord オブジェクトへの参照を維持します。このインスタンス変数は、ActiveRecord::Relation の「reset」メソッドを介して nil に設定できます。すべての関係オブジェクトでこれを実行する必要があります。

ObjectSpace.each_object(ActiveRecord::Base).count
# => 100

ObjectSpace.each_object(ActiveRecord::Relation).each(&:reset)

GC.start
ObjectSpace.each_object(ActiveRecord::Base).count
# => 0

注: Mass.detach (参照されているruby ​​-mass gem Oscar Del Ben を使用) を使用することもできますが、上記のコードよりもはるかに遅くなります。上記のコードは、一部の ActiveRecord::Relation オブジェクトをメモリから削除しないことに注意してください。ただし、これらはかなり重要ではないようです。あなたはやってみることができます:

Mass.index(ActiveRecord::Relation)["ActiveRecord::Relation"].each{|x| Mass.detach Mass[x]}
GC.start

そして、これにより ActiveRecord::Relation オブジェクトの一部が削除されますが、すべてではありません (理由は不明で、残っているオブジェクトには Mass.references がありません。奇妙です)。

4

2 に答える 2

11

私は何が起こっているのか知っていると思います。Ruby の GC は不変オブジェクト (シンボルなど!) を解放しません。group_by によって返されるキーは不変の文字列であるため、ガベージ コレクションは行われません。

更新

Rails自体の問題ではないようです。group_by を単独で使用してみましたが、オブジェクトがガベージ コレクションされない場合がありました。

oscardelben~/% irb
irb(main):001:0> class Foo
irb(main):002:1> end
=> nil
irb(main):003:0> {"1" => Foo.new, "2" => Foo.new}
=> {"1"=>#<Foo:0x007f9efd8072a0>, "2"=>#<Foo:0x007f9efd807250>}
irb(main):004:0> ObjectSpace.each_object(Foo).count
=> 2
irb(main):005:0> GC.start
=> nil
irb(main):006:0> ObjectSpace.each_object(Foo).count
=> 0
irb(main):007:0> {"1" => Foo.new, "2" => Foo.new}.group_by
=> #<Enumerator: {"1"=>#<Foo:0x007f9efb83d0c8>, "2"=>#<Foo:0x007f9efb83d078>}:group_by>
irb(main):008:0> GC.start
=> nil
irb(main):009:0> ObjectSpace.each_object(Foo).count
=> 2 # Not garbage collected
irb(main):010:0> GC.start
=> nil
irb(main):011:0> ObjectSpace.each_object(Foo).count
=> 0 # Garbage collected

私は GC 内部 (驚くほど理解しやすい) を掘り下げましたが、これはスコープの問題のようです。Ruby は現在のスコープ内のすべてのオブジェクトを調べて、まだ使用されていると思われるオブジェクトをマークします。その後、ヒープ内のすべてのオブジェクトを調べて、マークされていないオブジェクトを解放します。

この場合、スコープ外であっても、ハッシュはまだマークされていると思います。これが起こる理由はたくさんあります。調査を続けます。

更新 2:

オブジェクトの参照を保持しているものを見つけました。そのために、ルビー マスジェムを使用しました。Active Record リレーションは、返されたオブジェクトを追跡していることがわかりました。

User.limit(1).group_by(&:name)
GC.start
ObjectSpace.each_object(ActiveRecord::Base).each do |obj|
  p Mass.references obj # {"ActiveRecord::Relation#70247565268860"=>["@records"]}
end

残念ながら、resetリレーションを呼び出すことは役に立たなかったようですが、今のところこれで十分な情報であることを願っています。

于 2012-06-26T02:00:08.433 に答える
2

俺は答えを知らない

しかし、私はhttp://blog.headius.com/2010/07/browsing-memory-jruby-way.htmlにあるようにヒープを調べてみました

https://skitch.com/deepak_kannan/en3d/java-visualvmにスクリーンショットを添付しました。 これは単純なプログラムでした

class Foo; end
f1 = Foo.new
f2 = Foo.new
GC.start

次に、上記のように jvisualvm を使用しました。これをirbで実行していました。
jruby がオブジェクトのスコープを追跡しているようです。そのオブジェクトへの弱い参照がない場合、オブジェクトは GC されません。

于 2012-06-24T11:37:11.697 に答える