Garbage Collection



  • Die gibt es in der JVM schon - wenn der GC ein Objekt aufräumt, wird die spezielle finalize-Methode ausgeführt. Nur bringt der Garbage-Kollektor einen bei den meisten Ressourcen nicht wirklich weiter - wenn ein Socket aus dem Scope geht, will man in aller Regel, dass die Verbindung gleich geschlossen wird und nicht erst, wenn die VM nicht mehr genug Heap-Speicher hat und den GC anschmeißt.

    Auch, wenn man die Kriterien für die Collection anders setzt - bei einem GC werden die Aufräumarbeiten generell so lange wie möglich verzögert, weil er anders nicht performant zu betreiben ist. Immerhin hat der Garbage-Collector Data-Races mit allen anderen Threads, und nach derzeitigem Stand der Forschung bedeutet das, dass jeder GC-Lauf ein Stop-the-world-Event beinhaltet. Stell dir ein Client-Server-System vor, in dem beide Seiten ihre Sockets so lang wie möglich am Leben halten, nachdem die Verbindung nicht mehr gebraucht wird.



  • Es gibt aber keinerlei Garantie, dass diese Finalizer jemals aufgerufen werden. Es existiert in Java kein zuverlässiger Mechanismus, um Resourcen garantiert freizugeben. Das Auto-Closeable Dingens ist nur eine weitere Krücke für das sinkende Schiff.

    Und wenn der GC so ein großes Problem "Stop-the-world-Event" ist, dann ist Java nicht die Sprache der Wahl für solch ein Client-Server System.



  • Da spricht der Experte.



  • 314159265358979 schrieb:

    Das neue Resource-try ist doch nur noch eine weitere Verschiebung des Problems. Die sollen mal eine ordentliche, GC-gebundene Lösung bringen.

    314159265358979 schrieb:

    Und wenn der GC so ein großes Problem "Stop-the-world-Event" ist, dann ist Java nicht die Sprache der Wahl für solch ein Client-Server System.

    Das wird die Tomcat-Leute aber überraschen. Man muss, um Java-Programme als Server zu betreiben, ein bisschen mit den JVM-Parametern aufpassen, aber benutzen kann man sie dafür durchaus. Das hängt auch damit zusammen, dass keine "ordentliche, GC-gebundene Lösung" für alle Ressourcentypen verwendet wird.

    314159265358979 schrieb:

    Es gibt aber keinerlei Garantie, dass diese Finalizer jemals aufgerufen werden. Es existiert in Java kein zuverlässiger Mechanismus, um Resourcen garantiert freizugeben. Das Auto-Closeable Dingens ist nur eine weitere Krücke für das sinkende Schiff.

    So ziemlich alle ressourcenverwaltenden Klassen in Java bieten Mechanismen, mit denen man diese wieder freigeben kann; in der Tat würde ich alle, die das nicht tun, als fehlerhaft bezeichnen wollen. Damit existiert ein zuverlässiger Mechanismus, Ressourcen garantiert freizugeben - nur halt vor Java 7 kein komfortabler, und auch in Java 7 keiner, der nachlässige oder schlecht informierte Programmierer davor schützt, Ressourcen zu leaken. Das kann man aber aufgrund (beispielsweise) der Existenz nackter Zeiger auch von C++ nicht behaupten.

    Es ist richtig, dass Java in Abwesenheit automatischer Variablen böse Probleme mit der Fehlerbehandlung hatte - es ist nicht untertrieben, Exceptions in diesem Zusammenhang als kaum benutzbar zu bezeichnen - aber die AutoCloseable-Lösung ist eine vertretbare. Vollautomatisches Aufräumen, wie man es in C++ gewohnt ist, wäre in Java selbst dann nicht einführbar, wenn man Bedenken über Rückwärtskompatibilität ignorierte, weil Objekte dort Referenztypen sind und nicht automatisch am Ende des Blocks ihre Gültigkeit verlieren müssen. Eine shared_ptr-artige Vorgehensweise bricht bei Ringstrukturen zusammen, um diesen Einwand gleich abzufangen.

    Im Übrigen rate ich dir, dich vor einer Fortsetzung dieser Diskussion über die Funktionsweise generationeller Garbage-Kollektoren zu informieren (diese sind in JVMs üblich).



  • seldon schrieb:

    Eine shared_ptr-artige Vorgehensweise bricht bei Ringstrukturen zusammen, um diesen Einwand gleich abzufangen.

    Wenn ich Ringkonstrukte hab, dann hab ich sowieso ganz andere, schlimmere Probleme 😉



  • dot schrieb:

    seldon schrieb:

    Eine shared_ptr-artige Vorgehensweise bricht bei Ringstrukturen zusammen, um diesen Einwand gleich abzufangen.

    Wenn ich Ringkonstrukte hab, dann hab ich sowieso ganz andere, schlimmere Probleme 😉

    Wie implementierst du eine doppelt verkettete Liste?



  • Michael E. schrieb:

    dot schrieb:

    seldon schrieb:

    Eine shared_ptr-artige Vorgehensweise bricht bei Ringstrukturen zusammen, um diesen Einwand gleich abzufangen.

    Wenn ich Ringkonstrukte hab, dann hab ich sowieso ganz andere, schlimmere Probleme 😉

    Wie implementierst du eine doppelt verkettete Liste?

    Nicht mit zirkulären shared_ptr!?
    Sowas wäre imo ein schwerer Designfehler. Was macht es für einen Sinn dass sich Listenelemente gegenseitig besitzen? Ihre Lebensdauer ist im Allgemeinen völlig unabhängig. Sofern es sich nicht um eine ganz merkwürdige Art von Liste handelt, klingt mir Reference Counting da von vornherein nach einer sehr schlechten Idee.
    Aber zum Glück ist die Welt ja bei shared_ptr noch lange nicht zu Ende.
    Zuallermindest gibt es weak_ptr um solche Beziehungen aufzulösen...



  • dot schrieb:

    Nicht mit zirkulären shared_ptr!?
    Sowas wäre imo ein schwerer Designfehler. Was macht es für einen Sinn dass sich Listenelemente gegenseitig besitzen? Ihre Lebensdauer ist im Allgemeinen völlig unabhängig. Sofern es sich nicht um eine ganz merkwürdige Art von Liste handelt, klingt mir Reference Counting da von vornherein nach einer sehr schlechten Idee.
    Aber zum Glück ist die Welt ja bei shared_ptr noch lange nicht zu Ende.
    Zuallermindest gibt es weak_ptr um solche Beziehungen aufzulösen...

    Und jetzt erklär mir bitte noch, wie der GC entscheiden soll, ob er weak_ptr oder shared_ptr benutzen soll 😉

    Deine Erklärung, warum shared_ptr für doppelt verkettete Listen Quatsch ist, ist richtig. Trotzdem wirst du wohl nicht behaupten, dass doppelt verkttete Listen mit ihren zirkulären Pointern Quatsch sind. Also funktioniert ein GC wohl nicht mit shared_ptr. Nichts anderes wurde behauptet.



  • Wir reden hier ja über ein Objektsystem, wie es Java besitzt, also Klassen als Referenztypen. Alle Java-Objekte haben Referenzzähler, aber diese alleine reichen nicht aus, um in allen Strukturen den tatsächlichen Todeszeitpunkt eines Objektes zu bemerken. Stell dir vor, du hättest keine nackten Zeiger, sondern nur shared_ptrs - wie baust du eine doppelt verkettete Liste und verhinderst Speicherlecks? Wie Michael richtig bemerkt, enthalten doppelt verkettete Listen massig Ringstrukturen im relevanten Sinn, und die muss man unter diesen Bedingungen anders abkanzeln.

    Java und die meisten (wenn nicht alle) anderen Sprachen mit diesem Problem lösen das durch einen Garbage-Kollektor, der alle Weile mal kuckt, ob die Objekte noch gebraucht werden. Wenn man sich auf den Standpunkt stellen kann, dass diese Verzögerung bei Speicher kein Problem darstellt (was häufig der Fall ist), funktioniert das da auch ganz gut, aber eine allgemeine Ressourcenverwaltung kann man auf diese Weise halt nicht betreiben.



  • Darum gings doch nie!? Es ging darum zu zeigen, dass es nix ausmacht dass eine shared_ptr Lösung nicht mit zirkulären Abhängigkeiten klarkommt weil das sowieso ein Designfehler wäre.
    Dass C++ nicht Java ist, ist mir klar und ich bin jeden Tag aufs Neue froh drüber 😉



  • dot schrieb:

    Darum gings doch nie!?

    Eigentlich schon :p Keiner bezweifelt den Sinn von shared_ptr in C++ und jeder weiß, welche Alternativen es gibt. Aber seldon spricht doch über GC-Strategien und dass es keinen Sinn macht, dass ein GC sich auf shared_ptr stützt.



  • Dann hab ich seldons Post falsch verstanden 😉



  • seldon schrieb:

    Wir reden hier ja über ein Objektsystem, wie es Java besitzt, also Klassen als Referenztypen. Alle Java-Objekte haben Referenzzähler, aber ...

    Ich hoffe dies ist ein Gedankenexperiment, denn dem ist nicht so...



  • Zeus: Wie erkennt der GC dann tote Objekte?



  • Ach, ich Depp. Du hast natürlich Recht, Hotspot betreibt mark-and-sweep über Eden und die Survivor-Spaces (richtig?). Ich bin da gehörig durcheinandergeraten, tut mir Leid.

    Also, auf ein neues: Javas Objektsystem wäre durch Referenzzählung allein nicht darstellbar. Letztendlich besteht das Problem, dass der Zeitpunkt, wann ein Objekt nicht mehr gebraucht wird, in diesem System nicht trivial bestimmbar ist, und der Rest der Argumentation gegen eine GC-gestützte, allgemeine Ressourcenverwaltung bleibt unverändert.



  • Java 1.5?/1.6 und 1.7 verwenden paralleles Generation-GC, sowie .NET seid 2.0 als Standard Garbage Collection. Bei Java kann man sogar den GC über eine Parameter auswechseln, deswegen kann auch eine Kopplung von Referenzzähler in das Objektmodell nicht gegeben sein, oder? In dieser Weiße arbeiten nach meinen Kenntnisstand nur Delphi, Objective C/C++ <= 2.0 und Vala - wobei es durchaus unterschiede gibt. - Evtl. verstehen wir untereinander beim Begriff "Objektmodell" auch etwas anders.

    Die toten Objekte zu erkennen ist im Gegensatz beim Referenzzählung kein triviales Angelegenheit, demnach bei jeden unterschiedlich GC-Konzept auch anders. Mit fehlt gerade auch als Beispiel nur das berühmte "Wagen-Prinzip" ein, dass ein Abgleich zwischen den gefunden Stackpointer mit deren Länge gegen ein Bereich(- welches ein Wagen symbolisiert) in ein anderen Wagen kopiert und den alten Bereich bereinigt - damit sind alle toten Bereiche(und damit auch dessen Objekte) freigibt. Bei diesen Beispiel sieht man, dass das mitführen von Referenzzähler nicht vorgesehen ist, weil nach dem Konzept ein überflüssige Information ist - aber nicht wie in den Sprachen die ich aufgezählt habe.

    GC ist für mich ein zu komplexe Thema um sie alle in eine Schublade zu stecken - immerhin gibst es wissenschaftliche Material zu Nonblocking{lockfree} Real-Time Garbage Collection - zu lesen, hoffentlich find ich die zeit für native nano.



  • Was mir bei Java/C#/... abgeht, ist die Möglichkeit einfach und elegant Shared Ownership von "disposable" Resourcen zu implementieren.
    In C++ geht das ja super-fein mit shared_ptr.

    Natürlich kann man sich in Java/C#/... auch eine Klasse basteln die nen Ref-Count für ein bestimmtes Objekt managt, und dann bei 0 eben Dispose()/close() macht. Nur ist das alles andere als elegant. Und vor allem: es fehlt ein Standard.



  • Michael E. schrieb:

    Zeus: Wie erkennt der GC dann tote Objekte?

    Wie bereits von zeus gesagt, ist das von GC zu GC unterschiedlich.

    Ich weiß, dass der Flash-GC so arbeitet, dass er den Objektgraphen entlangwandert. Das heißt, irgendwo gibt es eine Menge von Objekten, die der GC kennt und er versucht für jedes Objekt zu beweisen, dass es von einem solchen Objekt referenziert wird. Dafür macht er eine Tiefensuche im Graphen und markiert alle Objekte, die referenziert werden. Den Rest löscht er. In der Praxis bricht der GC von Flash leider ab einer bestimmten Iterationstiefe ab und dann gibts memory leaks.

    //edit eine kleine Korrektur...



  • Naja, generationell sind GCs in gemanagten Sprachen seit einer ganzen Weile eigentlich alle, aber damit ist noch nicht gesagt, wie genau die Feststellung funktioniert, dass ein Objekt tot ist.

    Soweit ich das überblicke, läuft das bei Java so:

    Das Speicherlayout des Heaps sieht vom Konzept her so aus:

    | Eden | Survivor 1 | Survivor 2 |             Tenured              |
    +------+------------+------------+----------------------------------+
    

    Dabei ist Eden die Region, in der neue Objekte angelegt werden, eine der Regionen Survivor 1 und Survivor 2 enthält Objekte, die wenige GC-Durchläufe überlebt haben, während die jeweils andere leer ist, und Tenured ist eine Region für Objekte, die schon lange leben, die selten geprüft wird.

    Ein normaler GC-Durchlauf passiert, wenn Eden vollläuft. Dieser überprüft die Objekte in Eden und dem belegten Survivor-Space, zerstört die, von denen er beweisen kann, dass sie tot sind und verschiebt den Rest in den leeren Survivor-Space bzw. nach Tenured, wenn sie inzwischen alt genug sind. Dadurch landen die lebendigen Objekte kompaktiert im vormals leeren Survivor-Space, während Eden und der vorher belegte Survivor-Raum frei werden. Dann können neue Objekte wieder in Eden angelegt werden, und der nächste GC-Durchlauf verfährt halt mit verdrehten Survivor-Spaces.

    Das Verfahren ist dann performant, wenn die meisten Objekte nur sehr kurz leben (sonst muss ja massig Zeug verschoben werden, und man hat die ganze Problematik mit anderen Threads, die die Objekte zu benutzen versuchen), deswegen wird Tenured nur selten in einem gesonderten Modus angefasst, der dann den gesamten Tenured-Bereich aufräumt und kompaktiert.

    Eine andere Frage ist, wie der GC feststellt, dass ein Objekt tot ist. Wenn ich das richtig verstanden habe (und ganz sicher bin ich mir da nicht) benutzen GCs in Java 6 und früher (fragt mich nicht wie lang früher) dafür ein nebenläufiges mark-and-sweep-Verfahren, und in Java 7 wurde etwas neues eingeführt, das ich noch nicht kenne (vermutlich eine Tri-Color-Kiste o.ä.). Hier könnte man bis zu einem gewissen Punkt womöglich mit Referenzzählern arbeiten, aber da das allein nicht ausreicht wird es wohl nicht gemacht.

    Wenn ich von "Objektmodell" spreche, meine ich in der Hauptsache, dass Objekte in Java einer Referenzsemantik folgen. In C++ ist die Sache einfach - man legt ein Objekt auf den Stack, und wenn der Stapelrahmen zerstört wird, ist auch das Objekt dahin. In Java legt man das Objekt auf den Heap, und wenn der Block endet, in dem es erstellt wurde, können an anderen Stellen schon zwanzig Referenzen auf das selbe Objekt liegen. Der genaue Zeitpunkt, wann ein Objekt zerstört werden kann, ist so nicht trivial feststellbar, und das ist ein Problem, wenn man (und durch diesen Vorschlag kamen wir ja in die Diskussion) Ressourcenverwaltung allgemein (also nicht nur Speicher) durch den GC erledigen lassen will.



  • Das Prinzip nennt sich Generational Garbage Collection.

    Gibt es einen bestimmten Grund den Generationen putzige aber sinnlose Namen wie Eden, Survivor, Tenured zu geben?
    Hab' ich noch nie gehört, und finde ich auch reichlich doof.

    Generation X reicht doch vollkommen (pun intended).

    Im Endeffekt ist Generational Garbage Collection aber bloss eine Optimierung, und nicht nötig um das Prinzip bzw. die Limitierungen eines GC zu erklären - ob nun Mark & Sweep oder anders.

    Daher kann ich mich grad des Eindrucks nicht erwähren, dass du hier ein bisschen einen auf wichtig machst 🤡


Anmelden zum Antworten