Definitive Quelle fuer die Lebenszeit von Java Objekten?

Robert Zenz

Top Contributor
Es hat sich die Frage ergeben nach welchen Regeln Java Objekte am Leben bleiben, also die Zeit bis sie fuer die Garbage Collection freigegeben und dann eingesammelt werden. Das offensichtliche ist natuerlich solange die Instanz eine Verbindung mit einem GC-Root Objekt hat wird es nicht eingesammelt. Innerhalb eines Methoden Koerpers haelt der Methoden-Aufruf selbst die Referenz. Also zum Beispiel:

Java:
public void test(Parameter parameter) {
    // parameter lebt zumindest bis zum Ende der Funktion.
    
    Etwas etwasInstanz = new etwasInstanz(); // Lebt bis zum Ende der Methode.
    
    new EtwasInstanz(); // Kann sofort eingesammelt werden nachdem der Konstruktor von EtwasInstanz durch ist.
}

Zu diesen Regeln habe ich aber keine feste Aussage oder Quelle gefunden. Es gibt viele Antworten auf Stack Overflow und anderen Seiten, aber keine davon hat eine Quellenangabe. In der JLS und der JVMS habe ich nichts dazu gefunden, die behandeln augenscheinlich die GC und Lebenszeit von Objekten gar nicht. Die Buecher die ich habe (Java ist auch eine Insel 8, Java Core Programmierung, Concurrency in Practice) enthalten da auch keine konkreten Aussagen.

Hat da jemand eine definitive Quelle fuer das Verhalten? Oder ist das ein Implementierungsdetail? Habe ich das irgendwo uebersehen?
 
Ich fürchte, dass es schwer ist, hier klare Aussagen zu treffen. Aus meiner Sicht ist hier halt wichtig:
  • Es hängt immer vom verwendeten GC ab. So gibt es z.B. auch einen GC, der nie etwas bereinigt (Epsilon GC).
  • Die übliche Aussage, die man hier findet, ist daher oft nur das, was Du oben auch schon etwas angedeutet hast: Ab wann könnte ein GC einen Speicherbereich freigeben? Da wird halt nur sehr oberflächig geschaut.
Wenn man es ganz genau wissen will, dann hilft nichts und man muss sich dann den konkreten GC im Detail anschauen: Ab Java 9 dürfte das dann der G1 sein.

Wenn man die Details wissen will: Was bezeichnest Du z.B, als "Lebenszeit" eines Objektes?
  • Die Erreichbarkeit von irgend einem Prozess? Das wäre dann unabhängig von dem GC sobald es keine gespeicherte Referenz mehr gibt. Dann sind die Objekte nur noch Müll, der irgendwo im Speicher liegt unabhängig davon, ob diese noch als "live" oder als "garbage" markiert sind.
  • Wenn es um die Markierung geht: Die wäre gesetzt nach einem Scan der GC Root. Alle Objekte, die ausgehend von den GC roots nicht erreicht werden können, sind als Garbage markiert.
  • Wenn es um die aktive Speicherfreigabe geht: Da wird es dann komplexer. G1 unterteilt den Speicher in Regionen und die aktive Freigabe einer Region erfolgt erst, wenn es sich "lohnt". Also wenn genug nicht mehr "live" ist, dann lohnt es sich, ggf. "live" Reste zu verschieben um dann den ganzen Block als frei zu markieren. Aber auch jetzt ist das Objekt ja noch im Speicher - auch wenn der Bereich als frei gekennzeichnet ist. Wirklich weg ist es ja erst, wenn es durch neue Daten überschrieben wurde.

Hier wäre meine Frage: an welcher Stelle wäre es für Dich wichtig? Du schreibst ja ein Programm. Vom Verständnis der Logik her reicht der erste Punkt: Habe ich (das eigentliche Programm) noch Zugriff auf ein Objekt? Die nachfolgenden Dinge sind eher nebensächlich. das ist eine Black Box und es ist eigentlich eher nebensächlich. Zumal ich darauf wenig Einfluss habe, denn letztendlich kann ich nicht wirklich beeinflussen, wie meine Anwendung dann gestartet wird. Nach der Auslieferung kann ja beim Start ein -XX:+UseSerialGC angegeben werden oder so.

Aber wenn man die genauen Details haben will: Oracle hat da einiges zu dokumentiert, u.a. die "HotSpot Virtual Machine Garbage Collection Tuning Guide" welche auch G1 im Detail beschreibt:

Oracle liefert hier viele gute Informationen, die hoffentlich ausreichen. Darüber hinaus wäre sonst vermutlich das jdk selbst ein guter Anlaufpunkt (src/hotspot/share/gc/g1/) wenn man die letzten Details wissen will.
 
Es hängt immer vom verwendeten GC ab. So gibt es z.B. auch einen GC, der nie etwas bereinigt (Epsilon GC).
Das ist nicht ganz was ich meinte, ob eine Instanz eingesammelt werden kann bestimmt nicht der GC, soweit ich weisz. Oder falls doch, waere eine Quelle dafuer nett, weil genau diese Aussagen und Quellen suche ich.

Was bezeichnest Du z.B, als "Lebenszeit" eines Objektes?
Als "Lebenszeit" bezeichne ich hier die Zeitspanne von der Allokierung der Instanz bis zu dem Moment wo sie fuer den GC freigegeben wird und eingesammelt werden kann, nicht wird.

Hier wäre meine Frage: an welcher Stelle wäre es für Dich wichtig?
Mir geht es rein um die Regeln, wie zum Beispiel in meinem Beispiel oben. Wer bestimmt wie lange die Instanz wo nur der Konstruktor aufgerufen wurde lebt? Wer bestimmt wie lange die Instanz die zwar eine Referenz hat, aber nicht mehr verwendet wird, lebt? Fuer diese Regeln (und aehnliche Randfaelle) suche ich eine konkrete Aussage wo das festgehalten ist.

Bis jetzt war meine Annahme dass dies durch einen gesonderten beziehungsweise geregelten Mechanismus erfolgt und nicht vom jeweiligen GC bestimmt wird. Vielleicht ist die Annahme auch einfach falsch, das wuerde nicht ausschlieszen.
 
Das ist nicht ganz was ich meinte, ob eine Instanz eingesammelt werden kann bestimmt nicht der GC, soweit ich weisz. Oder falls doch, waere eine Quelle dafuer nett, weil genau diese Aussagen und Quellen suche ich.
Das ist doch die Phase, die hier als Marking bezeichnet wird:

Marking​

G1 marking uses an algorithm called Snapshot-At-The-Beginning (SATB) . It takes a virtual snapshot of the heap at the time of the Initial Mark pause, when all objects that were live at the start of marking are considered live for the remainder of marking. This means that objects that become dead (unreachable) during marking are still considered live for the purpose of space-reclamation (with some exceptions). This may cause some additional memory wrongly retained compared to other collectors. However, SATB potentially provides better latency during the Remark pause. The too conservatively considered live objects during that marking will be reclaimed during the next marking. See the Garbage-First Garbage Collector Tuning topic for more information about problems with marking.

Es ist also Bestandteil vom GC. Macht auch Sinn, denn von GC zu GC ist ja unterschiedlich:
  • Wann wird was gemacht?
  • Wie genau wird vorgegangen?

Das Marking ist also dafür verantwortlich, dass Objekte als "live" markiert werden. (Für das generelle Verständnis scheint das unerheblich, aber es gibt halt keine Markierung, dass ein Objekt eingesammelt werden kann oder eine Entscheidung: "Diese Instanz wird nicht mehr benötigt". Es gibt statt dessen genau das Gegenteil: Es wird festgestellt: Diese Instanz kann noch erreicht werden (vom GCRoot).

Im Source findet sich das bei g1 in z.B. g1ConcurrentMark[.cpp, .hpp]:

Das macht ja auch Sinn, denn das Marking ist ja das eigentlich teure. Wenn dies immer stattfinden würde, dann wäre es problematisch, denn es gibt ja hier unterschiedliche GC für unterschiedliche Szenarien. Extremes Beispiel ist der epsilon GC (NO OP GC), der ja gar nichts in der Beziehung macht um eben keinerlei Laufzeit zu verschwenden. Es gibt also nur eine Speicher Belegung auf dem Heap und nie eine Freigabe.
 
Das Spannende an der Spezifikation der Sprache Java und der Spezifikation der JVM ist gerade, dass das, was nicht beschrieben ist, eben nahezu willkürlich implementiert werden kann -- solange es im Rahmen der Spezifikation ist. Spec sagt:
The Java Virtual Machine assumes no particular type of automatic storage management system …
Fun Fact: Epsilon GC ist ein No-Op-Garbage-Collector und ein Storage-Manager, der niemals unerreichbare Objekte zurückgewinnt.
Interessanterweise gilt das Gleiche auch für Threads; auch hier ist die Spezifikation sehr weich, was überhaupt existieren muss von der Laufzeitumgebung.
Man muss also immer ganz genau die JVM-Implementierung anschauen, um solche Details herauszufinden.
 

Neue Themen


Zurück
Oben