Eine visuelle Zeitreise durch die Entwicklung des JDK

Richard Gross

Das Java Development Kit (JDK) hat seit seiner Entstehung erhebliche Veränderungen durchlaufen. Von den Anfängen unter dem Motto “Write once, run everywhere”, über die Applet-Kriege, die erste Veröffentlichung des OpenJDK bis hin zum neuen Release-Zyklus, der uns mittlerweile seit acht Jahren begleitet – in 30 Jahren ist viel passiert.

Der Großteil dieser Reise ist – zumindest aus heutiger Sicht – eher trocken dokumentiert: in Textform. Dort steht, wie viele Features hinzugefügt, geändert oder (ganz selten) entfernt wurden.

Was dort aber nicht steht: wie sich das auf den Code ausgewirkt hat. Welche architektonischen Veränderungen waren entscheidend? Welche Details wurden verfeinert? Und wie wurde mit der Komplexität einer so großen Codebasis überhaupt umgegangen?

Wir können diese Reise visualisieren, indem wir das JDK abbilden. Es gibt mehrere Tools, die das ermöglichen. Für diesen Artikel verwenden wir CodeCharta, da es kostenlos, Open Source und seit 2017 aktiv gepflegt ist. Außerdem kenne ich es sehr gut, da ich an seiner Entwicklung mitgewirkt habe.

Die Codebasis, die wir analysieren, ist das OpenJDK. Es ist seit Version 10 (2018) die Referenzimplementierung von Java SE und seit Version 16 (2021) auf GitHub verfügbar. Das erste OpenJDK war Version 7 (2011), aber wir können deutlich weiter zurückblicken – der erste Commit stammt vom 1. Dezember 2007.

Zurück bis JDK 1 (1996) reicht das allerdings nicht.

Ich selbst bin weder Mitglied noch in irgendeiner Form mit den Entwicklerinnen und Entwicklern des JDK verbunden. Die meisten Erkenntnisse basieren auf textlicher Dokumentation, fundierten Annahmen – und einer hilfreichen Visualisierung.

JDK 7

Mit CodeCharta können wir das JDK als 3D-Stadtkarte visualisieren. Jede Datei wird zu einem Gebäude. Die Größe des Gebäudes (✥) steht für die realen Codezeilen (RloC) – also jene Zeilen, die keine Kommentare oder Leerzeilen sind und die wir tatsächlich lesen müssen, um den Code zu verstehen.

In der folgenden Karte sind Pakete, die zu sun gehören, lila markiert, Java-Standarderweiterungen (wie Swing) in cyan und Standard-Java in blau. Ein roter Farbton wurde den häufig verwendeten Paketen java.lang, java.nio und java.util zur besseren Größenvergleichbarkeit zugewiesen. Zusätzlich werden nur Dateien farblich hervorgehoben, deren Namen das Schlüsselwort *test* enthalten.

JDK 7 (gefiltert nach *test*)

Schon 2011 ist Java riesig und beinhaltet 3,4 Millionen Codezeilen.

Das Testverhältnis ist allerdings eher bescheiden: Das Paket jdk/test enthält nur 283.000 Zeilen, also etwa 12 % des gesamten Codes. Spätere JDK-Versionen werden hier deutlich höhere Anteile zeigen.

Auffällig ist außerdem der große Umfang des sun-spezifischen Codes, dessen Existenz bis heute für Herausforderungen bei der Pflege des JDK sorgt.

Und schließlich sehen wir auch einen Ordner mit dem Namen hotspot. Dabei handelt es sich um die Java Virtual Machine (JVM), die den Java-Code ausführt und damit das Versprechen “write once, run anywhere” einlöst. Sie ist größtenteils in C++ geschrieben und seit Version 1.3 im Jahr 2000 die Standard-VM.

JDK 7 sollte ursprünglich zwei große Neuerungen enthalten: anonyme Funktionen (also Lambdas) und eine modularisierte Struktur. Doch 2010 wurde ein plan B beschlossen, um zunächst eine abgespeckte Version von JDK 7 zu veröffentlichen – ein Release mit eher wenigen neuen Features. Deshalb konzentriert sich die Visualisierung hier hauptsächlich auf die grobe Struktur.

JDK 8

JDK 8, veröffentlicht im Jahr 2014, ist deutlich gewachsen – auf 5,2 Millionen Codezeilen. Wir haben etwas näher herangezoomt, um zwei neu hinzugekommene Features genauer zu betrachten.

JDK 8 (herangezoomt)

Die 10.000 Zeilen von Stream (rötlich eingefärbt), die funktionalen Interfaces für anonyme Funktionen (373 Zeilen, ein kleiner roter Fleck rechts neben dem lila Block) und java.time (19.000 Zeilen) sind die Features, auf die wir hier blicken. Interessant dabei: Die Tests für Stream stehen in einem fast 1:1-Verhältnis zum eigentlichen Code (11.000 Zeilen), während java.time deutlich mehr Tests benötigt – ganze 45.000 Zeilen. Für mich zeigt das vor allem eines: wie kompliziert Zeit ist. Die hohe Anzahl der Testfälle müssen unglaublich viele Sonderfälle und Randbedingungen abdecken.

Diese Features zeigen auch die Grenzen unserer Analysemöglichkeiten auf. Die Einführung anonymer Funktionen im JDK bedeutete weit mehr, als nur 373 Zeilen funktionale Interfaces zu schreiben – doch die dafür notwendigen Änderungen sind viel weiter verteilt und ohne internes Wissen kaum sichtbar.

Was wir sehen, ist nur die Oberfläche. Die eigentliche Komplexität liegt tiefer verborgen im Compiler, in der JVM und in vielen kleinen Stellschrauben im bestehenden Code.

JDK 9+10

JDK 9, veröffentlicht im September 2017, wuchs erneut und erreichte 7,3 Millionen Codezeilen. Das ambitionierteste Feature war die Einführung des JPMS (Java Platform Module System) – und mit ihm die Modularisierung des gesamten JDK. Diese veränderte die Struktur grundlegend.

Einige Umstrukturierungen, insbesondere im Zusammenhang mit der HotSpot-VM, wurden jedoch nicht in Version 9 abgeschlossen, sondern auf die nächste Version verschoben. Passend vielleicht, denn JDK 10 brachte eine besondere Änderung mit sich, die “Konsolidierung des JDK-Quellcodes in ein einziges Repository.”. Da die Modularisierungsstruktur ab JDK 10 (März 2018) als stabil gilt, konzentrieren wir unsere nächste Visualisierung auf diese Version.

JDK 8+10 mit respektiver Ordnerstruktur

Mit 7,2 Millionen Zeilen ist Version 10 zwar etwas kleiner als Version 9, aber immer noch 2 Millionen Zeilen größer als JDK 8.

Der Großteil dieses Wachstums entfällt allerdings auf Tests: Sie machen nun 29 % der gesamten Codebasis aus. Dieses Verhältnis ist deutlich erkennbar – Tests sind mittlerweile zu einem Top-Level-Thema geworden.

Noch spannender als das Wachstum im Test-Bereich ist allerdings das src-Verzeichnis. Was früher ein großer Monolith war, wurde nun in 20 java.-Module und 37 jdk.-Module aufgeteilt. Diese Module wurden thematisch gruppiert.

UI-Frameworks wie AWT und Swing sind jetzt im Modul java.desktop gebündelt. Es gibt außerdem ein Modul java.base, das ausschließlich den Code enthält, auf den alle anderen Module angewiesen sind. Dazu gehören unter anderem Collections, Math, time und I/O (Input/Output).

Sogar interne JDK-Bestandteile wurden in eigene Module ausgelagert. Die Nashorn-JavaScript-Engine (ja, dieses JDK kann ECMAScript 5.1 ausführen) ist eines dieser Module. Auch der Java-Code, den HotSpot selbst verwendet, wurde in Module wie jdk.hotspot oder jdk.internal.vm.compiler überführt. Zuvor befand sich dieser Code im allgemeinen hotspot-Paket. Die verbliebenen 738.000 Zeilen im hotspot-Verzeichnis stammen aus C++ – also dem nativen Kern der virtuellen Maschine.

Aber diese Restrukturierung ist nur eine Auswirkung des Modulsystems. Die relevante Auswirkung und der eigentliche Grund vom genutzten Modulsystem ist, dass die internen Bestandteile der neuen Module von außen nicht mehr zugänglich sind. Für die Modulentwickler bedeutete das: Sie mussten lediglich sicherstellen, dass die öffentlich bereitgestellte API stabil bleibt. Alles andere – also die internen Implementierungsdetails – konnten sie frei ändern, ohne befürchten zu müssen, dass dadurch fremder Code bricht.

Mancher Code konnte allerdings nicht vor der Öffentlichkeit verborgen werden – auch wenn sich die JDK-Maintainer das gewünscht hätten. Eine Datei sticht dabei besonders hervor: die Klasse sun.misc.Unsafe. Sie ermöglicht es, performancekritischen, aber unsicheren Code zu schreiben – und ist bis heute Teil des JDK. Der Versuch, sie durch sichere Alternativen zu ersetzen, ist nach wie vor eine Herausforderung. Immerhin: Mit JDK 23 (September 2023) wurden 79 der 87 Methoden als @Deprecated markiert.

Die vollständige Entfernung von Unsafe ist Teil einer größeren Initiative, die von den Entwicklerinnen und Entwicklern als “Integrity by default” bezeichnet wird. Die dahinterstehende Idee: “Entwickler erwarten, dass ihr Code und ihre Daten vor ungewollter oder unkluger Nutzung geschützt sind.”

JDK 11

JDK 11 wurde im September 2018 veröffentlicht. Es und seine beiden Vorgänger sind Teil des neuen Release-Zyklus, bei der alle sechs Monate ein neues JDK erscheint. Anstatt JDK-Versionen an bestimmte Features zu koppeln und endlose Meetings über deren Fortschritt (oder Stillstand) zu führen, folgen die Maintainer nun einem Release-Train-Modell: Alle sechs Monate fährt der Zug ab. Was bis dahin fertig ist, wird veröffentlicht. Wenn ein Feature nicht rechtzeitig bereit ist – kein Problem. In sechs Monaten kommt der nächste Zug.

Mit jeder neuen JDK-Version endet der Support für die vorherige. Doch ein Upgrade alle sechs Monate ist nichts, was große Unternehmen gerne tun. Sie bevorzugen eine stabile Feature-Basis, die nur noch Sicherheitsupdates und Bugfixes erhält – keine potenziell riskanten Neuerungen. An dieser Stelle kommen die sogenannten Long-Term-Support-(LTS)-Versionen ins Spiel. Alle paar Jahre erscheint ein JDK, das über mehrere Jahre hinweg gepflegt wird – mit Korrekturen, aber ohne neue Funktionen. Die JDK-Maintainer nennen das das tip & tail-Modell – und empfehlen, dass auch Bibliotheksentwickler diesem Ansatz folgen sollten.

JDK 11 ist eine solche LTS-Version. Der kostenlose Support von Oracle endete im September 2023 – also fünf Jahre nach der Veröffentlichung.
Andere Anbieter, wie zum Beispiel Adoptium, bieten weiterhin kostenlosen Support an. Das gibt Unternehmen ausreichend Zeit, eine stabile LTS-Version im Einsatz zu behalten, bevor ein Upgrade auf die nächste erforderlich wird.

Aus Sicht der Funktionen werden LTS-Releases nicht anders behandelt als Short-Term-Releases. Beide folgen dem Prozess der JDK Enhancement Proposals (JEP). Auch in LTS-Releases müssen Verbesserungen daher nicht final sein. Sie können auch folgendes sein:

  • Experimental: Mechanismus zum Testen und Sammeln von Feedback zu nicht-trivialen HotSpot-Erweiterungen. Experimental-JEPs müssen einzeln über die JVM-Option -XX:+UnlockExperimentalVMOptions aktiviert werden.
  • Incubating: Noch nicht finale API oder ein Tool, das dazu dient, frühes Feedback einzuholen. Inkubations-JEPs müssen einzeln aktiviert werden über die Optionen --enable-preview --add-modules jdk.incubator.xyz.
  • Preview: API oder Tool, das final werden soll, bei dem aber noch Feedback gesammelt werden soll. Preview-JEPs lassen sich nicht einzeln aktivieren, sondern nur gesammelt über die JVM-Option --enable-preview.

Da LTS-Releases nicht anders behandelt werden als Nicht-LTS-Versionen, können auch sie nicht-finale Erweiterungen enthalten. So auch bei Version 11: Sie enthält den experimentellen Garbage Collector ZGC (kurz “Z”). Die folgende Karte zeigt den neuen Garbage Collector – aber auch einige Code-Entfernungen. Denn ein JEP muss nicht immer eine neue API oder ein neues Tool einführen – es kann genauso gut auch alte Features entfernen.

JDK 11 vs 10

Diese Karte vergleicht JDK 10 mit JDK 11. Grüne Gebäude zeigen Dateien, die mehr Zeilen enthalten als in Version 10. Rote Gebäude haben Zeilen verloren. Mit 8 Millionen Zeilen ist Version 11 erneut gewachsen. Die Tests machen nun 36 % der gesamten Codebasis aus. Da wir etwas näher herangezoomt haben und dadurch mehr Beschriftungen sichtbar wurden, erkennen wir: Die Tests bilden keinen einheitlichen Block. Ähnlich wie das src-Verzeichnis sind auch sie in Unterordner aufgeteilt – je nachdem, ob sie das JDK, HotSpot, Nashorn oder andere Komponenten testen. Besonders stark gewachsen ist der Bereich der HotSpot-Tests, vor allem durch das neu hinzugekommene Verzeichnis vmTestbase, das eine Vielzahl neuer Testfälle enthält.

Auch jdk.localedata hat einen großen Zuwachs erfahren. Es enthält die XML-Dateien des Unicode Common Locale Data Repository (CLDR). Das CLDR stellt zentrale Bausteine bereit, damit Software weltweit gesprochene Sprachen unterstützen kann – und ist das umfassendste Standard-Repository für Lokalisierungsdaten. Das JDK verwendet es, “um in den Standard-APIs Datums-, Zeit-, Währungs-, Sprach-, Länder- und Zeitzonenangaben korrekt zu formatieren”.. Mit 1,1 Millionen Zeilen nimmt jdk.localedata erheblich Platz ein. Da es sich dabei aber überwiegend um XML-Dateien handelt, werden wir es in zukünftigen Visualisierungen ausklammern und uns auf tatsächliche Code-Änderungen konzentrieren.

In der Karte sehen wir auch, dass einige Module nicht gewachsen, sondern entfernt worden sind. Mit JEP 320 wurden Java-EE- und CORBA-Module entfernt. Der Grund: Ihr Nutzen stand in keinem Verhältnis mehr zum Aufwand, sie weiter zu pflegen. Insgesamt bedeutet das, dass die Entwickler nun 357.000 Codezeilen weniger warten müssen.

Eine Komponente, die nur scheinbar entfernt wurde, war incubator.httpclient. In Wirklichkeit wurde sie verschoben, überarbeitet und als Modul java.net.http standardisiert. Dieser Ablauf ist typisch für Inkubationsmodule: Sie beginnen immer separat von den standardisierten Modulen. Je nach Fortschritt können sie später als Preview weitergeführt oder – wie im Fall des HTTP-Clients – direkt als stabile API übernommen werden. Wird während der Inkubationsphase festgestellt, dass ein Modul nicht genügend Mehrwert bietet, kann es auch ersatzlos entfernt werden.

Anders läuft es bei experimentellen HotSpot-JEPs. Diese starten nicht als eigene Module, sondern werden direkt im bestehenden HotSpot-Code verankert – wie etwa der neue skalierbare Garbage Collector „Z“, der trotz seines experimentellen Status vollständig im HotSpot integriert ist. Dass HotSpot in C++ geschrieben ist und damit außerhalb des Java Platform Module Systems (JPMS) liegt, könnte ein Grund sein für dieses abweichende Vorgehen.

JDK 12 to 17

Nach der Veröffentlichung von Version 11 folgen fünf weitere Releases, bevor im September 2021 mit JDK 17 das nächste „wichtige“ Release erscheint. Wichtig meint hier, dass JDK 17 den Status einer LTS-Version besitzt. Für viele Enterprise-Entwickler ist das der Moment, in dem sie endlich lange standardisierte Sprachfeatures einsetzen können – darunter switch expressions, records, sealed classes, sealed interfaces und pattern matching for instanceof. Das JDK Team entwickelt das JDK kontinuierlich weiter. Diese konstante Verbesserung sieht man auch, wenn wir die Bereiche hotspot und java.base betrachten. Bei jeder Version wachsen (Δ+) oder schrumpfen (Δ−) diese Bereiche um mehrere tausend Zeilen Code. Tatsächlich ändern sich fast alle Dateien, wie die folgende Collage zeigt.

Hotspot&java.base Collage: JDK 12 bis 17

Dass sich java.base so stark verändern kann, ist schon bemerkenswert. Dieses Modul bildet die Grundlage für alles – und umfasst 586.000 Zeilen. Noch beeindruckender ist jedoch, dass sich nahezu der gesamte hotspot-Code mit jeder neuen Version ändert. Das sind 806.000 Zeilen – der Code, der die Welt am Laufen hält. Oder zumindest einen großen Teil davon. Und all diese internen Veränderungen? Sie bleiben meist unbemerkt.

Aber warum überhaupt an der JVM rütteln? Zwei Gründe liegen auf der Hand. Erstens: Es gibt laufend spürbare Leistungsverbesserungen in HotSpot. Je nach Anwendungsszenario kann Version 17 bis zu 20 % schneller sein als Version 11. Zweitens: HotSpot muss sich anpassen, um neue Erweiterungen zu unterstützen – insbesondere neue Sprachfeatures wie records.

Auf dieser Detailebene ist es allerdings schwierig, neue Sprachfeatures gezielt zu lokalisieren – zumindest ohne Vorwissen. Es ist sehr wahrscheinlich, dass sie Auswirkungen auf das Modul hotspot hatten. Auch die 104.000 Zeilen im Modul jdk.compiler wurden mit jeder Version verändert. Wie viel davon allein auf records zurückgeht, lässt sich schwer sagen. Es gibt leider keine Datei mit Namen CompileRecord.java die alle record-spezifischen Änderungen bündelt.

Kurz gesagt: Sprachänderungen sind schwerer sichtbar zu machen. Sie führen meist nicht zu auffälligen Strukturänderungen und lassen sich daher schlechter visualisieren. Auch wenn ich gerne mehr darüber sagen würde – derzeit fehlt mir das Wissen, um das fundiert zu untersuchen.

JDK 18 bis 21

Nach Version 17 wurde der Zeitraum bis zur nächsten LTS-Version von drei auf zwei Jahre verkürzt. Deshalb erschien im September 2023 bereits die nächste LTS-Version: JDK 21. Zu diesem Zeitpunkt hatten die Maintainer das Prinzip verinnerlicht, zuerst Feedback aus der Praxis zu sammeln, bevor Erweiterungen stabilisiert werden. Die Versionen 19 und 20 enthielten daher keine für Nutzer sichtbaren JEPs. Stattdessen waren sämtliche Erweiterungen experimentell, in der Inkubationsphase oder als Vorschau markiert. Nach mehreren Feedback-Runden wurden viele dieser Features schließlich in JDK 21 stabilisiert. Sprachlich bedeutete das: stabile Record Patterns und Pattern Matching für switch – zwei Funktionen, die den Weg ebnen für das, was die Entwickler data-oriented programming nennen. Eine bemerkenswerte neue Art, in Java zu denken und zu programmieren.

Nach zwei Vorschauversionen wurden mit JDK 21 auch die virtuellen Threads eingeführt. Sie sind eine Alternative zu den klassischen Plattform-Threads und besonders nützlich für nebenläufige Anwendungen mit hohem Durchsatz, bei denen viele Aufgaben I/O-gebunden sind – also zum Beispiel stark mit dem Netzwerk interagieren. Virtuelle Threads lassen sich eins zu eins als Ersatz für Plattform-Threads einsetzen, da sie dieselbe API verwenden. Das ist großartig – erschwert aber auch die Nachvollziehbarkeit der notwendigen Codeänderungen. Man kann davon ausgehen, dass die Unterstützung virtueller Threads tiefgreifende Änderungen an HotSpot erforderte. Ein Paket namens virtual oder virtualthread sucht man jedoch vergeblich.

Eine Suche nach *Thread*, *Executor*, *Continuation* zeigt immerhin einige Änderungen – wenn auch erstaunlich wenige im Vergleich zu dem, was vermutlich wirklich verändert wurde. In lang erhielt die Klasse Thread.java 377 zusätzliche Zeilen, VirtualThread.java wurde mit 778 Zeilen neu eingeführt, ebenso ThreadBuilders.java mit 357 Zeilen. In util.concurrent kamen 20 Zeilen in Executors.java hinzu. Und in internal.vm wurde die neue Datei Continuation.java ergänzt. Und natürlich wurde auch hotspot umfassend überarbeitet – nur lässt sich auf dieser Ebene nicht genau erkennen, welche Änderungen konkret durch virtuelle Threads motiviert waren.

Virtual Threads: JDK 18 vs 21

Eine Erweiterung, bei der sich die Änderungen isolieren lassen – oder zumindest glaube ich das – ist die neue Foreign Function & Memory API.

Sie ermöglicht es Java-Entwicklerinnen und -Entwicklern, sicher auf fremde Funktionen (also Code außerhalb der JVM) zuzugreifen und fremden Speicher (Speicher, der nicht von der JVM verwaltet wird) zu nutzen. Damit löst sie das bisherige JNI (Java Native Interface) ab, das in dieser Hinsicht lange Zeit der Standard war.

Foreign Function & Memory API: JDK 17 bis 22

Die Foreign Function & Memory (FF&M) API begann als Inkubator-Feature und wurde in JDK 19 zur Preview erklärt. Man sieht, dass der ursprüngliche Code verschoben, angepasst und als Preview verfügbar gemacht wurde.

Interessanterweise liegt der Großteil der Logik intern im JDK – etwa 11.000 Zeilen in internal/foreign). Nur ein kleiner Teil, rund 1.000 Zeilen in lang/foreign), ist für die Außenwelt sichtbar. Selbst beim Hineinzoomen in java.baseist dieser kleine, exponierte Abschnitt kaum auszumachen. Diese Struktur blieb auch in den folgenden Vorschauversionen bestehen. Mit jeder Version wurde der Code weiter optimiert, bis er schließlich mit JDK 22 – einer Nicht-LTS-Version – stabilisiert wurde. Man erkennt deutlich: Die JDK-Maintainer nehmen sich Zeit, um Dinge richtig zu machen. Sie hetzen nicht, um einen Release-Zug zu erwischen – selbst keinen LTS-Zug.

Und entsprechend teile ich meine FF&M-Analyse auch nicht künstlich auf zwei Kapitel auf, sondern platziere sie einfach dort, wo sie am besten hinpasst.

JDK 22 bis 25

JDK 25, dessen Veröffentlichung für September 2025 geplant ist, ist nicht nur das neueste LTS-Release. Es ist auch das erste Mal, dass sich Versionsnummer und Erscheinungsjahr entsprechen. Das hat zwar nur symbolischen Wert – aber hey, wenn wir schon 30 Jahre Java feiern, darf auch dieser kleine Fun Fact nicht fehlen.

Zum Zeitpunkt dieses Textes ist noch unklar, welche Erweiterungen in JDK 25 tatsächlich stabil werden. Aber man kann spekulieren: Virtuelle Threads werden mit hoher Wahrscheinlichkeit weiter verbessert.
Bereits in Version 24 wurde das Feature synchronize virtual threads without pinning eingeführt. Vielleicht sehen wir in 25 auch die sogenannten scoped values – eine besser verständliche Alternative zu thread-local. Was wir hingegen nicht sehen werden: die Stabilisierung von structured concurrency. Für JDK 25 ist hier bereits eine fünfte Vorschau geplant.

Wir werden vermutlich auch einige neue, stabile Sprachfeatures sehen. Darunter Primitive Types in Patterns, instanceof, und switch, Flexible Constructor Bodies, Module Import Declarations und Simple Source Files and Instance Main Methods. Gerade letzteres wäre besonders passend für JDK 25, da es laut den Entwicklerinnen und Entwicklern “die Auffahrt ebnet” – also den Einstieg in Java für Neueinsteiger deutlich vereinfachen soll. Ein symbolträchtiger Schritt zum 30-jährigen Jubiläum.

Die Idee von “die Auffahrt ebnen” ist es, Menschen den Einstieg in Java zu erleichtern. Derzeit erfordert das Schreiben eines einfachen HelloWorld-Programms das Erlernen vieler Schlüsselwörter und Konzepte, nur um zwei Wörter auf den Bildschirm zu drucken. Es wäre toll, wenn wir unseren class HelloWorld { public static void main ... auf das Wesentliche reduzieren könnten: void main() { println("Hello World"); }. Genau das macht der JEP. Die anderen Konzepte sind nicht verschwunden, sondern können nach und nach eingeführt werden. Ob dies mit Version 25 abgeschlossen wird, ist jedoch Spekulation. Die Entwickler lassen sich nicht von beliebigen LTS-Fristen einschränken.

Was nicht spekulativ ist, ist die Tatsache, dass JDK 24 eine Verbesserung geliefert hat, die seit mindestens 10 Jahren benötigt wurde: die Class File API. Bis zu ihrer Veröffentlichung bot das JDK keine offizielle Möglichkeit, den von ihm generierten Bytecode zu verarbeiten. Es konnte den Bytecode natürlich immer ausführen, aber es gab keine offizielle Möglichkeit, die Klassendateien, die den Bytecode enthalten, zu parsen, zu generieren und zu transformieren. Dies ist etwas, das Frameworks oft tun, um Funktionalität hinzuzufügen. Es ist auch etwas, das das JDK selbst macht, zum Beispiel um Lambda-Ausdrücke zur Laufzeit zu unterstützen.

In der Vergangenheit hat das JDK seine eigene Version des beliebten Open-Source-Bytecode-Prozessors ASM gebündelt, um seinen eigenen Bytecode zu verarbeiten. Dies führt zu mehreren Problemen. Wie in der JEP für die Class File API beschrieben, ist eines davon ein Teufelskreis: „Die ASM-Version für JDK N kann nicht finalisiert werden, bevor JDK N finalisiert ist, sodass Tools in JDK N keine Bytecode-Funktionen verarbeiten können, die in JDK N neu sind. Das bedeutet, dass javac Bytecode-Funktionen, die in JDK N neu sind, nicht sicher ausgeben kann, bis JDK N+1 erschienen ist.” Wenn man dieses Problem mit einem 6-monatigen Veröffentlichungsrhythmus kombiniert, wird es für die Entwickler sehr kompliziert. Vermutlich noch komplizierter für Frameworks und die ASM-Entwickler Schritt zu halten. Ein umso größerer Grund, eine API nicht nur für das JDK intern, sondern auch für JDK-Nutzer zu haben.

Class File API: JDK 23 vs 24

Wie die FF&M API besteht der größte Teil des Codes aus internem Code und ist daher verborgen. Die Class File API möchte die verfügbare API-Oberfläche ebenfalls so klein wie möglich halten. Mit 6.000 Zeilen ist die sichtbare API immer noch recht groß. Gleichzeitig ist sie auch eine Errungenschaft, da sie ohne das Modulsystem vier Mal so groß gewesen wäre. Die anderen 21.000 Zeilen befinden sich in internal/classfile, tragen aber nicht zur API-Oberfläche bei. Die sichtbare API und diese interne Logik zusammen sind in etwa so groß wie ASM. Allein aufgrund der Größe können wir vermuten, dass die neue API etwa die gleichen Funktionen wie das gebündelte ASM hat. Und tatsächlich ist der Plan, diese Kopie aus dem JDK eventually zu entfernen.

Die Reise bisher

Das JDK ist jetzt fast 30 Jahre alt. Wir haben diese visuelle Reise mit JDK 7 im Jahr 2011 auf halber Strecke begonnen. Damals war das JDK 3,4 Millionen Zeilen schwer und nur 12% davon waren Tests. Mit JDK 9 (September 2017) und 10 (März 2018) wurde das JDK in die neue Struktur umgewandelt, die bis heute beibehalten wurde. Version 10 enthielt 7,2 Millionen Zeilen und 29% davon waren Tests. Wir befinden uns jetzt bei JDK 24 mit einem Veröffentlichungsdatum im März 2025. Der Code umfasst heute 9,4 Millionen Zeilen. 40% davon sind Tests. Ohne Coverage-daten gibt es natürlich keine Möglichkeit zu sagen, wie sich das auf die Branch-coverage übersetzt. Aus meiner Erfahrung ist eine src/test-Verteilung von etwa 50:50 allerdings gesund.

JDK 10+24

Wenn wir auf die 4,3 Millionen Zeilen von src zoomen, sehen wir die bereits bekannten Module. java.desktop macht 25% aus. Heute verlassen wir uns größtenteils auf diesen Code, um IntelliJ, Eclipse und andere IDEs zu rendern. Weitere 22% entfallen auf hotspot, 15% auf java.base und 6% auf java.xml. Alle diese Module existierten bereits in JDK 10. Der große Neuling ist das Inkubator-Modul für Vektoroperationen, das 12% des gesamten Quellcodes ausmacht.

Kurz gesagt, das JDK ist riesig. Dies spiegelt sich auch in der Größe der Dateien wider, die wir in der Karte eingefärbt haben. Dateien über 500 Zeilen sind groß und gelb, alles über 1.500 Zeilen ist sehr groß und rot (dabei zählen wir die tatsächlichen Codezeilen, leere Zeilen oder JavaDoc-Kommentare werden ausgeschlossen). Die Entwickler teilen nicht die gleiche Einschätzung der Größe. Ich denke, ein großer Grund dafür ist, dass das JDK viel Komfort-Code benötigt, um die Nutzung angenehmer zu gestalten. Die List.java– Schnittstelle hat etwa 50 Methoden für alle möglichen Interaktionen. Arrays.java hat über 100 Methoden, was 2.000 Zeilen ausmacht.

Selbst mit diesen Zahlen sind die großen roten Codeblöcke in der Mitte von incubator.vector besonders. Es handelt sich um Assembler-Dateien für Linux und Windows mit jeweils 3.000 bis 20.000 Zeilen. Insgesamt 402.000 Zeilen. Ich weiß nicht, ob sie von Hand geschrieben oder aus einer anderen Quelle generiert wurden. Es wäre seltsam, generierten Code in die Versionskontrolle zu übernehmen. Ich hoffe aber, dass sie alle generiert wurden, denn sie von Hand zu schreiben und synchron zu halten wäre… eine Herausforderung.

Klar ist dagegen, was diese Dateien tun. Sie unterstützen die neuen Vektoroperationen. Vektoroperationen an sich sind nichts Besonderes. Das Multiplizieren von Vektoren und Matrizen sind schließlich gängige mathematische Operationen. Sie sind so üblich, dass CPU-Architekturen oft Erweiterungen bereitstellen, die Vektoroperationen deutlich beschleunigen können. Hotspot hat sogar eine Funktion zum automatischen Vektorisieren geeigneter Codes, aber dies geschieht ohne Einwirkung des Programmierers.

Die Vector API macht diese Operationen nun für leistungskritischen Code zugänglich und wurde bereits neunmal als Inkubator ausgeliefert. Der erste Inkubator wurde mit JDK 16 im März 2021 bereitgestellt. In den letzten Versionen hat sich der Assembler-Code nicht verändert, aber die Java-API erhält in fast jeder Veröffentlichung Ergänzungen für fast jede Datei. Die Entwickler arbeiten weiterhin an der API, um sie so klar und prägnant wie möglich zu gestalten. Sie werden dies fortsetzen, bis “notwendige Funktionen von Project Valhalla als Vorschau-Features verfügbar sind”. Aber was ist Valhalla und wann wird es verfügbar sein?

Die Reise vor uns

Das Ziel von Project Valhalla ist es, value objects in Java einzuführen, die “sich wie eine Klasse schreiben lassen, aber wie ein int funktionieren”. Dies würde Objekte ermöglichen, die die Leistungsmerkmale von Primitiven (d.h. schnell) haben, aber die vollen Modellierungsmöglichkeiten von Klassen besitzen. Genau diese Leistung ist auch der Grund, warum die Vector API auf der Arbeit von Valhalla aufbauen möchte.

Wenn diese Wertobjekte im JDK existierten, gäbe es keinen Unterschied zwischen einem Primitiv und einem Wertobjekt. Wir könnten das Typsystem vereinheitlichen. Zum Beispiel würde Boxing/Autoboxing von int zu Integer und zurück der Vergangenheit angehören. List<Integer> wäre dasselbe wie List<int>. Man könnte auch verhindern, dass null dort eingefügt wird, wo nur Primitive erlaubt sein sollten. Aktuell verhindert ein int[]-Array null, aber eine List<Integer> erlaubt es. Das Typsystem ist nicht vereinheitlicht.

Mit Valhalla könnten wir wählen, eine value class zu deklarieren, die nur durch die Werte definiert wird, die sie enthält, oder die bekannte class, die durch ihre Identität im Heap definiert wird. Die Letztere wird tatsächlich durch Schreiben von identity class definiert, aber identity ist das Standardschlüsselwort für Klassen, sodass wir es weglassen können. Beide Klassentypen können im Wesentlichen auf die gleiche Weise geschrieben werden, aber value classes haben mehr Einschränkungen als identity classes. Die Einschränkungen ermöglichen es Hotspot, die Leistung zur Laufzeit zu optimieren. Eine dieser Einschränkungen, die wir Entwickler definieren können, ist, ob das Objekt nullbar ist oder nicht (“JEP-Entwurf: Null-Restricted Value Class Types'”)

Ich hoffe es ist offensichtlich geworden, dass value classes im JDK großartig wären. Aber sie Abwärtskompatibel ins JDK zu bringen ist eine wahre Mammutaufgabe, welche vor 11 Jahren mit der Veröffentlichung von JDK 8 begann. Einige Verbesserungen wurden bereits zurück ins JDK gemergt, aber der Großteil der Arbeit wird noch in einem separaten openjdk/valhalla-Repository durchgeführt. Der Hauptzbranch des JDK wird zu bestimmten Zeitpunkten in das Repository gemergt, was es uns ermöglicht zu sehen, was Valhalla verändern wird.

JDK 25 vs Valhalla

Valhalla umfasst derzeit 124.000 Zeilen Code. 17% befinden sich in Hotspot und 81% in Tests. Die Tests enthalten bisher 39% mehr Code in den Hotspot-Tests und 31% mehr micro/org. Letztere sind wahrscheinlich die Leistungs-Benchmarks. Ob dies der vollständige Umfang von Valhalla ist, bleibt abzuwarten.

Bei seiner Veröffentlichung wird Valhalla sicherlich die bedeutendste Transformation des JDK bisher sein. Das ist eine beachtliche Leistung, wenn man bedenkt, welche Reise das JDK bereits hinter sich hat. Valhalla hat die JDK-Entwickler bereits 10 Jahre lang beschäftigt und ist eines der Puzzleteile, um Java für die nächsten 30 Jahre vorzubereiten. Und Valhalla wird nicht die einzige Erweiterung des JDK sein. In den kommenden Jahren wird noch sehr viel passieren.

Die Karten reproduzieren

Wenn dieser Artikel für dich interessant war, du aber noch mehr Details wissen möchtest, dann kann ich dir nur empfehlen, die Ergebnisse selbst nachzuvollziehen.

  1. Klone das OpenJDK
  2. Wechsel zum tag der JDK-Version, die du analysieren möchtest. Ich habe die allgemein verfügbare (globally available, GA) Version gewählt. Die korrekte Build-Nummer ist auf der Seite der Referenzimplementierung aufgeführt. Die jdk-xx-ga-Tags scheinen genau den gleichen Zweck zu haben wie die folgenden expliziten tags:
    1. Für JDK 21 ist es jdk-21+35
    2. Für JDK 17 ist es jdk-17+35
    3. Für JDK 11 ist es jdk-11+28
  3. Lade CodeCharta herunter
  4. Generiere eine Karte des OpenJDK mit CodeCharta. Führe dazu die Tokei-Metriken mit Git-Metriken zusammen, wie in der Dokumentation gezeigt. Es gibt auch ein Skript für eine automatisierte einfache Analyse..
  5. Visualisiere die Karte im Web Studio.

Und der letzte Schritt:
Bitten deine Kollegen um Hilfe bei der Codeanalyse. In meinem Fall hatte ich enorme Hilfe von Stephan Schneider und Hans Spielvogel. Ich hätte diesen Artikel nicht ohne ihre Analyse schreiben können. Vielen Dank euch beiden.

Total
0
Shares
Previous Post

Kurze Links, klare Architektur – Ein URL-Shortener in Core Java

Next Post

Teil II – URL – Shortener

Related Posts

Bewährte Praktiken für APIs

Das Tor zum Himmel. Alle oben genannten Mechanismen sind langwierig zu implementieren und zu warten. Anstatt das Rad neu zu erfinden, sollten Sie sich für eine ausgereifte und leistungsstarke API-Verwaltungslösung mit all diesen Optionen entscheiden, um Geld, Zeit und Ressourcen zu sparen und Ihre Markteinführungszeit zu verkürzen. Ein API-Gateway hilft Ihnen, Ihren Datenverkehr zu sichern, zu kontrollieren und zu überwachen.
Read More