2017-02-07 8 views
4

Ich habe ein Speicherverlust in einer Rails 4.2.6-Anwendung. Ein Controller weist ein großes GaragesPresenter-Objekt als Instanzvariable zu, die rückgängig gemacht und nach Abschluss der Anforderung bereinigt werden soll. Ich sehe jedoch, dass dies nie passiert.Rails Speicherverlust: Controller-Klasse, die einen Verweis auf Instanz

def show 
    @garage = GaragesPresenter.new(@garage, view_context) 
    respond_to do |format| 
    format.html 
    end 
end 

sehe ich, dass ein Verweis auf die GaragesPresenter Instanz wird durch die GaragesController Instanz gehalten wird, und eine Instanz zu dem durch die GaragesController Klasse festgehalten wird. Dies ist wahr, lange nachdem die Anfrage abgeschlossen und GC.start aufgerufen wurde. Warum enthält die Klasse GaragesController einen Verweis auf die Instanz?

Ich weiß das, weil ich einen Heapdump mit ein:

require 'objspace' 
... 
GC.start 
file = File.open("/tmp/dumpfile", 'w') 
ObjectSpace.dump_all(output: file) 

Und in der resultierenden Datei ich die folgenden drei Objekte sehen:

Das folgende Objekt ist ein GaragesPresenter, das ist sehr groß:

{"address":"0x7fd077217e20", "type":"OBJECT", "class":"0x7fd074a04618", "ivars":7, "references":["0x7fd0772bf940", "0x7fd077711480", "0x7fd077748188", "0x7fd077772898", "0x7fd07720c778", "0x7fd0771ef8d0", "0x7fd0771ef8d0"], "file":"/Users/dyoung/workspace/commutyble/site-app/app/controllers/garages_controller.rb", "line":19, "method":"new", "generation":35, "memsize":56, "flags":{"wb_protected":true, "old":true, "marked":true}}

Ein Verweis auf die obige Aufgabe wird durch eine GaragesController Instanz gehalten wird (zu erwarten, da die Show-Methode den Moderator als Instanzvariable zuordnet):

{"address":"0x7fd0727559f0", "type":"OBJECT", "class":"0x7fd0727865a0", "ivars":22, "references":["0x7fd0727558b0", "0x7fd072755888", "0x7fd072755838", "0x7fd0732400e0", "0x7fd072754a50", "0x7fd0734c5658", "0x7fd07704e878", "0x7fd0732ab020", "0x7fd072785ee8", "0x7fd077217e20", "0x7fd0771ffe10", "0x7fd07720cde0", "0x7fd0732a82d0"], "file":"/Users/dyoung/.rvm/gems/ruby-2.1.0/gems/actionpack-4.2.6/lib/action_controller/metal.rb", "line":237, "method":"new", "generation":35, "memsize":176, "flags":{"wb_protected":true, "old":true, "marked":true}}

Ein Verweis auf das obigen GaragesController Beispiel wird gehalten von der GaragesController-Klasse, vermutlich verhindert Garabage-Sammlung. Warum??

{"address":"0x7fd0727865a0", "type":"CLASS", "class":"0x7fd0726a7260", "name":"GaragesController", "references":["0x7fd0727559f0", "0x7fd0726a72b0"], "file":"/Users/dyoung/.rvm/gems/ruby-2.1.0/gems/activesupport-4.2.6/lib/active_support/callbacks.rb", "line":435, "method":"instance_exec", "generation":35, "memsize":672, "flags":{"wb_protected":true, "old":true, "marked":true}}

+1

scheint '[WeakRef] (Rubin-doc. org/stdlib-1.9.3/libdoc/weakref/rdoc/WeakRef.html) 'kann helfen – oklas

Antwort

3

Sie benötigen WeakRef

Schwache Referenzklasse zu verwenden, die eine referenzierte Objekt ermöglicht Garbage Collection zu sein. Ein WeakRef kann genau wie das Objekt Referenzen verwendet werden.

foo = Object.new 

foo = WeakRef.new(foo) # Creates a weak reference to orig 

ObjectSpace.garbage_collect 

p foo.to_s  # should raise exception (recycled) 

Der dort Anwendungsfall ist in den zwei Objektreferenz verwendet wird. Der erste ist Meister und der zweite ist schwach. Ihr Objekt wird erst dann abgemagert, wenn der erste Master-Link verwendet wird. Verwenden Sie die Master-Verknüpfung (generische Variable) im Objekt, die mindestens genauso lange wie das referenzierte Objekt vorhanden ist. Und innerhalb des referenzierten Objekts verwenden Sie eine schwache Verbindung.

Es ist entsprechende Praxis für diesen Fall. In anderen Sprachen auch mit Garbage Collector, zum Beispiel in Perl. Die C++ - Bibliotheken haben zu viele Lösungen für Speicherverwaltungsstrategien. Garbage Collector kann Müll (Objekte) nicht entfernen, wenn sie verwendet werden. Wenn sich das Objekt auf ein anderes bezieht und ein anderes sich auf das erste bezieht, bedeutet dies, dass beide verwendet werden. Es ist also kein Müll - es ist nützlich für "Müllsammler Meinung". Aber es ist wirklich Müll - und es ist Speicherleck.

Graph von Objekten-Referenzen dürfen keine Schleifen oder Zyklen haben. Wenn wir eine Referenz benötigen, die eine Schleife oder einen Zyklus im Objektreferenzdiagramm bildet, müssen wir mindestens eine quasi schneidende schwache Referenz in jedem verwenden.

+0

Danke für den Tipp. Aber ich möchte nicht, dass mein '@ garage'-Objekt Müll gesammelt wird, bis ich damit fertig bin. Es scheint, dass, wenn ich 'WeakRef' verwende, es eine Chance gibt, dass es Müll gesammelt werden könnte, bevor die Controller-Methode abgeschlossen ist. Liege ich falsch? – davidgyoung

+0

wird immer gut. Ich habe eine Verbesserung der Antwort gemacht. Verwenden Sie den Master-Link an dieser Stelle, an der kein Müll gesammelt werden soll. – oklas

+0

Ja, es scheint, als könnte das mit der Verbesserung funktionieren. Aber das scheint den gleichen Effekt zu haben, indem ich am Ende meiner Methode einfach '@ garage = nil 'setze. Obwohl beides eine sinnvolle Umgehungslösung sein kann, ist mein Verständnis, dass ich das nicht in Rails tun sollte. Ich möchte zuerst verstehen, was ich falsch mache, wenn überhaupt, das dieses unerwartete Verhalten verursacht. – davidgyoung

1

GaragesPresenter hält eine Referenz auf view_context

@garage = GaragesPresenter.new(@garage, view_context) 

view_context gibt ein instance of of a view class die einen Verweis auf self hält, die der rufenden Controller:

# File actionview/lib/action_view/rendering.rb, line 71 
def view_context 
    view_context_class.new(view_renderer, view_assigns, self) 
end 
+0

Danke. Dies sollte jedoch die Speicherbereinigung nicht verhindern, richtig? Ich denke, es ist in Ordnung für den Moderator, einen Verweis auf die Controller-Instanz zu halten, da beide Objekte nur für die Lebensdauer der Anfrage existieren sollten. Beide Objekte sollten nach dem Ende der Anforderung für die Garbage Collection verfügbar sein. – davidgyoung

+0

Es sollte nicht, es sei denn, etwas hält sich an den Controller oder Moderator, in diesem Fall werden beide bestehen bleiben. Ich würde mir genauer ansehen, ob irgendwelche Konstanten mutiert werden. – fylooi

+0

Ja, in meinem Fall habe ich Beweise, dass das Etwas einen Hinweis auf den Controller enthält. Was meinst du mit "ob irgendwelche Konstanten mutiert werden"? – davidgyoung

Verwandte Themen