Ich habe einen Fehler in meinem Code untersucht, der anscheinend durch einen “hässlichen” Finalizer-Code verursacht wird. Der Code sieht ungefähr so aus
public class A {
public B b = new B();
@Override public void finalize() {
b.close();
}
}
public class B {
public void close() { /* do clean up our resources. */ }
public void doSomething() { /* do something that requires us not to be closed */ }
}
void main() {
A a = new A();
B b = a.b;
for(/*lots of time*/) {
b.doSomething();
}
}
Was meiner Meinung nach passiert, ist das a
wird als ohne Referenzen nach der zweiten Zeile von erkannt main()
und durch den Finalizer-Thread GC’d und finalisiert zu werden – während die for
Schleife passiert immer noch, mit b
während a
noch “in Reichweite”.
Ist das plausibel? Darf Java ein Objekt GC machen, bevor es den Gültigkeitsbereich verlässt?
Hinweis: Ich wissen dass es schlecht ist, irgendetwas innerhalb von Finalizern zu tun. Dies ist Code, den ich geerbt habe und den ich beheben möchte – die Frage ist, ob ich das Grundproblem richtig verstehe. Wenn dies nicht möglich ist, muss etwas Subtileres die Wurzel meines Fehlers sein.
Kann Java ein Objekt finalisieren, wenn es sich noch im Gültigkeitsbereich befindet?
Jawohl.
Allerdings bin ich hier pedantisch. Zielfernrohr ist ein Sprachkonzept, das die Gültigkeit von Namen bestimmt. Ob ein Objekt Garbage Collection (und somit finalisiert) werden kann, hängt davon ab, ob dies der Fall ist erreichbar.
Die Antwort von ajb hätte es fast geschafft (+1), indem sie eine signifikante Passage aus dem JLS zitierte. Ich denke jedoch nicht, dass es direkt auf die Situation anwendbar ist. JLS §12.6.1 sagt auch:
EIN erreichbar Objekt ist ein beliebiges Objekt, auf das in jeder möglichen fortlaufenden Berechnung von jedem Live-Thread aus zugegriffen werden kann.
Stellen Sie sich nun vor, dass dies auf den folgenden Code angewendet wird:
class A {
@Override protected void finalize() {
System.out.println(this + " was finalized!");
}
public static void main(String[] args) {
A a = new A();
System.out.println("Created " + a);
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
// System.out.println(a + " was still alive.");
}
}
Auf JDK 8 GA wird dies abgeschlossen a
jedes Mal. Wenn Sie die auskommentieren println
Am Ende, a
wird nie abgeschlossen sein.
Mit dem println
auskommentiert, kann man sehen, wie die Erreichbarkeitsregel gilt. Wenn der Code die Schleife erreicht, gibt es keine Möglichkeit, auf die der Thread zugreifen kann a
. Daher ist es nicht erreichbar und unterliegt daher der Finalisierung und Garbage-Collection.
Beachten Sie, dass der Name a
ist immer noch im Visier weil man es gebrauchen kann a
irgendwo innerhalb des umschließenden Blocks – in diesem Fall die main
method body — von seiner Deklaration bis zum Ende des Blocks. Die genauen Geltungsbereichsregeln werden in JLS behandelt §6.3. Aber wie Sie sehen können, hat der Geltungsbereich nichts mit Erreichbarkeit oder Garbage Collection zu tun.
Um zu verhindern, dass das Objekt von der Garbage Collection erfasst wird, können Sie einen Verweis darauf in einem statischen Feld speichern, oder wenn Sie dies nicht möchten, können Sie es erreichbar halten, indem Sie es später in derselben Methode nach der Zeit verwenden. verbrauchende Schleife. Es sollte ausreichen, eine harmlose Methode wie aufzurufen toString
darauf.
JLS §12.6.1:
Es können Optimierungstransformationen eines Programms entworfen werden, die die Anzahl der erreichbaren Objekte auf weniger reduzieren als diejenigen, die naiverweise als erreichbar angesehen würden. Beispielsweise kann sich ein Java-Compiler oder Codegenerator dafür entscheiden, eine Variable oder einen Parameter, der nicht mehr verwendet wird, auf null zu setzen, um zu bewirken, dass der Speicher für ein solches Objekt potenziell früher zurückgewonnen werden kann.
Also ja, ich denke, es ist zulässig, dass ein Compiler versteckten Code zum Set hinzufügt a
zu null
, wodurch die Garbage-Collection ermöglicht wird. Wenn dies der Fall ist, können Sie dies möglicherweise nicht anhand des Bytecodes erkennen (siehe Kommentar von @ user2357112).
Mögliche (hässliche) Problemumgehung: Hinzufügen public static boolean alwaysFalse = false;
in die Hauptklasse oder einige andere Klassen, und dann am Ende main()
addieren if (alwaysFalse) System.out.println(a);
oder etwas anderes, das verweist a
. Ich glaube nicht, dass ein Optimierer das jemals mit Sicherheit feststellen kann alwaysFalse
wird nie gesetzt (da einige Klassen immer Reflektion verwenden könnten, um es zu setzen); daher kann es das nicht sagen a
wird nicht mehr benötigt. Zumindest könnte diese Art von “Workaround” verwendet werden, um festzustellen, ob dies tatsächlich das Problem ist.
Nach dem wenigen, das ich weiß, gibt es ein Objekt, wenn ein Garbage Collector ein In-Scope-Objekt GCs enthält Ernst Fehler damit, weil es Objekte sammelt, die kein Müll sind. Ich vermute also, es ist etwas anderes, aber ich weiß vergleichsweise wenig über diese Art von Dingen, also ist es durchaus möglich, dass ich etwas übersehe …
– awksp
24. Juni 2014 um 0:55 Uhr
Sie haben diese Klassen als Äußere und Innere bezeichnet. Ist das Innere wirklich eine innere Klasse des Äußeren? Oder geben Sie einfach an, dass Outer einen Verweis auf Inner hat (was das Codebeispiel zu zeigen scheint)? Dies ist ein wichtiger Unterschied, denn wenn es sich um eine nicht statische innere Klasse handelt, hätte Inner einen impliziten Verweis auf Outer.
– Brett Okken
24. Juni 2014 um 0:57 Uhr
Eine kleine Begriffsklärung: Umfang ist ein lexikalisches Konstrukt der Sprache. Es ist mit dem verwandt, unterscheidet sich aber letztendlich von ihm Lebenszeit eines Objekts. Die Lebensdauer wird durch geregelt Erreichbarkeit: Wenn kein Codepfad das Objekt erreichen kann, kann es gesammelt werden. Oft wird ein Objekt, das von einer Variablen im aktuellen Gültigkeitsbereich referenziert wird, als erreichbar betrachtet, muss es aber nicht sein.
– Daniel Priden
24. Juni 2014 um 1:57 Uhr
Übrigens: mit Java 9 gibt es eine explizite Methode, um Objekte bis zum Ende eines Programms erreichbar zu halten: download.java.net/java/jdk9/docs/api/java/lang/ref/…
Reference.reachabilityFende(Object)
– eckes
20. November 2016 um 21:10 Uhr
@awksp nein, der lexikalische Umfang der Sprache/des Compilers hat nichts mit der tatsächlichen Erreichbarkeit zu tun. Ein Objekt kann nicht mehr verwendet werden, selbst wenn es sich im Zielfernrohr befindet, und daher gesammelt werden, andererseits sind Objekte normalerweise viel länger erreichbar (durch Verweilen in Stapelplätzen) als das Zielfernrohr. Letzteres sind jedoch Implementierungsdetails, auf die man sich nicht verlassen sollte (Inlining und EA ändern die Erreichbarkeitseffekte des Codes).
– eckes
20. November 2016 um 21:12 Uhr