2010-07-29 25 views
6

Ich habe these slides über Java Finalizer gelesen. Darin beschreibt der Autor ein Szenario (auf Folie 33), wobei CleanResource.finalize() vom Finalizer-Thread ausgeführt werden könnte, während CleanResource.doSomething() noch auf einem anderen Thread ausgeführt wird. Wie konnte das passieren?Java GC Frage: Wie kann ein Objekt unerreichbar werden, während eine seiner Methoden noch ausgeführt wird?

Wenn doSomething() ist eine nicht-statische Methode, dann, um diese Methode jemanden auszuführen, irgendwo muss haben einen starken Bezug darauf ... richtig? Wie also könnte diese Referenz gelöscht werden, bevor die Methode zurückkehrt? Kann ein anderer Thread einspringen und den Verweis aufheben? Wenn das passiert, würde doSomething() immer noch normal auf dem ursprünglichen Thread zurückkehren?

Das ist alles, wirklich wissen, was ich will, aber für einen wirklich oben und jenseits Antwort, können Sie mir sagen, warum die doSomething() auf Folie 38 als die doSomething() 29. Warum auf Folie besser ist, ist es einfach ausreichend Rufen Sie diese keepAlive() Methode auf? Müssten Sie nicht den gesamten Anruf auf myImpl.doSomething() in einem synchronized(this){} Block wickeln?

Antwort

3

EDIT3:

Das Ergebnis ist, daß der Destruktor und ein reguläres Verfahren können gleichzeitig auf der gleichen Instanz ausgeführt werden. Hier ist eine Erklärung, wie das passieren kann. Der Code ist im Wesentlichen:

class CleanResource { 
    int myIndex; 
    static ArrayList<ResourceImpl> all; 

    void doSomething() { 
    ResourceImpl impl = all.get(myIndex); 
    impl.doSomething(); 
    } 

    protected void finalize() { ... } 
} 

dieser Client-Code Gegeben:

CleanResource resource = new CleanResource(...); 
resource.doSomething(); 
resource = null; 

Dies könnte wie diese Pseudo-C

register CleanResource* res = ...; call ctor etc.. 
// inline CleanResource.doSomething() 
register int myIndex = res->MyIndex; 
ResourceImpl* impl = all->get(myInddex); 
impl->DoSomething(); 
// end of inline CleanResource.doSomething() 
res = null; 

wie das Ausgeführt etwas JITed werden, res wird nach der gelöschten inline CleanResource.doSomething() ist getan, so dass die GC wird nicht passieren, bis nach dieser Methode die Ausführung abgeschlossen ist. Es gibt keine Möglichkeit, die Ausführung gleichzeitig mit einer anderen Instanzmethode für dieselbe Instanz abzuschließen.

Aber das Schreiben in res nicht nach diesem Punkt verwendet wird, und da gibt es keine Zäune, kann es früher in der Ausführung bewegt werden, um unmittelbar nach dem Schreib:

register CleanResource* res = ...; call ctor etc.. 
// inline CleanResource->doSomething() 
register int myIndex = res->MyIndex; 
res = null; /// <----- 
ResourceImpl* impl = all->get(myInddex); 
impl.DoSomething(); 
// end of inline CleanResource.doSomething() 

Am markiert location (< ---), es gibt keine Verweise auf die CleanResource-Instanz und daher ist es für die Sammlung und die Finalizer-Methode geeignet. Da der Finalizer jederzeit nach dem Löschen der letzten Referenz aufgerufen werden kann, ist es möglich, dass der Finalizer und der Rest der CleanResource.doSomething() parallel ausgeführt werden.

EDIT2: Die keepAlive() stellt sicher, dass auf den this Zeiger am Ende der Methode zugegriffen wird, damit der Compiler die Verwendung des Zeigers nicht optimieren kann. Und dass dieser Zugang garantiert ist, in der angegebenen Reihenfolge geschehen (das synchronisierte Wort markiert einen Zaun, die Nachbestellung nicht zulässt, der liest und schreibt vor/nach diesem Punkt.)

Original-Beitrag:

Das Beispiel sagt dass die doSomething-Methode aufgerufen wird und einmal aufgerufen wird, können die über den Zeiger this referenzierten Daten früh gelesen werden (myIndex im Beispiel).Sobald die referenzierten Daten gelesen sind, wird der Zeiger this in dieser Methode nicht mehr benötigt und der CPU/Compiler überschreibt möglicherweise die Register/deklariert das Objekt als nicht mehr erreichbar. Der GC kann dann gleichzeitig den Finalizer aufrufen, während die doSomething() -Methode des Objekts ausgeführt wird.

Aber da der Zeiger this nicht verwendet wird, ist es schwer zu sehen, wie dies irgendeine greifbare Wirkung haben wird.

EDIT: Nun, vielleicht, wenn es Cache-Pointer auf die Felder des Objekts gibt, auf die zugegriffen wird über Cache, berechnet von this, bevor es zurückgefordert wurde, und das Objekt wird dann zurückgewonnen die Speicherreferenzen werden ungültig. Es gibt einen Teil von mir, der es schwer hat zu glauben, dass dies möglich ist, aber dann scheint dies ein heikler Eckfall zu sein, und ich denke nicht, dass es in JSR-133 etwas gibt, das das standardmäßig verhindert. Es ist eine Frage, ob ein Objekt nur durch Zeiger auf seine Basis oder durch Zeiger auf seine Felder als referenziert wird.

+0

Also könnte dies auch auf einer stack-basierten VM passieren? Jetzt, wo ich auf Bytecodeebene denke, wie du bist, verstehe ich das viel besser. Ich denke, im Fall einer Stapelmaschine muss die Referenz nicht auf dem Stapel sein, damit "invokevirtual" abgeschlossen werden kann. Sobald der 'myIndex'-Wert abgerufen wurde, kann 'this' vom Stapel genommen und möglicherweise zurückgewonnen werden. –

+0

Bei einer strikten stackbasierten Implementierung ist dies nicht möglich, da der this-Zeiger auf dem Stack verbleibt. Aber mit Inlining, Out-of-Order-Ausführung und Registerzuweisung wird ein gleichzeitiger Aufruf an den Finalizer möglich. Sehen Sie meine letzte Bearbeitung in dieser fortlaufenden Saga. :) – mdma

+0

Große Antwort! Ich kannte niemanden außer Androids Dalvik VM hatte eine registerbasierte JVM implementiert. Ist das üblich? –

Verwandte Themen