BLOG
Technischer Teardown 010|Java 27: Drei neue Features direkt am Quellcode zerlegt – 64-Bit-Objekt-Header, Pattern Matching für primitive Typen, strukturierte Nebenläufigkeit
Am 15. September 2026 wurde JDK 27 offiziell veröffentlicht (GA, Build 35) und bringt 9 JEPs mit sich. Die meisten Berichte bleiben auf dem Niveau einer Pressemitteilung: eine Nummer, ein Satz Zusammenfassung, ein Codebeispiel. Dieser Artikel wählt einen anderen Ansatz – er zeigt direkt den Quellcode. Drei Features dieser Version stehen jeweils auf einer eigenen Ebene: JEP 534 Kompakte Objekt-Header (HotSpot-Laufzeitumgebung) komprimiert den Objekt-Header auf 64-Bit-Architekturen von 96 bits auf 64 bits; JEP 532 Mustererkennung für primitive Typen (javac) erweitert instanceof und switch um alle primitiven Typen; JEP 533 Strukturierte Nebenläufigkeit (java.base-API) fasst eine Gruppe zusammenhängender Thread-Aufgaben in einem schließbaren Scope zusammen. Dieser Artikel zerlegt sechs Dinge: was es ist, den Kernmechanismus bis hinunter zu den Quellcode-Zeilennummern, eine technische Einschätzung, ob sich das Nachziehen lohnt, wie man es aktiviert – und den Weg vom Lesen des Quellcodes bis zum Einreichen eines eigenen Patches bei OpenJDK.
1. Was ist das
Zunächst das Gesamtbild. JDK 27 umfasst insgesamt 9 JEPs, gegengeprüft anhand der offiziellen Projektseite: JEP 523 G1 als Standard in allen Umgebungen, JEP 527 TLS 1.3 hybrider Post-Quantum-Schlüsselaustausch, JEP 531 Lazy Constants (3. Vorschau), JEP 532 Pattern Matching für primitive Typen (5. Vorschau, Fokus dieses Artikels), JEP 533 strukturierte Nebenläufigkeit (7. Vorschau, Fokus dieses Artikels), JEP 534 kompakte Objekt-Header standardmäßig aktiviert (finales Feature, Fokus dieses Artikels), JEP 536 JFR-In-Process-Datenmaskierung, JEP 537 Vector API (12. Inkubator-Runde), JEP 538 PEM-Kodierung (3. Vorschau). Zu beachten: Die Nummern sind nicht fortlaufend; 524–530 und 535 fehlen.
Die drei Fokus-Features decken jeweils eine eigene Ebene ab, und ihr Reifegrad unterscheidet sich erheblich. JEP 534 ist ein finales Feature, in JDK 27 standardmäßig aktiviert; es ändert das Objekt-Speicherlayout von HotSpot und ist für Java-Code völlig transparent. Historie: JEP 450 (JDK 24) experimentell eingeführt, JEP 519 (JDK 25) finalisiert, aber nicht standardmäßig aktiviert, JEP 534 (JDK 27) standardmäßig aktiviert; Owner ist Roman Kennke. JEP 532 ist ein Preview-Feature auf Sprachebene, das --enable-preview erfordert, und löst ein Ärgernis, das sich über zwanzig Jahre angestaut hat: Pattern Matching, instanceof und switch haben lange nur Referenztypen akzeptiert. JEP 533 ist ein Preview-Feature auf API-Ebene, das ebenfalls --enable-preview erfordert; es nimmt „eine Gruppe verwandter Aufgaben“ aus dem freien Zustand der Thread-Pools und macht daraus Arbeitseinheiten mit klar definierten Grenzen. Authors sind Alan Bateman, Viktor Klang und Ron Pressler; entwickelt von JEP 428 (JDK 19, Inkubator) bis heute.
Ein roter Faden verbindet alle drei: Speicher sparen, den Compiler entlasten, Nebenläufigkeit beherrschbar machen – alles Ingenieurspragmatismus — kein neues Paradigma, nur die Begleichung alter Schulden.
2. Kernmechanismen
Vorab die Quellenregel: „JEP-Stand“ stammt von den offiziellen JEP-Seiten auf openjdk.org; „was im Quellcode steht“ ist der Originaltext des openjdk/jdk-Repositorys unter dem Tag jdk-27+35, mit Dateipfad und Zeilennummern.
2.1 JEP 534: 96 Bit auf 64 Bit komprimiert – das Bit-Layout in markWord.hpp ist die ganze Antwort
Auf 64-Bit-Architekturen besteht der alte Objektkopf aus dem mark word (64 bit) plus dem class word (32 bit, wenn compressed class pointers aktiviert sind), zusammen 96 Bit. Die Idee von JEP 450 ist, die Grenze zwischen den beiden Segmenten aufzuheben und den komprimierten Klassenzeiger ins mark word zu packen. Das offizielle Layout-Diagramm (aus dem Originaltext von JEP 450):
Header (compact):
64 42 11 7 3 0
[CCCCCCCCCCCCCCCCCCCCCCHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHVVVVAAAASTT]
(Compressed Class Pointer) (Hash Code) /(GC Age)^(Tag)
(Valhalla-reserved bits)(Self Forwarded Tag)
Der Klassenzeiger wird weiter auf 22 Bit komprimiert, die Größe des hash code bleibt unverändert, und 4 Bit sind für Project Valhalla reserviert. Das ist der JEP-Stand; weiter unten gleicht der Quellcode das Bit für Bit ab. Das Bit-Layout aus dem Kommentar am Kopf von markWord.hpp L43–49:
// 64 bits (without compact headers):
// unused:22 hash:31 valhalla:4 age:4 self-fwd:1 lock:2
// 64 bits (with compact headers):
// klass:22 hash:31 valhalla:4 age:4 self-fwd:1 lock:2
In einem Satz: Die obersten 22 Bit waren bisher unused und stecken jetzt in klass; die übrigen fünf Bitfelder bleiben unangetastet, in der Summe genau 64 Bit. Die Konstantendefinitionen (gleiche Datei L116–154, die zentralen Zeilen im Auszug):
static const int lock_bits = 2;
static const int self_fwd_bits = 1;
static const int age_bits = 4;
static const int hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits;
// Used only with compact headers: the (narrow) Klass* lives in bits 43 to 64.
static constexpr int klass_bits = 22;
Drei Details: hash ist explizit auf 31 Bit gedeckelt, im Einklang mit JEP 450 („Größe des hash code unverändert“); der 22-Bit-narrow-Klass liegt in bit 43–64; die Bitfelder verlaufen von niedrigen zu hohen Bits in der Reihenfolge lock(2)→self-fwd(1)→age(4)→valhalla-reserviert(4)→hash(31)→klass(22) – Kommentar, Konstanten und Summe stimmen an allen drei Stellen überein. Auf der Schalterebene (globals.hpp L131–132):
product(bool, UseCompactObjectHeaders, true,
"Use compact 64-bit object headers in 64-bit VM")
Ein reguläres Product-Flag für die LP64-Plattform, standardmäßig true. Zum historischen Vergleich: In der Ära von JEP 450 war es ein experimental-Switch, der -XX:+UnlockExperimentalVMOptions erforderte; in JDK 27 ist es einfach ein gewöhnliches Product-Flag in einer Zeile. In derselben Datei steht in L150, dass die 32-Bit-VM es stets auf false belässt. Wer zum alten Layout zurückschalten möchte, verwendet -XX:-UseCompactObjectHeaders; das alte 96-Bit-Layout bleibt in dieser Version weiterhin erhalten, und JEP 534 führt die Entfernung des alten Layouts ausdrücklich als Non-Goal auf. Abgrenzung der Quellbasis: Mechanismenbeschreibungen wie dass Sperroperationen das Mark Word nicht mehr überschreiben und dass die GC-Weiterleitung ein neues self-forwarded-Tag erhält, stammen aus der JEP-450-Dokumentation; self_fwd_bits wird auf Kommentar-Ebene bestätigt; der konkrete C++-Code zu ObjectMonitor und zur GC-Weiterleitung liegt außerhalb des Rahmens dieses Artikels und wird nicht näher ausgeführt.
2.2 JEP 532: Pattern Matching für primitive Typen – was javac in der Lowering-Phase tut
Drei historische Einschränkungen (laut JEP): Pattern Matching in switch unterstützt keine Muster für primitive Typen; die primitive Komponente eines Record-Patterns muss strikt denselben Typ haben (JsonNumber(double a) darf nicht als JsonNumber(int age) geschrieben werden), während es im Rest der Sprache automatisches Widening gibt; instanceof unterstützt nur Referenztypen. JEP 532 öffnet all dies auf einen Schlag: Der switch-Selektor wird auf long/float/double/boolean erweitert. Semantischer Kern ist die Exactness — eine Konvertierung ist exact, wenn sie keinen Informationsverlust verursacht; ob long→int oder int→float exact ist, hängt vom zur Laufzeit vorliegenden Eingabewert ab und erfordert einen Laufzeittest; bei unconditionally exact lässt sich dagegen bereits zur Compilezeit feststellen, dass niemals Information verloren geht (zwei Kategorien: type-based und value-based). Die Regeln für dominance und exhaustiveness werden entsprechend erweitert, und Gleitkomma-Case-Konstanten werden nach Repräsentationsäquivalenz auf Duplikate geprüft. Offizielles Beispiel (Originaltext aus JEP 532):
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i; // 原 default 分支
}
int i = 1000;
if (i instanceof byte b) { ... } // false,不进入分支
float f = 1000.0f;
f instanceof int; // true (exact)
Was der Quellcode zeigt (TransPatterns.java). Der instanceof-Test auf primitive Typen wird in der Lowering-Phase umgeschrieben, L200–221:
// $expr instanceof $primitiveType
// =>
// $expr instanceof T $temp && $temp instanceof $primitiveType
if (tree.erasedExprOriginalType!=null && ...) {
BindingSymbol temp = new BindingSymbol(Flags.FINAL | Flags.SYNTHETIC, ...);
// 先对擦除前类型做绑定模式匹配,再对临时变量做原始类型测试
result = translate(resultExpr); // 两段式 && 复合表达式
}
Ein einzelnes expr instanceof int erzeugt nicht direkt einen Bytecode für Primitivtyp-Tests, sondern eine zweiteilige UND-Verknüpfung: Zuerst wird der Wert in eine synthetische FINAL | SYNTHETIC-Temporärvariable überführt (ihr Name enthält syntheticNameChar und kollidiert daher nicht mit Benutzervariablen), anschließend wird gefragt: „Geht bei dieser Konvertierung Information verloren?“ Der erste Teil bringt den Wert sicher zurück, der zweite ist die exactness-Entscheidung zur Laufzeit. makePrimitive aus L772–828 liefert die andere Hälfte der Antwort: Primitivtypen erhalten ihr Class-Objekt über die Constant-Bootstrap-Methode (condy) ConstantBootstraps.primitiveClass, die Signatur wird von PrimitiveGenerator zusammengesetzt — der Compiler erzeugt hier invokedynamic und Konstantpool-Material für die Primitivtyp-Muster. Ferner L510: Ist der Selektor ein Primitivtyp, entfällt die null-Prüfung (Primitivtypen haben kein null); die null-Prüfung des Bindungsmusters in L935 verzweigt je nachdem, ob der Typ primitiv ist, in zwei Zweige. Abgrenzung: Die vollständigen Regeln für exactness, dominance und exhaustiveness wurden ausschließlich den JEP-Dokumenten entnommen; die Compilezeit-Implementierungen in Attr.java und Check.java sind nicht Teil dieses Textes.
2.3 JEP 533: Strukturierte Nebenläufigkeit – ein sealed-Interface und eine Zustandsmaschine mit drei Zuständen
Strukturierte Nebenläufigkeit behandelt eine Gruppe zusammengehöriger Aufgaben als eine einzige Arbeitseinheit: Unteraufgaben laufen standardmäßig in virtuellen Threads; fork/join/close darf nur vom Owner-Thread aufgerufen werden, bei einem Verstoß wird StructureViolationException geworfen; schlägt eine Unteraufgabe fehl, werden die übrigen per Short-Circuit abgebrochen; wird der Owner unterbrochen, wird der Scope geschlossen und sämtliche Unteraufgaben werden abgebrochen; Unteraufgaben erben ScopedValue; der JSON-Thread-Dump zeigt den Hierarchiebaum der Aufgaben (alle genannten Laufzeitverhaltensweisen entsprechen der JEP-Darstellung). Die fünf Änderungen dieser Version (JEP 533 History): Interface und Joiner erhalten einen dritten Typparameter R_X (den Ausnahmetyp, den join() wirft); neu hinzukommt open(UnaryOperator); drei Factory-Methoden wie allSuccessfulOrThrow() lassen join() eine ExecutionException werfen und erhalten jeweils zusätzliche Überladungen mit Function; Joiner.awaitAll() wurde entfernt; onTimeout() wurde durch timeout() ersetzt, die Timeout-Ausnahme trägt CancelledByTimeoutException als cause. Offizielles Beispiel (Originalwortlaut JEP 533):
try (var scope = StructuredTaskScope.open()) {
Subtask<String> user = scope.fork(() -> findUser());
Subtask<Integer> order = scope.fork(() -> fetchOrder());
scope.join();
return new Response(user.get(), order.get());
}
Was der Quellcode zeigt (StructuredTaskScope.java, insgesamt 1469 Zeilen, Implementierungsklasse StructuredTaskScopeImpl). L373–421:
public sealed interface StructuredTaskScope<T, R, R_X extends Throwable>
extends AutoCloseable
permits StructuredTaskScopeImpl {
sealed interface Subtask<T> extends Supplier<T> permits StructuredTaskScopeImpl.SubtaskImpl {
enum State { UNAVAILABLE, SUCCESS, FAILED }
Zwei sealed-Deklarationen zurren die Struktur fest: Das Scope permit-t nur die paketprivate Implementierungsklasse, Subtask nur SubtaskImpl. Während der Inkubationsphase residierte diese API in jdk.incubator.concurrent, ihr offizieller Platz in JDK 27 ist java.util.concurrent in java.base — vom Inkubator-Abschluss bis zur Preview ist die Modulzugehörigkeit ein Wegweiser dieser Entwicklung. Die Subtask-Zustandsmaschine hat nur drei Zustände, und mit extends Supplier<T> liefert get() das Ergebnis direkt — vom ganzen Kleinkram eines Future keine Spur. Die Joiner-Default-Methoden (L572–618) schreiben die Zustandsdisziplin in den Code: onFork verlangt, dass eine Subtask UNAVAILABLE sein muss, onComplete verlangt, dass sie nicht mehr UNAVAILABLE sein darf — zwei Assertionen, die exakt abstecken, was im Callback sichtbar sein kann. Die dreistufigen Defaults der open-Factory (L1268–1269): Das parameterlose open() nutzt Joiner.awaitAllSuccessfulOrThrow() — scheitert eine einzige Subtask, scheitert das Ganze; javadoc (L1173–1176) hält fest, dass die Default-Konfiguration unbenannte virtuelle Threads ohne Timeout erzeugt. Alle öffentlichen APIs der Datei tragen @PreviewFeature(STRUCTURED_CONCURRENCY) — auf Quellcode-Ebene die erneute Bestätigung, dass es weiterhin eine Preview-API ist. Abgrenzung des Umfangs: Der Erzeugungspunkt der virtuellen Threads, die Interrupt-Aufrufe der Abbruchweitergabe und die Timeout-Zeitsteuerung liegen allesamt in der Implementierungsklasse; dieser Artikel zitiert deren internen Code nicht.
3. Technische Bewertung
Legen wir zunächst die Beweislage offen: Sämtliche Quellcode-Zitate stammen aus dem Tag jdk-27+35; sämtliche Leistungszahlen sind den offiziellen JEP-Seiten entnommen – offizielle Angaben also, keine unabhängigen Nachmessungen.
JEP 534: Mechanismus belegbar, beim Nutzen gelten die offiziellen Angaben. Auf Mechanismusebene greifen drei Stellen im Quellcode ineinander – die Annotation des Bitlayouts, die Bitfeld-Konstanten und der Default-Schalter; das Kompressionsschema ist im Code vollständig in sich schlüssig, und das ist ein harter Beleg. Auf der Nutzenseite stehen die offiziell zitierten Zahlen: In einem Szenario verbraucht SPECjbb2015 22 % weniger Heap und 8 % weniger CPU-Zeit; in einem anderen Szenario sinkt die Zahl der GC-Läufe um 15 % (das gilt für G1 ebenso wie für Parallel); ein hochparalleles JSON-Parsing-Benchmark ist 10 % schneller. Auch die Referenzen stammen aus der Selbstauskunft des JEP: Bei Amazon laufen mehrere hundert Produktionsdienste damit (die meisten als Backport auf JDK 21/17), SAPs SapMachine hat es standardmäßig aktiviert. Die Risiken stehen klar im Text: 4 Bits sind für Valhalla reserviert; reicht das nicht, lassen sich Klassenzeiger und identity hash code weiter komprimieren (gemäß JEP 450). Der Dämpfer: klass_bits ist hart auf 22 festgelegt, die Adressierungsobergrenze des Klassenraums wird dadurch weiter eingeengt – ein bewusster Trade-off: Bitbreite eingetauscht gegen die standardmäßige Aktivierung.
JEP 532: Semantik stabil, weiterhin Preview. Die 5. Preview, und diese Version geht gegenüber JDK 26 (JEP 530) unverändert in eine erneute Preview – das Signal ist, dass die Semantik im Wesentlichen konsolidiert ist; doch das „5. Mal“ selbst zeigt, dass es nicht zum finalen Status gekommen ist: An der Syntax kann sich noch etwas ändern, und ein großflächiger Rollout in der Produktion birgt Nacharbeitungsrisiken. Implementierungsseitig zeigen die zweistufige Absenkungsübersetzung plus das per condy geladene Class-Objekt, dass das im Inneren des Compilers nicht leichtgewichtig ist – der Preis dafür, dass primitive Typen ins Pattern Matching einziehen, ist eine zusätzliche Zwischenschicht, die javac generiert.
JEP 533: Die API-Oberfläche zieht sich zusammen. Die Teile, die sich über sieben Previews am häufigsten geändert haben – Konstruktionsweise, Abschlussstrategie, Timeout-Mechanismus – verdichten sich in JDK 27 zu drei Aktionen: R_X plus open(UnaryOperator) plus timeout() – die typische Gestalt einer Konvergenzphase. Das sealed-Interface zieht die Implementierungen ein, der Zustandsautomat wird auf drei Zustände gestutzt – die Zurückhaltung im Design ist deutlich sichtbar. Der Dämpfer: Die 7. Preview bedeutet, dass permanente Kompatibilität nicht zugesagt ist; die internen Mechanismen für Abbruch-Propagation und Timeout stecken in den Implementierungsklassen – eine Bewertung bleibt deshalb zwangsläufig auf der Ebene der Interface-Semantik stehen.
Zusammengenommen. Die Reifestufen sind klar: Bei 534 genügt das Upgrade; 532 und 533 erfordern das Aktivieren des Preview-Schalters, und keines davon gehört auf einen kritischen Pfad, der langfristige Stabilität verlangt. Keiner der drei führt neue Konzepte ein – kompakte Header sind Bit-Wiederverwendung, Pattern Matching für primitive Typen ergänzt die Muster um widening, strukturierte Nebenläufigkeit verlagert das Strukturierungsprinzip in die Nebenläufigkeit – die Kehrseite dieses Engineering-Pragmatismus: keine Überraschungen, keine Magie.
IV. Wertung
Die echten Probleme sind wirklich echte Probleme: Der Overhead des Objektheaders auf 64-Bit-Heaps wird seit Jahren bemängelt; dass instanceof und switch primitive Typen ausschließen, ist eine alte Wunde der Sprachkonsistenz; Fehlerbehandlung und Abbruchweitergabe in nebenläufigem Code gehören zu den Bereichen mit der höchsten Unfallquote bei Java-Programmierern. Jedes der drei JEPs zielt dabei auf je ein echtes Problem.
AI schreibt immer mehr Code, und der Wert davon, dass Menschen Quellcode lesen, steigt im Gegenteil sogar — kurz angemerkt: Wenn ein Großteil des Codes von Modellen generiert wird, wohin mit der eingesparten Zeit? Ein Ziel mit äußerst gutem Preis-Leistungs-Verhältnis ist es, eine Ebene tiefer zu lesen: zu sehen, in welchen Bytecode der Compiler die neue Syntax verwandelt und welche Bits die Laufzeitumgebung im Objektheader verändert. Compiler und Laufzeitumgebung werden nicht einfacher, nur weil man sie nicht gelesen hat; je mehr Sprachfeatures es gibt, desto größer wird die Distanz zwischen „was im Quellcode tatsächlich passiert“ und „dem Code, den man schreibt“. Dass expr instanceof int in eine zweistufige Form aus Bindungsmuster plus UND-Verknüpfung umgeschrieben wird, ist ein Beispiel: Wer nur auf den syntaktischen Zucker schaut, hält das für einen einzelnen Test; wer TransPatterns liest, weiß, dass es eine Typ-Rückgewinnung plus eine Exactness-Prüfung zur Laufzeit ist.
Speichereinsparung ist für das Deployment von AI-Diensten unmittelbar relevant, aber übertreibt es nicht: Bei Inferenzdiensten, Retrieval-Diensten und Agent-Gateways auf der JVM senkt jede Stufe weniger Heap-Belegung auch die Cloud-Rechnung um eine Stufe; den Objektheader um ein Drittel zu kürzen, ist für Dienste mit hoher Objektdichte eine klar vorteilhafte Richtung. Aber „Richtung“ ist nicht dasselbe wie „der eigene Dienst bekommt genau diese 22%“ — der Nutzen hängt von der Verteilung der Objektgrößen und der Allokationsrate ab.
Die Grenzen klar benannt: 532 und 533 sind Previews; in JDK 28 können sie erneut angepasst oder sogar erneut in die Preview gehen — Produktionscode wartet auf die Finalisierung. 534 ist standardmäßig aktiviert, lässt sich aber zurücksetzen; wenn Kompatibilitätsprobleme mit Monitoring-Tools oder nativen Agents auftreten, ist der Rollback-Knopf noch da. Die Performance- und Endorsement-Informationen dieses Artikels entsprechen der offiziellen Darstellung; beim Zitieren sollte man dieselbe Darstellung beibehalten. Wann es sich zu verfolgen lohnt: Wer JVM-Dienste betreut, auf die Speicherkosten achtet oder nebenläufigkeitsintensiven Code schreibt, sollte jetzt --enable-preview aktivieren und sich mit 532 und 533 gründlich vertraut machen. Wann man es nicht verfolgen sollte: Bei kritischen Produktionspfaden, die eine dauerhaft stabile API verlangen — dort wartet man auf die Finalisierung.
5. Praktische Umsetzung
Schalter. JDK 27 ist bereits GA (2026-09-15, build 35). JEP 534 ist standardmäßig aktiviert und benötigt keine Parameter; es gilt unter LP64 und bleibt auf 32 Bit dauerhaft deaktiviert. Mit -XX:+PrintFlagsFinal können Sie prüfen, dass UseCompactObjectHeaders true sein sollte. JEP 532 und 533 sind Preview-Features; sowohl beim Kompilieren als auch beim Ausführen muss --enable-preview gesetzt sein (bei javac zusätzlich --release 27), fehlt eine der beiden Seiten, wird der Vorgang sofort abgelehnt.
Beispiele ausführen. Die offiziellen Beispiele aus den Abschnitten 2.2 und 2.3 sind der einfachste Einstiegspunkt. Es empfiehlt sich, zwei Varianten auszuprobieren: case int i durch case long i ersetzen, um den Dominance-Fehler zu sehen; Joiner.anySuccessfulOrThrow() durch das Standard-open() ersetzen, um den Unterschied bei der Fehlerweitergabe zu erkennen. Einmal selbst ausprobieren bringt mehr, als die Regeln zehnmal zu lesen.
Quellcode lesen. Zwei Wege: Das installierte JDK bringt lib/src.zip mit — nach dem Entpacken liegen die Quellen von java.base und jdk.compiler vor; den C++-Code von HotSpot holen Sie sich über GitHub openjdk/jdk und checken das Tag jdk-27+35 aus. Diese vier Dateien können Sie direkt übernehmen: src/hotspot/share/oops/markWord.hpp (Kommentare in L40–76 ansehen, Konstanten in L116–154), src/hotspot/share/runtime/globals.hpp (Schalter in L131), src/jdk.compiler/share/classes/com/sun/tools/javac/comp/TransPatterns.java (L200–221 und L772–828), src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java (L373–1468). Leseweise: zuerst die Kopfkommentare und javadoc lesen — OpenJDK ist extrem dicht kommentiert, Bit-Layouts und Transformationsregeln stehen direkt in den Kommentaren — dann mit konkreten Fragen in die Implementierung springen und nicht Zeile für Zeile durchkämmen.
6. Wie man selbst eine ähnliche Lösung aufsetzt
Das „Selbst-nachbauen“ dieser Kolumne, auf das JDK übertragen, bedeutet auf dem realistischsten Weg: vom Lesen des Quellcodes bis zum Einreichen eines kleinen Patches bei OpenJDK. OpenJDK ist ein gewaltiges Projekt, für einzelne Beitragende aber keine unüberwindbare Mauer – gegliedert in vier Abschnitte.
Erster Abschnitt: Mit Quellcode-Lektüre das Fundament legen. Beginnen Sie mit den vier Dateien aus Abschnitt 5; wenn Sie die Kopkommentare verinnerlicht haben, folgen Sie den Fäden: von markWord.hpp hinab durch die oops-Hierarchie, von TransPatterns.java hinauf zu TreeTranslator sowie Attr und Check im selben Verzeichnis, von StructuredTaskScope.java direkt zu den Implementierungsklassen. Ziel ist nicht, alles zu verstehen, sondern eine Intuition dafür zu entwickeln, „wen eine Änderung in einer Datei alles betrifft“.
Zweiter Abschnitt: Lokal bauen. openjdk/jdk bringt ein eigenes Build-System mit; auf Unix-ähnlichen Plattformen folgt auf ./configure ein make images. Der erste vollständige Build dauert ein bis zwei Stunden, danach brauchen inkrementelle Kompilierungen nur ein paar Dutzend Sekunden. Erst wenn Sie lokal bauen können, steht Ihr Experimentiertisch.
Dritter Abschnitt: Erst Tests fahren, dann den Code ändern. OpenJDK nutzt jtreg: Die javac-Tests liegen unter test/langtools, HotSpot unter test/hotspot/jtreg und die JDK-API unter test/jdk. Richtig vorgehen heißt: zuerst neue, fehlschlagende Testfälle schreiben (bei Preview-Features sind zusätzliche Grenzfall-Tests besonders willkommen), dann den Code so anpassen, dass sie durchlaufen.
Vierter Abschnitt: Der Einreichungsprozess. Die üblichen Arbeitsschritte: Stil- und Commit-Konventionen prüft das im Repository mitgelieferte jcheck; Änderungen werden als webrev an die Review-Liste der jeweiligen Komponente gehängt und von Committers begutachtet; vor dem ersten Beitrag unterzeichnen Sie die OCA (Oracle Contributor Agreement), elektronisch, maßgeblich ist die offizielle Contributors-Seite. Bei der Wahl des Einstiegs: 532 und 533 befinden sich in der Preview-Phase – risikoarme Verbesserungen wie javadoc-Formulierungen, Fehlermeldungen und Grenzfall-Tests sind ein echter und willkommener Einstieg; auch HotSpots Bit-Layout und die Dokumentation seiner Schalter nehmen ganzjährig kleine Korrekturen entgegen. Für konkrete Zuständigkeiten und das Mentoring-System gelten die aktuellen Angaben auf den jeweiligen Projektseiten.
Auf ein Bild verdichtet:
读源码(markWord.hpp / TransPatterns.java / StructuredTaskScope.java)
→ ./configure && make images(本地构建 JDK 27 镜像)
→ jtreg 跑目标组件测试(test/langtools、test/jdk……)
→ 先写失败用例,再改实现,再跑绿
→ jcheck → webrev 挂评审 → OCA(首次)→ Committer 合并
Die Schwierigkeitsverteilung, ehrlich eingeschätzt: Die ersten beiden Abschnitte schaffen Erfahrene in zwei Wochen; ab dem dritten Abschnitt wird das Verständnis der Komponente auf die Probe gestellt; im vierten Abschnitt dauern die Review-Schleifen am längsten. Der Vorteil ist dafür einzigartig: Jede Zeile, die Sie ändern, läuft im nächsten Jahr auf mehreren hundert Millionen JVMs weltweit.
Fazit
Die drei Features von JDK 27 sind drei Seiten derselben Münze: JEP 534 verkleinert den Objektkopf von 96 Bits auf 64 Bits – die Bitbelegung in markWord.hpp und die Standardschalter in globals.hpp beweisen, dass es sich um ausgereifte Ingenieursarbeit handelt, und der Nutzen bemisst sich nach der offiziellen Darstellung. JEP 532 erweitert instanceof und switch um Unterstützung für alle primitiven Typen; die zweistufige Absenkungsübersetzung von TransPatterns baut die Exactness-Prüfung zur Laufzeit auf, und eine fünfte Vorschau ohne Änderungen bedeutet, dass die Semantik konvergiert ist. JEP 533 zieht die strukturierte Nebenläufigkeit in java.base zusammen: das sealed-Interface, die Drei-Zustände-Zustandsmaschine und die Standardstrategie awaitAllSuccessfulOrThrow sind im Quellcode auf einen Blick erkennbar. Keines davon ist ein neues Paradigma – überall werden nur alte Schulden beglichen. Beim Upgrade erhält man 534 automatisch; die anderen beiden erkundet man mit --enable-preview und nimmt sie erst in Produktion, wenn sie offiziell standardisiert sind.
Quellenangaben
- JEP 534: Compact Object Headers by Default (openjdk.org/jeps/534; Owner: Roman Kennke; Closed/Delivered, Release 27)
- JEP 450: Compact Object Headers (Experimental) (openjdk.org/jeps/450; Details zur kompakten Layout-Bitmap und zum Komprimierungsmechanismus)
- JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview) (openjdk.org/jeps/532; Owner: Angelos Bimpoudis; Regelwerk und offizielle Beispiele)
- JEP 533: Structured Concurrency (Seventh Preview) (openjdk.org/jeps/533; Authors: Alan Bateman, Viktor Klang, Ron Pressler; API-Form, fünf Änderungspunkte und offizielle Beispiele)
- openjdk/jdk-Quellcode (GitHub, tag jdk-27+35): markWord.hpp (L40–76, L116–154), globals.hpp (L131–132, L150), TransPatterns.java (L200–221, L510, L772–828, L935), StructuredTaskScope.java (L373–1468)
- JEP-Liste auf der JDK-27-Projektseite (openjdk.org/projects/jdk/27, „JDK 27 reached General Availability on 15 September 2026“)