GC-Minimierung in Java

vorhergehende Artikel in: Java
05.11.2017

Auf meinen Fahrten von und zur Arbeit schweife ich hin und wieder gedanklich ein wenig ab. Hier das Ergebnis meiner jüngsten Gedankenexpedition...

Als ich vor inzwischen vielen Jahren in einem Projekt mit VxWorks arbeiten durfte funktionierte alles toll - bis zum ersten Langzeitversuch: Die Analyse der Messwerte dauerte immer länger bis sich die Analysedauer irgendwann asymptotisch einem Höchstwert annäherte.

Das geschah über mehrere Tage hinweg - der Anstieg geschah also sehr langsam, dennoch war das Erreichen der Asymptote bereits zu einem Zeitpunkt erreicht, der weit unterhalb der geplanten durchschnittlichen Missionsdauer lag. Die für die Analyse benötigte Maximalzeit (der asymptotisch erreichte Wert) lag deutlich über dem in der Systemspezifikation vorgegebenen.

Wir mussten also Änderungen vornehmen und dafür mussten wir herausfinden, wo?

Nach längeren Untersuchungen erkannten wir, dass die Speicherverwaltung für unser Board Support Package als einfach verkettete Liste implementiert war und der Speicher während der Laufzeit mehr und mehr fragmentiert wurde - damit wurde die Zeit zum Finden eines passenden Speicherblocks natürlich immer länger, was sich letztlich auf die Analysedauer niederschlug.

Wir waren über diese Diagnose extrem überrascht, denn wir waren eigentlich der Meinung, dass wir dem Leitsatz harter Echtzeitanforderungen gefolgt waren: Beim Start des Systems wird einmal der Arbeitsspeicher aufgeteilt und allokiert, danach startet die eigentliche Arbeit und währenddessen ist Speicher allokieren oder frei geben des Teufels.

Dies wurde durch intensive und gründliche Codereviews sichergestellt - aber woher kam die Fragmentierung und das ständige neu Allokieren? Letztlich fanden wir, dass die STD-Library verantwortlich war: Wir nutzen verkettete Listen und einige andere Datenstrukturen der STDLib und diese benutzen ihr eigenes Speichermanagement, das fröhlich dynamisches Speichermanagement betrieb. NAchdem wir diese wieder ausgebaut hatten (eine undankbare Aufgabe!) war das Performanceproblem gelöst.

Daraufhin kam mir der Gedanke, ob dieses Paradigma: Am Anfang allokieren und dann nur noch mit bestehenden Speicherbereichen (Objekten) interagieren auch in Java möglich wäre?

Es existieren ja bereits diverse Object-Caches, auch ich habe solche für Threads mit Erfolg erstellt und eingesetzt. Weitere übliche Verdächtige sind Connection-Pools für die Kommunikation mit Datenbanken.

Aber funktioniert das für alle (irgendwelche) Objekte? folgende Überlegungen dazu:

  • Einen Objektstore für Objekte einer beliebigen Klasse zu bauen ist trivial: Er muss ja ketztlich nur eine Methode zur Herausnahme eines Objektes und eine zum Zurücklegen anbieten. Einen zu bauen für Objekte verschiedener beliebiger Klassen ist nicht wesentlich schwieriger: Man kann dazu als Grundlage die in einem früheren Artikel vorgestellte Mechanik nutzen.
  • Aber beim Herausnehmen eines Objektes müsste man sicherstellen, das man ein vollig neuwertiges erhält - etwaige Änderungen des letzten Nutzers (der es wieder zurückgelegt hat) dürfen den neuen Nutzer nicht beeinflussen.
  • Dazu wäre es hervorragend, wenn man die Methode <init> per Reflection aufrufen könnte. Aber erstens geht das nicht und zweitens - siehe nächster Punkt
  • Java-Objekte sind per se keine Invarianten ohne Seiteneffekte: Man stelle sich ein Objekt vor, das in seinem Konstruktor einen Thread startet und keine Instanzvariable zur Speicherung einer Referenz auf ihn vorsieht - komplett legaler Java-Code, allerdings hat man keine Möglichkeit, diesen Thread wieder einzufangen oder zu stoppen. Würde man dieses Objekt wieder in den Objekt-Cache zurücklegen, wäre die Semantik unklar: soll der Thread damit auch aufhören zu existieren? Soll dafür ein Automatismus greifen?
  • Am vorhergehenden Beispiel sind zweierlei Aspekte deutlich zu erkennen: Java ist nicht darauf vorbereitet ein solches Paradigma zu unterstützen und man müsste für alle Objekte, die über einen solchen Objekt-Cache verwaltet werden sollten, eine gemeinsame Semantik fordern: nach dem Zurücklegen muss der Objekt-Cache in der Lage sein, eine Reset-Methode an den Objekten aufzurufen, die folgender Semantik genügen muss: Alle Seiteneffekte der Konstruktion des Objektes sind rückgängig zu machen. Der interne Zustand des Objektes darf von dem eines frisch erzeugten nicht unterscheidbar sein. Der interne Zustand nach Aufruf einer beliebigen Methode mit beliebigen Parametern darf sich für ein frisch aus dem Objektstore geholtes Objekt und ein neu erzeugtes nicht unterscheiden.
  • Als ich mit meinen Gedanken an diesem Punkt angekommen war, stellte ich den Nutzeffekt einer tatsächlichen Implementierung und Nutzung in Frage: Wenn man wirklich nur darauf vorbereitete Objekte mit solchen Objekt-Caches verwalten kann - dann ist es eigentlich unmöglich, dadurch messbare Performancegewinne zu erzielen: denn der eigene Code arbeitet mit den Java-Bibliotheken und diversen Third-Party-Bibliotheken zusammen, die alle nicht auf diese Art und Weise versuchen, den Garbage Collector zu entlasten.
Alles in allem also ein interessantes Gedankenexperiment aber auch nicht mehr.

Aktualisierung vom 5. November 2017

Und doch kommt hier noch ein Nachsatz: Als ich neulich wieder einmal ein Loblied auf die funktionale Programmierung lesen musste (etwas, das mir durch Vorlesung und Seminar in meinem Studium gründlich verleidet wurde), blieb mein Auge an der Formulierung der Tatsache hängen, dass ein Grundbestandteil des funktionalen Paradigmas die grundsätzliche Unveränderlichkeit sämtlicher Objekte ist - Mit anderen Worten: Es werden nie Objekte geändert, sondern maximal neu erzeugt.

Als jemand, der da nicht so genau drinsteckt, würde mich interessieren, ob es bei den funktionalen Sprachen, die es für die JVM gibt, einen speziellen Garbage-Collector-Modus gibt. Ich kann mich nämlich noch sehr genau erinnern was passiert ist, als ich eine eigene Softwarelösung zur Visualisierung dreidimensionaler Daten schaffen wollte: Bei jeder mathematischen Operation mit 3D-Vektoren wurde ein neues Objekt erzeugt, das das Ergebnis enthielt. Bei der 3D-Visualisierung sind unheimlich viele solcher mathematischen Operationen durchzuführen. Die JVM überwacht das Verhältnis der Zeit, die im Garbage Collector verbracht wird zu der Zeit, die die Laufzeitumgebung mit der Abarbeitung des eigentlichen Programmes zubringt. Wird dieses zu ungünstig , wird das Programm mit einer Exception beendet.

Daher würde mich wirklich interessieren, ob funktionale JVM-Sprachen dieses Problem gelöst haben oder aber nur ganz spezielle Algorithmen damit umgesetzt werden können - eben solche, die nicht zu viel tun (und damit die Erzeugung neuer Objekte minimieren). Das ist schon fast ein Anreiz, mich doch noch einmal mit funktionaler Programmierung uz befassen...

Artikel, die hierher verlinken

Lambda Expressions vs. anon. Inner Classes

13.01.2019

Angefangen hat alles mit der Information, dass ein Meetup hier in der Nähe zum Thema "Java upside down: funktionale Programmierung in Java mit Vavr"

GC und beliebige Präzision in Java

24.11.2018

Das letzte Mal, als ich über Vor- und Nachteile der Durchführung mathematischer Berechnungen mit verschiedenen Datentypen schrieb, vernachlässigte ich einen Aspekt, dessen Einfluss und Wichtigkeit von der benutzten Programmiersprache und konkreten Implementierung abhängt:

Alle Artikel rss Wochenübersicht Monatsübersicht Codeberg Repositories Mastodon Über mich home xmpp


Vor 5 Jahren hier im Blog

  • Multi-User-WebDAV, Docker, GitHub

    17.11.2019

    Nachdem ich mich in letzter Zeit verstärkt mit Docker und dem zugehörigen Ökosystem beschäftige, habe ich begonnen, verschiedenste Dienste in Containern zu testen um zu sehen, ob in manchen Fällen LXC oder KVM nicht doch die bessere Wahl wäre...

    Weiterlesen...

Neueste Artikel

  • Migration der Webseite und aller OpenSource Projekte

    In eigener Sache...

    Weiterlesen...
  • AutoHideToolbar für Java Swing

    Ich habe eine neue Java Swing Komponente erstellt: Es handelt sich um einen Wrapper für von JToolBar abgeleitete Klassen, die die Werkzeugleiste minimieren und sie nur dann einblenden, wenn der Mauszeiger über ihnen schwebt.

    Weiterlesen...
  • Integration von EBMap4D in die sQLshell

    Ich habe bereits in einem früheren Artikel über meine ersten Erfolge berichtet, der sQLshell auf Basis des bestehenden Codes aus dem Projekt EBMap4D eine bessere Integration für Geo-Daten zu spendieren und entsprechende Abfragen, bzw. deren Ergebnisse auf einer Kartenansicht zu visualisieren.

    Weiterlesen...

Manche nennen es Blog, manche Web-Seite - ich schreibe hier hin und wieder über meine Erlebnisse, Rückschläge und Erleuchtungen bei meinen Hobbies.

Wer daran teilhaben und eventuell sogar davon profitieren möchte, muss damit leben, daß ich hin und wieder kleine Ausflüge in Bereiche mache, die nichts mit IT, Administration oder Softwareentwicklung zu tun haben.

Ich wünsche allen Lesern viel Spaß und hin und wieder einen kleinen AHA!-Effekt...

PS: Meine öffentlichen Codeberg-Repositories findet man hier.