4 Punkte von GN⁺ 4 시간 전 | 2 Kommentare | Auf WhatsApp teilen
  • JEP 401: Value Classes and Objects hat die Stufe erreicht, als echtes JDK-Preview aufgenommen zu werden
  • Das Kernziel ist, Java-Objekte so zu machen, dass sie „wie eine Klasse codiert sind und wie ein int arbeiten“, um die Kosten von Objekt-Headern, Heap-Allokation, GC und Pointer-Indirektion zu senken
  • Die Value Class in JDK 28 ist noch ein nullable Referenztyp; Non-Null-Typen, spezialisierte Generics und 128-Bit-Codierung sind nicht enthalten, und --enable-preview ist erforderlich
  • Die JVM kann Value Objects skalieren oder in Feldern und Arrays eine Heap-Abflachung vornehmen, aber bei höheren Typen wie erasure-basierten Generics oder Object können sie als Heap-Objekte materialisiert werden
  • Java-Entwickler müssen den Unterschied zwischen Identity und Value in ihr Code-Design einfließen lassen; betroffen sind unter anderem ==, synchronized, Primitive-Wrapper, Array-Performance und künftig auch die Generics-Spezialisierung

Der Umfang von Valhalla in JDK 28

  • Am 15. Juni bestätigte Oracle-Ingenieurin Lois Foltan die Integration von JEP 401: Value Classes and Objects in das OpenJDK-Haupt-Repository und das Ziel JDK 28
  • Der zugehörige Pull Request fügt in 1.816 Dateien mehr als 197.000 Zeilen hinzu
  • Wegen des großen Umfangs der Änderungen gab es während der Integration die Bitte an andere Committer, große Commits vorübergehend zurückzustellen
  • JEP 401 ist eine standardmäßig deaktivierte Preview-Funktion
    • Für die Verwendung der Syntax ist --enable-preview erforderlich
    • Brian Goetz grenzte dies als „den ersten Teil von Valhalla“ ein
  • JDK 28 soll im März 2027 erscheinen, die Integration in die Mainline ist etwa für Juli 2026 geplant

Die Kosten des Java-Objektmodells, auf die Valhalla zielt

  • Das Motto von Valhalla lautet „codes like a class, works like an int
    • Ziel ist, normale Klassen mit Methoden, Konstruktorvalidierung und aussagekräftigen Feldnamen zu verwenden, während die JVM sie so effizient wie Primitive behandeln kann
  • In Java ist mit Ausnahme von acht Primitives fast alles ein Referenztyp
    • Bei Point p = new Point(1, 2) ist p nicht der Point selbst, sondern ein Pointer auf ein Heap-Objekt
    • Jedes Mal, wenn ein Feld gelesen wird, muss die JVM dem Pointer folgen
  • Mit wachsender Objektzahl steigen die Kosten stark an
    • Jedes Objekt hat einen Objekt-Header für Typ, Synchronisierungsstatus und Ähnliches
    • Objekte werden auf dem Heap allokiert und später Ziel der GC
    • Ein Array mit einer Million Point besteht in Wirklichkeit aus einer Million Pointern und einer Million über den Heap verteilter Objekte
  • Brian Goetz bezeichnete dieses Speicherlayout in „State of Valhalla“ als fluffy
    • Valhalla zielt stattdessen auf ein densees Layout, bei dem Daten direkt nebeneinander liegen

Die Hardware-Lücke und die Grenzen der Escape Analysis

  • Ein dichtes Speicherlayout ist wegen der Geschwindigkeitslücke zwischen CPU und Arbeitsspeicher wichtig
    • 1995 waren die Kosten eines Speicherzugriffs ähnlich hoch wie eine CPU-Operation
    • Heute sind CPUs um Größenordnungen schneller als der Hauptspeicher, und Caches überbrücken diese Lücke
  • CPUs lesen Speicher meist in 64-Byte-Cache-Lines
    • Wenn Daten dicht und in Reihenfolge angeordnet sind, werden auf einmal viele nützliche Werte geladen
    • Greift man über Pointer auf verstreute Objekte zu, kann es zu Cache-Misses kommen, die deutlich langsamer sind als Hits
  • Die Escape Analysis der JVM kann einige Objektallokationen eliminieren
    • Wenn festgestellt wird, dass ein Objekt nicht aus einem lokalen Codeabschnitt „entkommt“, kann es statt auf dem Heap in Variablen oder Registern in seine Felder zerlegt werden
  • Allerdings ist Escape Analysis wenig vorhersehbar und anfällig
    • Wenn ein Objekt in Felder anderer Klassen gelangt, in Arrays gespeichert wird, an komplexe Methoden übergeben wird oder Grenzen überschreitet, die der JIT nicht analysieren kann, kann die Optimierung stoppen
    • Schon kleine Refactorings, JDK-Updates oder Änderungen an der Codestruktur können dazu führen, dass das Objekt wieder auf dem Heap landet
  • Gibt man für Performance Objekte auf und codiert direkt als rohe Bytes wie r, g, b, gewinnt man zwar Geschwindigkeit, verliert aber Sicherheit, Lesbarkeit, Validierung und Methoden

Der Start 2014 und der Wechsel von Q World zu L World

  • Project Valhalla startete offiziell im Jahr 2014
  • James Gosling beschrieb es damals als „six PhDs tied into a single knot“
  • Die Java-Schöpfer wollten schon seit Java 1.0 Value Types, gaben das Thema 1995 aber auf, weil das Problem zu schwierig war
  • Das frühe Ziel war, die Ausrichtung von Programmiermodell und den Performance-Eigenschaften moderner Hardware wiederherzustellen
    • Die Richtung war, dass Nutzer selbst flache und dichte Typen wie Primitive deklarieren können, die aber wie normale Klassen aussehen und funktionieren
  • Frühe Prototypen folgten dem Ansatz Q World
    • Dabei wurden neue Value Types als grundsätzlich andere Entitäten als Objekte behandelt, mit separatem Type Descriptor, Bytecode und Top Type
    • Dadurch musste das gesamte JVM-Typsystem zwei Varianten tragen, was die Komplexität erhöhte
  • Der um 2019 aufgekommene Ansatz L World wurde zum Wendepunkt
    • Value Types teilen sich denselben „L carrier“ wie normale Referenzen
    • Das Team erwartete, dass diese Integration schwierig werden würde, aber sie funktionierte ohne große Kompromisse und löste mehrere Probleme früherer Prototypen
  • In L World entstand eine wichtige Trennung
    • JVM-Modell und Sprachmodell müssen sich nicht zu 100 % decken
    • Die JVM kann ein L-World-Modell haben, während Programmierern ein komfortableres Sprachmodell angeboten wird
  • Die anschließende Arbeit wurde in zwei Phasen aufgeteilt: Value Classes und spezialisierte Generics

Änderungen bei Namen und Modell

  • Die Terminologie von Valhalla hat sich mehrfach geändert; das war nicht nur eine Umbenennung, sondern spiegelte Änderungen am Modell wider
  • Der frühe Begriff war value types
    • Damals war noch nicht genau klar, was diese Typen eigentlich sein sollten
  • Etwa 2019–2020 setzte sich das Modell der inline classes durch
    • Bestehende Klassen wurden als identity classes verstanden, neue Klassen als inline classes ohne Identität
    • Für inline classes galten Beschränkungen wie standardmäßig final, final-Felder und keine Möglichkeit zur Synchronisierung
  • „State of Valhalla“ von 2021 behandelte primitive classes und ein Modell mit zwei Projektionen
    • Die Idee war, dass ein Typ sowohl eine flache, nicht-nullfähige value-Variante als auch eine nullfähige reference-Variante besitzt
    • Es wurden sogar Syntaxen wie Point.val / Point.ref, später Point! / Point?, erprobt
  • Dieses Modell war leistungsfähig, brachte aber eine hohe kognitive Belastung mit sich
    • Programmierer mussten im Alltag zwei Formen desselben Typs und die Zeitpunkte ihrer Umwandlung verstehen
    • Letztlich wurde dieser Dualismus reduziert, um das Benutzermodell zu vereinfachen
  • Das aktuelle JEP 401 verwendet value class und value object
    • Mit dem Modifier value wird eine value class deklariert
    • Instanzen sind value objects ohne Identität
    • Eine value class ist weiterhin ein reference type
  • Die Nicht-Nullfähigkeit wurde in das separate optionale JEP Null-Restricted Value Class Types ausgelagert
    • Sie ist nicht in JDK 28 enthalten
  • Ältere Artikel, die das frühere Modell der „primitive classes“ erklären, können vom aktuellen Stand in OpenJDK abweichen
  • In JEP 401 ist auch das Preview-JEP 402: Enhanced Primitive Boxing enthalten
    • Es geht in Richtung reibungsloserer Konvertierungen zwischen Primitive und Wrapper
    • Man sollte nicht davon ausgehen, dass alles in seiner endgültigen Form gemeinsam mit JEP 401 ausgeliefert wird

Das value-class-Modell in JDK 28

  • Eine value class wird mit dem Modifier value deklariert
value class USDCurrency implements Comparable<USDCurrency> {  
    private int cents; // implicitly final  
    public USDCurrency(int dollars, int cents) {  
        this.cents = dollars * 100 + cents;  
    }  
  
    public USDCurrency plus(USDCurrency that) {  
        return new USDCurrency(0, this.cents + that.cents);  
    }  
  
    // dollars(), cents(), compareTo(), toString()...  
}  
  • Auch ein value record ist möglich
  • Die wichtigsten Regeln sind folgende
    • Alle Instanzfelder sind implizit final
    • Methoden dürfen nicht synchronized sein
    • Die Klasse ist standardmäßig final
    • Hierarchien aus value classes und abstract value classes sind möglich
    • Eine Klasse mit Identität kann nicht erweitert werden
    • Interfaces können implementiert werden
  • Die Kerneigenschaft ist das Fehlen von Identität
    • Normale Objekte sind verschieden, selbst wenn sie denselben Inhalt haben und zweimal mit new Point(1, 2) erzeugt werden
    • Ein value object hat keine Identität, so wie es beim int-Wert 4 nicht „zwei verschiedene Vierer“ gibt

Änderungen bei ==, synchronized und null

  • Bei value objects ist == kein Identitätsvergleich mehr, sondern eine Prüfung auf Substituierbarkeit
    • Rekursiv wird verglichen, ob es dieselbe Klasse ist und dieselben Feldwerte vorliegen
    • Primitive-Felder werden bitweise verglichen, Objektfelder wiederum mit ==
    • new USDCurrency(3,95) == new USDCurrency(3,95) ergibt also true
  • Da == jedoch den internen Zustand betrachtet, ist für die Frage, ob „dieselben Daten dargestellt werden“, meist equals geeigneter
  • Ein value object hat keine Identität, an die synchronisiert werden könnte
    • Ein Synchronisierungsversuch löst eine IdentityException aus
    • Wenn Identität ausdrücklich geprüft werden muss, können Objects.requireIdentity und Objects.hasIdentity verwendet werden
  • value classes in JDK 28 sind weiterhin nullfähig
    • USDCurrency d = null; ist zulässig
    • Ein Typ, der null verbietet, ist Gegenstand eines separaten künftigen JEP und nicht Teil von JDK 28
  • Nicht-Nullfähigkeit ist nicht nur eine Frage der Syntax, sondern ein Performance-Hebel, der eine stärkere Abflachung von value classes ermöglicht

Skalarisierung und Heap-Abflachung

  • JEP 401 gibt der JVM die Freiheit, value objects zu optimieren
  • Skalarisierung (scalarization) ist eine JIT-Technik, bei der eine value-object-Referenz in eine Menge von Feldern zerlegt wird
    • Statt eines Color-Zeigers können etwa die Bytes r, g, b und ein Null-Flag übergeben werden
    • Zuweisungs- und GC-Kosten können damit entfallen
    • Es ähnelt der Escape Analysis, ist aber vorhersagbarer und kann über die Grenzen nicht inlineter Methodenaufrufe hinweg angewendet werden
  • Die Skalarisierung hat Grenzen
    • Wenn der Variablentyp ein Obertyp der value class wie Object oder ein durch Erasure entstandener generischer Parameter ist, funktioniert sie in der Regel nicht
    • In diesem Fall muss das Objekt auf dem Heap materialisiert werden
  • Heap-Abflachung (heap flattening) bedeutet, dass Feldwerte eines value object als kompakter Bitvektor codiert und direkt in ein Feld oder Array-Element geschrieben werden
    • Ein Zeiger auf eine andere Stelle im Heap ist nicht nötig
    • Das erhöht Datendichte und Lokalität
  • Abgeflachte Daten müssen bei konkurrierendem Zugriff atomar gelesen und geschrieben werden können, um Tearing zu vermeiden
    • Auf gängigen Plattformen kann „klein genug“ inklusive Null-Flag in der Größenordnung von 64 Bit liegen
    • Kleine value classes lassen sich gut abflachen, aber schon zwei int-Felder oder nur ein double können die Größe atomarer Schreibvorgänge überschreiten, sodass wieder ein normales Heap-Objekt nötig wird
  • Künftig könnten 128-Bit-Codierungen und null-restricted types die Abflachung größerer value classes ermöglichen

Auswirkungen bei Boxing, Wrappern und Arrays

  • Wenn Preview aktiviert ist, werden primitive wrapper classes wie Integer, Long und Double selbst zu value classes
    • Boxed-Werte verlieren ihre Identität, sodass die JVM sie skalarisieren und abflachen kann
    • Integer[] nähert sich dann in der Effizienz int[] an, und der Boxing-Overhead soll stark sinken
  • JEP 402: Enhanced Primitive Boxing erweitert Konvertierungen zwischen Primitive und Box weiter
    • Es öffnet den Weg zu Ausdrücken wie List<int>, ist aber weiterhin ein separates, noch reifendes Vorhaben
  • Die Wirkung zeigt sich besonders deutlich bei Arrays
    • Ein herkömmliches Color[] kann aus einer Million Zeigern und einer Million über den Heap verstreuten Objekten bestehen
    • Ein Color[] aus value classes kann zu einem contiguous block werden, der die Farbwerte direkt fortlaufend speichert
    • Die CPU kann dann mehrere Werte sequenziell cache-line-weise lesen

Vorher-Nachher-Unterschied am Beispiel Point[]

  • Das folgende Beispiel zeigt eine normale class vor Valhalla
final class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • Dieses Array enthält eine Million Zeiger
    • Jeder Zeiger verweist auf ein separates Point-Objekt irgendwo auf dem Heap
    • Jedes Objekt hat zusätzlich zu den beiden int einen Objekt-Header
    • Beim Iterieren muss man den Zeiger lesen, zu dieser Adresse springen und dann die Felder lesen
  • Nach Valhalla sieht ein Beispiel für eine Value Class so aus
value class Point {  
    final int x;  
    final int y;  
    Point(int x, int y) { this.x = x; this.y = y; }  
}  
  
Point[] points = new Point[1_000_000];  
  • Im Code besteht der Unterschied nur aus dem Wort value, aber das Speicherlayout ändert sich
    • Die JVM kann die Werte jedes Point dicht gepackt im Array speichern
    • Es gibt weder Header pro Element noch Zeiger
    • Bezogen auf die beiden int x und y kann das als 8 Byte plus möglichem Null-Flag zusammenhängend angeordnet werden
  • Auch die Wartbarkeit bleibt erhalten
    • Point ist weiterhin eine class mit Namen, Konstruktor, Validierung und Methoden
    • Man kann vermeiden, in int[] xs und int[] ys aufzuteilen und die Indizes manuell synchron zu halten

Warum spezialisierte Generics noch ausstehen

  • Java-Generics werden über Type Erasure implementiert
    • List<String> und List<Integer> sind zur Runtime dasselbe List
    • Der Typparameter T wird zu Object erased
  • Erasure war eine bewusste Entscheidung, um Generics einzuführen, ohne die bestehende Java-Codebasis zu brechen
    • Selbst wenn eine nicht-generische class in eine generische geändert wird, bleiben bestehende Source-Dateien und kompilierte Klassen kompatibel
  • Valhalla und Erasure geraten bei der Performance aneinander
    • Wenn man ein Value Object in List<Point> legt, wird T zu Object erased, daher muss das Objekt auf dem Heap materialisiert werden
    • Der Vorteil der Flattening in Point[] kann in ArrayList<Point> verloren gehen
  • Der Plan zur Behebung besteht aus zwei Schritten
    • Universal Generics: Auf Sprachebene sollen Typvariablen auch Value Types handhaben können
      • Weiterhin mit Erasure
      • Weil ein T-Feld standardmäßig mit null beginnt, kann eine Compiler-Warnung zur „null pollution“ entstehen
      • Wenn diese Warnung behoben ist, ist die API weitgehend specialization-ready
    • Specialized Generics: Auf JVM-Ebene werden spezialisierte Class-Layouts pro konkretem Typargument erzeugt
      • In der Projektsprache sind species und type restriction dabei relevant
      • Erst in diesem Schritt kann ArrayList<Point> tatsächlich flachen Speicher nutzen
  • JDK 28 enthält keine vollständigen Specialized Generics
    • Dass Collections, Streams und APIs auf Value Types flach und ohne Allokationen arbeiten, ist Arbeit für ein zukünftiges Release

Was in JDK 28 enthalten ist und was nicht

  • In JDK 28 kommt Folgendes
    • Deklarationen für value class und value record
    • Die Migration bestehender value-based classes im JDK, darunter Klassen wie Primitive Wrapper, zu Value Classes
    • Skalarisierung und Flattening für Klassen, die die Bedingungen erfüllen
    • Günstigeres Boxing
  • Nicht in JDK 28 enthalten ist Folgendes
    • null-restricted types
    • vollständige Specialized Generics
    • 128-Bit-Kodierung
    • ein vollständig ausgereiftes JEP 402
  • Da es sich um ein Preview-Feature handelt, können sich Syntax und Verhalten je Release je nach Feedback ändern
  • JDK 28 ist kein LTS
    • Das nächste LTS wird voraussichtlich JDK 29 im September 2027 sein
    • Viele Unternehmen werden stabiles Valhalla wohl erst in einem LTS sehen, aber die Preview in JDK 28 startet den echten Feedback-Loop aus produktivem Code

Veränderungen für Ökosystem und Code

  • Im High-Performance-Java-Bereich eröffnet Valhalla einen Weg zu dichten Datenstrukturen, ohne auf Abstraktion zu verzichten
    • Dazu zählen Bereiche wie Datenverarbeitung, Vektorberechnungen, ML, Spieleentwicklung, Finanzen und Codecs
  • Frameworks und Bibliotheken können mit der Migration von value-based classes beginnen
  • Code, der auf Identity angewiesen ist, kann Verhaltensunterschiede erleben
    • == bedeutet bei Value Objects keinen Adressvergleich mehr, sondern einen Vergleich auf Substituierbarkeit
    • synchronized führt bei Value Objects zu einer IdentityException
  • Auch wenn Integer zu einer Value Class wird, bleibt das Binary in den meisten Fällen weiter verlinkbar
    • Neu auftretende Compile-Fehler betreffen Fälle, in denen auf solchen Typen synchronisiert werden soll
    • == mit Abhängigkeit von Integer-Identity oder synchronized(someInteger) kann betroffen sein
  • Early-Access-Builds sind unter jdk.java.net/valhalla verfügbar

Zusammenfassung häufiger Fragen

  • Eine Value Class ist nicht dasselbe wie ein Record
    • record ist die Entscheidung, dass der Inhalt aus Komponenten besteht
    • value ist die Entscheidung, auf Identity zu verzichten
    • Es sind alle Kombinationen aus normaler class, record, value class und value record möglich
  • Value Objects können mit == verglichen werden
    • Die Bedeutung ist nicht Adressvergleich, sondern Substituierbarkeit
    • Für die Gleichheit der dargestellten Daten ist meist equals besser geeignet
  • Value Classes in JDK 28 können null sein
    • Nicht-nullbare Typen sind ein zukünftiges JEP
    • Das ist auch für das Flattening größerer Value Classes wichtig
  • Ein schnelles flaches ArrayList<Point> gibt es noch nicht
    • Wegen Type Erasure werden Objekte in generischen Collections auf dem Heap materialisiert
    • Ein typischer Fall, in dem Flattening in JDK 28 direkt wirkt, sind Felder und Arrays von Value Types wie Point[]
  • Escape Analysis kann nicht alles ersetzen
    • Wenn Objekte in Felder, Arrays oder über die Grenzen der Analyse hinaus gelangen, kann die Optimierung scheitern
    • Die Skalarisierung von Value Objects ist vorhersehbarer und kann weiter über Method-Call-Grenzen hinausreichen
  • Valhalla insgesamt wird sich über mehrere Releases hinweg erweitern
    • JDK 28 ist die erste Preview von Value Classes
    • Specialized Generics, null-restricted types und 128-Bit-Kodierung sind Arbeit für zukünftige Releases

2 Kommentare

 
click 1 시간 전

Die über einen langen Zeitraum hinweg veröffentlichten virtuellen Threads von Project Loom sind praktisch und lösen auf JVM-Runtime-Ebene vieles, sodass Entwickler vergleichsweise wenig grübeln müssen.
Es wäre schön, wenn auch Project Valhalla bei seinem finalen Release so wirken würde, als käme es einem kostenlosen Mittagessen ziemlich nahe.

 
GN⁺ 4 시간 전
Hacker-News-Kommentare
  • Es hieß zwar, der Speicherunterschied sei grundlegend, aber ich frage mich, ob der Artikel überhaupt ordentlich redigiert wurde.
    Wurde nicht noch eben erklärt, dass Objekte mit einer Darstellung von mehr als 64 Bit nicht im Heap flachgelegt werden? Das Point im Beispiel hat zwei 32-Bit-Integer plus ein Null-Flag und kommt damit auf mindestens 65 Bit.
    Bei Formulierungen wie „es könnte ein Null-Flag geben“ und den folgenden kurzen Hervorhebungssätzen wirkt es, als habe eine KI die Betonungssätze erzeugt und sei dabei vom Weg abgekommen, und auch der mittlere Block "[IMAGE: the same Point[] array in two variants..." ist bedauerlich.

    • Sätze wie „Kein Header pro Element. Keine Pointer. Kein Herumspringen im Heap.“ riechen nach KI-Stil und wirken deshalb wie faules Schreiben.
      Es ist okay, sich beim Schreiben von KI helfen zu lassen, aber wenn keine eigene Stimme erkennbar ist, gibt es keinen Grund, das zu lesen.
      https://en.wikipedia.org/wiki/Wikipedia:Signs_of_AI_writing#...
    • Das Thema wirkt wirklich interessant, ich wollte den Text gern lesen, und sogar über das KI-generierte Bild hätte ich hinwegsehen können.
      Aber nach ein paar Absätzen war klar, dass der Artikel durch ein LLM gegangen ist oder auf noch schlimmere Weise erzeugt wurde.
      Ob technischer Blog oder sonst etwas: Bitte lasst nicht die KI an eurer Stelle schreiben. Niemand will so etwas lesen.
    • Es gibt doch 18446744073709551616 mögliche Werte, und davon kann man nicht einen für null reservieren? :)
      Heute habe ich gelernt, dass es in Rust NonZeroU64 gibt und man in Kombination mit Optional das gewünschte Verhalten mit nur 64 Bit pro Eintrag bekommt.
      https://doc.rust-lang.org/std/num/type.NonZeroU64.html
    • Die übermäßige Nutzung von KI war so offensichtlich, dass ich nach zwei Absätzen aufgehört habe.
    • Die Aussage „Objekte mit einer Darstellung von mehr als 64 Bit werden nicht im Heap flachgelegt“ bezog sich auf den ersten Commit.
      Wie auch im JEP klar gesagt wird, ist das nur das erste Ergebnis eines riesigen Features, und wie bei aktuellen Java-Features üblich wird es stückweise ausgeliefert.
      Natürlich ist das Ziel, auch größere Werte flachzulegen, und der Mechanismus dafür existiert bereits in der JVM. Es bleibt nur noch, auf Sprachebene die Absicht auszudrücken, dass „Tearing erlaubt ist“.
  • Ich erkenne die Mühe an, die tatsächlich in Valhalla geflossen ist, aber der Deutung „das Modell war mächtig, aber mental schwergewichtig“ kann ich nur schwer zustimmen.
    Zu sagen, dass eine Variable nicht null sein kann, ist keine mental belastende Unterscheidung, erst recht nicht, wenn alles ausreichend gekennzeichnet ist.
    Auch die Haltung „wir opfern die obere Leistungsgrenze, um das Benutzermodell zu vereinfachen“ könnte den Nutzern gerade eine Vereinfachung genommen haben.
    Das Typsystem einer Programmiersprache soll Entwicklern nützliche Garantien auf einer CPU bieten, die letztlich nur mit Zahlen arbeitet. Es gibt keinen Grund, optionale Sicherheitsgarantien zu verringern, nur weil sie „zu komplex“ seien.
    Und das, obwohl man sogar schon zu der Einsicht gelangt ist, dass sich „Sprachmodell und JVM-Modell nicht zu 100 % überlappen müssen“.

    • Ich habe wenig Vertrauen, dass Java seinen Kurs noch richtig bestimmen kann.
      Die Java-Governance wirkt schwach, besonders im Vergleich zu .NET, wo man von Anfang an meist die richtigen Entscheidungen getroffen hat.
      Ich frage mich inzwischen sogar, ob Java innerhalb von Oracle überhaupt noch Wert oder Aufmerksamkeit genießt. Das Unternehmen wirkt heute wie ein Datacenter-/Compute-Geschäft mit angehängten Legacy-Aktivitäten und enormen Verbindlichkeiten.
      Manchmal habe ich das Gefühl, die einzigen Bereiche bei Oracle, die noch Geld verdienen, seien die Rechtsabteilung und die Rasenmähersparte.
    • Richtet sich dieser Unmut nicht eher gegen den Blogger als gegen die Java-Sprache?
      Und ein Null-Marker kommt ebenfalls: https://openjdk.org/jeps/8303099
      Es muss nur schrittweise ausgeliefert werden, und allein dieser PR zur Einführung von Wertklassen/-objekten umfasst schon 200.000 Zeilen.
    • Nicht-nullfähige Werttypen sind einfach auf ein späteres JEP verschoben worden.
      Es klingt nicht so, als würde jemand sagen, dass es unmöglich sei; eher, dass man einen Elefanten nicht in einem Bissen essen kann.
      Wobei man an diesem einen Bein inzwischen schon ziemlich lange nagt.
    • Nullable ist im Railway-Oriented Programming nur ein anderer Ladezustand.
      Wenn das seit 2012 ein gelöstes Konzept ist, gibt es keinen Grund, verschiedene Geschmacksrichtungen von Zustand direkt in die Sprache einzubauen. Die Schienen führen nur nach A oder B, und der Zug wird je nach Ladezustand umgeleitet.
      Wenn Konzepte kommen und gehen und dabei Sprachkriege auslösen, ist das ein Zeichen dafür, dass eine Nachfrage existiert, die die Sprache entweder nicht richtig bedient oder nur um den Preis mentaler Überlastung.
      Dinge wie Value, Errorstates, Null, IoExceptions, WeirdOsStatesNeededToHandleUpstairs.
      https://fsharpforfunandprofit.com/rop/
      Um es mit Monty Python zu sagen: Machen wir jetzt bitte weiter.
    • Hier geht es nicht um Null-Sicherheit, sondern um Referenz-/Wertprojektionen ähnlich wie bei Integer/int.
      Das Valhalla-Team hat sich dagegen entschieden, für jeden Typ eine Projektion mit Identität und eine ohne Identität zu haben; stattdessen haben Werttypen grundsätzlich keine Identität, sodass Integer und int synonym werden.
      Das Speicherlayout wird dann je nach Kontext und Optimierungsentscheidung automatisch festgelegt. Deshalb hat sich auch die Bedeutung von == für primitive Wrapper wie Integer geändert und hängt nun nicht mehr davon ab, ob man eine „Referenzprojektion“ oder eine „Wertprojektion“ verwendet.
      Hier ist also nicht passiert, dass optionale Sicherheitsgarantien mit der Begründung „mental zu belastend“ reduziert wurden.
  • In HN-Kommentaren zu Java/JVM sieht man immer wieder, dass erstaunlich viele Menschen zwar noch das alte Bild der JVM oder von Java im Kopf haben, aber kaum wissen, wie es heute aussieht
    Die JVM des Jahres 2026 ist ein sehr gesunder Spitzenprädator. Hat sie Schwächen? Natürlich, aber das Fundament ist extrem gut

    • Auf HN ist es schwer, gute Bewertungen für die JVM zu bekommen, hier gilt sie als veraltete Technologie
      Im Beruf nutze ich das aktuelle Java 26 mit Preview-Features, vor allem StructuredConcurrency, und es ist hervorragend. Selbst aus der Perspektive von jemandem, der in früheren Firmen Haskell und Python benutzt hat, bereue ich das überhaupt nicht
    • Viele müssen immer noch Java-Monolithen warten, die aus der Zeit stammen, als Java in den 2000ern seinen Boom hatte, und deshalb weiterhin auf Java 8 laufen
      Ich kenne die neuen Features der letzten Jahre persönlich, aber im tatsächlichen Arbeitsalltag steckt Java buchstäblich noch in der Vergangenheit fest
  • Ein großer Teil der Kommentare hier ist etwas unfair im Vergleich zu der großartigen laufenden Arbeit und den noch spannenderen JEPs, die kommen werden
    Wenn man Java mit einem Kind vergleicht, dann ist es erst ein paar Jahre bei liebevollen Eltern (Sun) aufgewachsen, dann mit den anderen Kindern in die Garage geworfen und von einem bösen Vormund (Oracle) vernachlässigt worden
    Bis JDK 8 war es vernachlässigt und ungeliebt, also musste es im Grunde aufholen
    Dass man sagt, „jetzt bekommt es endlich Dinge wie Structs oder Value Types“, stimmt zwar, aber sein Wachstum wurde durch riesige, bürokratische und feindselige Unternehmensprozesse gehemmt. Jetzt ist es frei und wird durch die OpenJDK-Familie geliebt
    Auch in Zukunft werden wir weiter die Freude an write once, deploy anywhere haben

    • Ob man Oracle mag oder nicht, das ist keine korrekte Beschreibung der Java-Geschichte
      Eher haben liebevolle Eltern es großgezogen, mussten es wegen finanzieller Probleme in eine Pflegefamilie geben, wo es dann vernachlässigt wurde
      Danach wurde es von neuen, liebevollen Eltern, Oracle, adoptiert, und Java ist aufgeblüht und zu einem gesunden, stabilen Erwachsenen geworden
      Oracle hat OpenJDK zur Referenzimplementierung gemacht und damit die Open-Source-Umstellung der Plattform vollendet, und auch zuvor proprietäre Werkzeuge wie JFR und Mission Control als Open Source veröffentlicht
      Viele Gründungsmitglieder des Sprach-Teams wurden ebenfalls gehalten, was bei solchen Übernahmen ziemlich selten ist, und Java hat sich sowohl bei der Sprache als auch bei der Runtime stark verbessert
    • Vernachlässigt wurde Java in den letzten Jahren von Sun
      Oracle hat Java mit beispielloser Geschwindigkeit vorangebracht und dabei den größten Teil der Abwärtskompatibilität erhalten
      .NET habe es „von Anfang an richtig gemacht“, heißt es, aber wenn damit die Trennung und das Neuschreiben von .NET Framework/.NET Core/.NET gemeint ist, ergibt das auch in dieser Diskussion keinen Sinn. .NET konnte von Java lernen und hat trotzdem manches vermasselt
      Dasselbe gilt für MySQL. Auf dieser Seite hieß es, es sei „tot“, aber für Leute, die sich wirklich auskennen, wurde es unter Oracle wiederbelebt
    • Die Aussagen „Oracle hat Java vernachlässigt“ und „es wurde bis JDK 8 vernachlässigt“ widersprechen sich gegenseitig
      Die letzte Java-Version unter Sun erschien 2006, Oracle kaufte Sun 2010, JDK 7 erschien 2011 und JDK 8 2014
      Das Team blieb im Großen und Ganzen dasselbe, und der größte Unterschied war, dass Oracle die Vernachlässigung beendete und mehr Geld bereitstellte. Deshalb hat sich das Tempo von Java nach der Übernahme erhöht
      Von „Aufholen“ zu sprechen ist fragwürdig, weil unklar ist, zu wem eigentlich aufgeschlossen werden soll. Die einzigen Sprachen, die so populär oder populärer als Java sind, sind JS/TS und Python
      Leute, die sagen, Java sei zurückgefallen, vergleichen es meist mit Sprachen, die viel schlechter dastehen als Java. Wer bestimmte Features liebt, übersieht oft, dass die Sprachen, die diese Features haben, nicht wegen dieser Features erfolgreich sind, sondern trotz ihnen schwächeln
      Manager mögen schnelle Releases, und ironischerweise waren es technische Führungskräfte schon aus der Sun-Zeit, die auf Vorsicht und darauf pochten, Dinge langsam und richtig zu machen
      Ich verstehe die Stimmung, dass Java nicht mehr so populär ist wie 2003, aber das war nicht nur für Java, sondern für das gesamte Software-Ökosystem eine außergewöhnlich einheitliche Zeit, und davor oder danach war es nie wieder so einheitlich
    • Es heißt zwar „write once, deploy anywhere“, aber nicht im Browser, auf iOS oder auf Embedded-Systemen
      Die Technologie, die heute wirklich write once, deploy anywhere kann, ist WebAssembly. Die JVM hatte ihre Chance und hat verloren
    • Wenn man die Metapher weiterführt, wurde Java nicht nur in die Garage geworfen, sondern auch dafür benutzt, Milliarden-Dollar-Unterhaltsklagen gegen Google zu führen, und wurde am Ende zu einem Mittel, um Cash einzusammeln
      Trotzdem würde ich nicht so weit gehen, zu sagen, Java sei „im Wachstum gehemmt“ worden. Es wurden Entscheidungen getroffen, manche vernünftig, manche nicht, und solche Entscheidungen sind später sehr schwer zu korrigieren
      Schon bei C++ sehe ich die Halbkompatibilität mit C persönlich als einen 150-Fuß-Albatros, den man nicht loswerden kann, und viele Versionen seit C++11 bestanden darin, diesen Albatros ein wenig erträglicher zu machen
      Dass auf der JVM alle Value Classes als ein einheitlicher L-Typ wie Primitive behandelt werden, halte ich für eine ziemlich elegante Lösung für ein schwieriges Problem
      Letztlich geht das alles auf die Entscheidung von Java 2 zurück, Generics aus Gründen der Abwärtskompatibilität mit Type Erasure zu implementieren, und C3 hat dieses Ergebnis gesehen und diesen Weg abgelehnt
  • Allein über die Evolution der Value Types in Java könnte man wohl schon einen technischen Thriller schreiben
    Ich habe die Mailinglisten gelesen und alle zugehörigen Videos gesehen, und ich bin wirklich beeindruckt davon, wie das Design zu etwas zusammengeführt wurde, das immer noch unverkennbar nach Java aussieht
    Gleichzeitig wurde viel feiner ausgelotet, was Value Types eigentlich bedeuten und welche Optimierungen wo möglich sind

    • Die Syntaxänderung besteht nur darin, value hinzuzufügen
  • Bei Wertklassen funktioniert == im Grunde wie memcmp()
    Das ist etwas bedauerlich, weil es die Kapselung aufbricht und Implementierungsdetails offenlegt
    Client-Code kann abhängig davon verzweigen, wie ein gegebener Wert intern dargestellt ist. In gewisser Hinsicht ist das sogar schlimmer als Identitätsvergleich, denn der legt wenigstens keinen internen Zustand offen

    • Werttypen sind ein Konzept, das sehr weit von der objektorientierten Vorstellung eines „magischen Blackbox-Organismus“ entfernt ist
      Es ist nicht klassische Objektorientierung in neuer Form, sondern eher ein weiterer Schritt einer aus objektorientierten Ideen hervorgegangenen Sprache in eine post-objektorientierte Welt
    • Wenn ein Datenklumpen internen Zustand hat, ist dieser Datenklumpen selbst falsch entworfen
      Ich nehme an, dass man auf Java-Seite zumindest darüber nachgedacht hat, Padding beim Vergleich auszuschließen oder Padding-Bytes auf 0 zu zwingen
      Das müsste auch für Strings funktionieren. Strings werden offensichtlich weiterhin auf dem Heap allokiert, und Zeiger innerhalb einer neuen „Struct“ per memcmp zu vergleichen ist exakt Identitätsvergleich
    • Der Kern von Wertklassen ist, dass sie keinen Zustand kapseln sollen, also vollständig transparente Datenträger sind
    • Wenn man Java nicht praktisch verwendet hat, spürt man die eigentliche Tragweite dieser Änderung womöglich nicht. Das ist eine der seltenen breaking changes in Java
      Java trennt zwischen dem Prüfen der Identität eines Objekts und dem Prüfen auf Gleichheit. == prüft grundsätzlich, ob zwei Zeiger gleich sind, und Gleichheit ist ein subjektiver Begriff auf Basis von Interfaces wie equals/hashCode
      Deshalb war new Integer(1000) == new Integer(1000) früher false, wird jetzt aber true, und new Integer(1000).equals(new Integer(1000)) ist true, während new Integer(10) == new Long(10) früher false war und jetzt ein Compilerfehler ist
      Im alten Java wurden Ganzzahlen bis zu einem bestimmten Wert oft durch kanonisierte Typen ersetzt, ich meine irgendwo um 128. Daher kam der Unterschied zwischen 10 und 1000
      Jetzt scheinen diese Vergleiche implizit entboxt zu werden. Dass der Vergleich Integer/Long früher false war und jetzt ein Compilerfehler ist, zeigt klar, dass Unboxing im Spiel ist
      Mit Variablen bekommt man vielleicht weiterhin das alte Verhalten
      Jedenfalls führt der Verlust von Identität bei Wertklassen dazu, dass == von Zeigergleichheit zu bitweiser Gleichheit wird. Ich hoffe, man löst diese ganzen Randfälle, aber technisch ist das eine breaking change
  • Ich verstehe die Absicht von Wertklassen, aber die Umsetzung ist mangelhaft
    Was gibt folgender Code aus? Point a = new Point(10, 10); Point b = a; a.x = 100; System.out.println(b.x);
    Bisher war die Antwort eindeutig, aber mit Wertklassen hängt sie davon ab, ob Point eine Wertklasse oder eine Referenzklasse ist. Dieses Design schadet also der Lesbarkeit
    Das ist ein Verstoß gegen das Prinzip der Uniformität. In Weinbergs The Psychology of Computer Programming wird Uniformität als psychologisches Prinzip beschrieben, nach dem Benutzer erwarten, dass ähnlich aussehende Dinge sich ähnlich verhalten und unterschiedlich aussehende Dinge sich unterschiedlich verhalten
    Wenn eine Programmiersprache an der Verwendungsstelle zwei Syntaxformen zulässt, die fast gleich aussehen, sich semantisch aber unterschiedlich verhalten, steigt die kognitive Last für Leser. Um zu wissen, ob Zuweisung, Gleichheit, Identität und Mutation sich wie bei normalen Referenzobjekten oder wie bei Werten verhalten, muss man die Typdeklaration prüfen oder sich auf Werkzeuge verlassen
    Man hätte das beheben können, wenn das Schlüsselwort value nicht nur bei der Deklaration, sondern auch bei der Verwendung erforderlich wäre. Also etwa value Point a = new Point(10, 10);

    • Wenn man sich das Ziel von JEP 401 anschaut, ist das unmöglich. Wertklassen sollen es Entwicklern ermöglichen, ein Programmiermodell für unveränderliche Daten zu wählen
      https://openjdk.org/jeps/401
    • Wenn man den Text liest, ist die dritte Zeile ein Syntaxfehler. Alle Felder eines Werttyps sind final
      Natürlich kann man allein anhand dieser vier Zeilen nicht wissen, dass so etwas passiert, aber dieses Problem gibt es schon heute. Dasselbe passiert, wenn Point ein Record ist
    • Die Anweisung a.x = 100; wäre nicht gültig. Record-Typen sind unveränderlich
      Daher sollte die befürchtete Situation unmöglich sein
    • Trotzdem bleibt es etwas verschwommen
      Mit einer Schreibweise wie value Point a = new Point(10, 10); value Point b copy= a; a.x uniq= 100; System.out.println(b.x); wäre sehr viel klarer, dass eine Duplikation/Kopie stattfindet und dass eine Feldänderung sich nicht auf die Felder eines anderen Objekts auswirkt
  • Ich weiß, dass es in der Java-Welt als unhöflich gilt, die Existenz von .NET anzuerkennen, aber ich frage mich, worin sich das von .NET-Structs unterscheidet
    Wenn man Werttypen, Generics-Spezialisierung und Boxing grob überfliegt, sieht es so aus, als hätte man dieselben Entscheidungen getroffen

    • In C# gibt es tatsächlich einige Fallstricke, und Java verfolgt explizit das Ziel, sie sichtbar zu machen
      Während C# C aus Low-Level-Perspektive im Wesentlichen kopiert hat, ist Java das Thema auf höherer Ebene angegangen und hat detailliert analysiert, welche Einschränkungen welche Vorteile bringen
      In anderen Sprachen ist die Einteilung in Structs/Klassen binär, aber Java erlaubt feinere Kontrolle, die die Semantik der zugrunde liegenden Domäne widerspiegelt
      Und gerade bei Structs zeigt sich, dass es verschiedene Fußkanonen gibt, besonders in parallelen Kontexten
    • Im Artikel gibt es einen Abschnitt dazu
      Persönlich würde ich sagen, dass Structs in C/C# veränderlich sind und per Kopie übergeben werden, während Wertklassen unveränderlich sind und als Wert übergeben werden
      Ich glaube nicht, dass Stack-Allokation in Java möglich sein wird
    • Funktional ist das nicht anders, Java holt damit nur alte Praxis nach
      Diese falsche Dichotomie nach dem Muster „C#-Structs haben Identität und Mutation, deshalb muss Kopiersemantik bei Zuweisung oder Übergabe exakt definiert werden, was Programmierern ein schwergewichtigeres Modell und der Runtime weniger Freiheit gibt“ passt nicht besonders gut zu dem, was erklärt wird
      Es wird zwar keine Identität im Sinne der Referenzsemantik von Java-Klassen geben, aber im Sinne einer eindeutigen Speicherstruktur an einer bestimmten Adresse gibt es natürlich weiterhin Identität. Das ist fast schon Wortklauberei anhand der Java-Terminologie
  • Fußnote 6, „Wie unterscheidet sich das von struct in C#“, ist ungenau
    Wenn man sieht, dass der Artikel voller KI-generierter Bilder ist, fragt man sich, ob nicht auch beim Schreiben oder zumindest bei der Recherche reichlich Halluzinationen eingeflossen sind

  • Der Text ist etwas vage und dramatisch, aber zum Glück sind die Originaldokumente ziemlich gut lesbar
    Oberste Seite: https://openjdk.org/projects/jdk/28/spec/
    JEP-Status: https://bugs.openjdk.org/secure/Dashboard.jspa?selectPageId=...
    Es wäre schön, wenn jemand die entsprechenden Entwicklungen in C#, Swift, Java und Rust verfolgen würde. Alle haben darum konkurriert, mit der Hardware Schritt zu halten, und beeinflussen sich meiner Meinung nach gegenseitig
    Persönlich mache ich mir Sorgen, welche Auswirkungen diese Änderungen auf FFI-Memory-Sharing haben werden