Kann Java ein Objekt finalisieren, wenn es sich noch im Gültigkeitsbereich befindet?

Lesezeit: 8 Minuten

Kann Java ein Objekt finalisieren wenn es sich noch im
Michael Anderson

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.

  • 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


Kann Java ein Objekt finalisieren wenn es sich noch im
Stuart Mark

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.

  • Die Pedanterie stimmt genau mit dem überein, worum ich gebeten habe – Ihre Verwendung des Bereichs entspricht meiner und meiner “a wird erkannt, dass keine Referenzen vorhanden sind” stimmt mit Ihrem “erreichbar” überein.

    – Michael Anderson

    24. Juni 2014 um 7:21 Uhr

  • OK, wenn Pedanterie passt, umso besser. Es zahlt sich aus, in diesem Geschäft pedantisch zu sein. 🙂 Einige der Kommentatoren (mit der bemerkenswerten Ausnahme von Daniel Pryden) verwechseln Geltungsbereich mit Erreichbarkeit oder gehen davon aus, dass im Geltungsbereich Erreichbarkeit impliziert. Ich habe sogar mit diesem Phänomen vertraute Leute sagen hören, dass eine lokale Variable nach ihrer letzten Verwendung in einem Block den Gültigkeitsbereich verlässt, was sicherlich nicht stimmt.

    – Stuart Marks

    24. Juni 2014 um 7:45 Uhr

  • Die toString() Aussage weckt meine Zweifel. Seit toString() Implementierungen sind normalerweise frei von Nebeneffekten, der Aufruf kann vollständig entfernt werden, wenn sein Ergebnis nicht verwendet wird, und JLS §12.6.1 schlägt vor, dass solche Optimierungen zu einer früheren Erfassung führen dürfen. Beachten Sie außerdem, dass ohne Synchronisation, selbst wenn das Ergebnis wie in der print-Anweisung des Beispiels verwendet wird, keine längere Lebensdauer garantiert wird. Ohne passiert-vorher Beziehungen, sieht der Finalizer-Thread möglicherweise eine andere Reihenfolge der Aktion des Haupt-Threads, dh Abschluss der toString() Aufruf vor dem Abschluss der Schleife.

    – Holger

    14. September 2016 um 16:42 Uhr

  • @Holger Genau genommen hast du recht, toString() bietet keine vollständigen Garantien zur Verhinderung von GC. Für die meisten Zwecke sollte es aber ausreichen. In Java 9 gibt es eine neue API, die eine solche Garantie bietet: Reference.reachabilityFence.

    – Stuart Marks

    5. Juli 2017 um 21:59 Uhr

  • @RFST war es nie und ist immer noch nicht garantiert. Alles, was der spezifische Fehlerbericht anspricht, sind die unsichtbaren Variablen einer for-each-Schleife, die Iteratoren enthalten, die verhindern können, dass die Sammlung von Garbage Collection erfasst wird. Wie diese Antwort zeigt, gilt dies normalerweise nicht für optimierten Code. Eigentlich finde ich es seltsam, dass jetzt javac fügt zusätzliche Anweisungen in jeden Code ein, um diesen einzelnen, sehr spezifischen Eckfall zu adressieren, wenn sich das allgemeine Verhalten (hängende lokale Variablen können immer noch existieren) nicht geändert hat.

    – Holger

    18. September 2018 um 6:15 Uhr

1646315229 767 Kann Java ein Objekt finalisieren wenn es sich noch im
ajb

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.

  • Sie können das nicht anhand des Bytecodes erkennen, da die Transformation wahrscheinlich in der JIT-Optimierung stattfindet.

    – user2357112 unterstützt Monica

    24. Juni 2014 um 1:09 Uhr

  • Nebenbemerkung: Beachten Sie, dass dies nicht der Fall ist public static **final** boolean alwaysFalse sonst wäre es a Kompilierzeitkonstante und daher zur Kompilierzeit inline (der Compiler würde keinen Code für die if Block, gemäß JLS)

    – ignis

    6. Juli 2015 um 7:15 Uhr

  • Ein Optimierer muss das nicht erkennen alwaysFalse ist noch nie gesetzt, alles, was es wissen muss, ist, dass es nicht zwischen der Konstruktion von gesetzt ist A und der Test in Programmreihenfolge. Da die Variable nicht deklariert ist volatile, muss der optimierte Code keine Aktualisierungen bemerken, die von anderen Threads vorgenommen wurden, weder direkt noch über Reflection. Aber selbst wenn es deklariert wurde volatile wir sollten niemals Code schreiben, der sich auf eine angenommene Unfähigkeit des Optimierers stützt.

    – Holger

    14. September 2016 um 16:19 Uhr


  • @Holger ja, deshalb habe ich die Wörter “möglich” und “hässlich” in meine Antwort aufgenommen …

    – ajb

    15. September 2016 um 3:17 Uhr

  • Es ist zuverlässig zu bedienen Reference.reachabilityFence(a) in Java9 am Ende von main, aber es ist immer noch hässlich, wenn so etwas benötigt wird.

    – eckes

    20. November 2016 um 21:16 Uhr

924000cookie-checkKann Java ein Objekt finalisieren, wenn es sich noch im Gültigkeitsbereich befindet?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy