Die String-Klasse ist eines der zentralen Elemente in Java-Programmen. Ob bei der Verwendung als Schlüssel in Maps, beim Vergleich von Texten oder in unzähligen anderen Szenarien – Strings sind allgegenwärtig. Daher sind Optimierungen in diesem Bereich besonders bedeutsam, da sie sich auf eine Vielzahl von Anwendungen positiv auswirken. Mit der Einführung von JDK 25 hat Oracle einen wichtigen Schritt unternommen, um die Performance der String-Klasse nachhaltig zu verbessern. Die Neuerungen ermöglichen unter anderem, dass die Methode String::hashCode nun größtenteils constant foldable ist, was zu drastisch schnelleren Ausführungszeiten bei Anwendungen mit unveränderlichen String-Schlüsseln führt.
Das Resultat sind von Benchmarks belegte Performanceverbesserungen um den Faktor acht gegenüber der Vorgängerversion JDK 24. Doch wie wurde diese Beschleunigung erreicht und welche Auswirkungen hat sie im Detail? Ein genauerer Blick zeigt die technischen Hintergründe und erläutert die Implikationen für Java-Entwickler. Im Zentrum der Optimierung steht die Methode hashCode(), die einen ganzzahligen Wert berechnet, der den Inhalt des Strings eindeutig identifizieren soll. Dabei wird der Hashcode beim ersten Aufruf berechnet und innerhalb einer privaten Instanzvariablen gespeichert. Da Strings unveränderlich sind, bleibt dieser Hashcode für die Lebensdauer der String-Instanz konstant.
Interessanterweise konnte bisher die virtuelle Maschine diese Information nicht optimal nutzen, weil das zugrundeliegende Feld nicht als stabil erkannt wurde. Mit JDK 25 wurde dies nun geändert: Die interne Variable, die den Hashcode speichert, wurde mit der @Stable-Annotation versehen. Diese Annotation signalisiert der JVM, dass der Wert nach seiner ersten Berechnung unveränderlich bleibt und sicher benutzt werden kann, um den Aufruf der hashCode-Methode durch einen konstanten Wert zu ersetzen. Dadurch können Aufrufe von String::hashCode in bestimmten Szenarien komplett vor der Laufzeit berechnet und eingefroren werden, was für eine wesentliche Beschleunigung sorgt. Besonders sichtbar wird der Performancegewinn bei sogenannt unveränderlichen Maps, die Strings als Schlüssel verwenden und bei denen die Werte über konstante Strings nachgeschlagen werden.
Ein Beispiel hierfür sind Maps, die native Systemaufrufe mittels Java-MethodHandles speichern, wie die Bindung an malloc() oder free(). Hier werden die MethodHandles über einen String-Key referenziert, der im Map nachgeschlagen wird. Bisher war der Lookup mit einem gewissen Overhead verbunden, da bei jedem Zugriff der Hashcode neu berechnet oder zumindest erneut verwendet werden musste. Durch die neue Optimierung wird die Hashcode-Berechnung konstant gehalten und sogar der gesamte Prozess des Map-Lookups und der MethodHandle-Auflösung kann von der JVM vorweggenommen und optimiert werden. Dies führt zu einem direkten Aufruf des nativen Systemaufrufs, ohne die sonst entstehenden Verzögerungen.
Benchmarks zeigen eindrucksvoll die drastische Verbesserung: Während unter JDK 24 ein durchschnittlicher Zeitaufwand von rund 4,6 Nanosekunden pro Hashcode-Lookup gemessen wurde, reduziert sich dieser bei JDK 25 auf etwa 0,57 Nanosekunden. Diese Beschleunigung um den Faktor acht macht sich insbesondere in Bereichen bemerkbar, in denen ein häufiges Nachschlagen von unveränderlichen Strings stattfindet. Für Entwickler bedeutet das schnellere Programmstarts, bessere Reaktionszeiten und insgesamt effizientere Anwendungen. Allerdings gibt es gewisse Einschränkungen, die diese Optimierung mit sich bringt. Derzeit funktioniert das konstante Falten nur bei Hashcodes, die nicht den Standardwert null besitzen.
Dies ist relevant, da die String-Hashcode-Variable initial mit null (bzw. 0) vorbelegt ist. Insbesondere der häufig verwendete leere String hat den Hashcode null und profitiert daher aktuell nicht von der Optimierung. Auch wenn Fälle, in denen der Hashcode null ist, statistisch gesehen selten sind, sind sie bei besonders häufig genutzten Strings mitunter kein Randphänomen. Dennoch arbeitet das Java-Team aktiv daran, auch diesen Sonderfall in zukünftigen Updates zu beheben, um die Performance-Verbesserung noch breiter nutzbar zu machen.
Ein weiterer Aspekt ist, dass die @Stable-Annotation aktuell nur für JDK-internen Code anwendbar ist. Das bedeutet, dass eigene Java-Anwendungen nicht direkt von der Annotation profitieren können. Zur Milderung dieser Einschränkung ist jedoch JEP 502 in Arbeit, welches Stable Values in einer Vorschau-Version für Nutzercode einführen soll. Dadurch werden Entwickler künftig auch in ihren eigenen Programmen von stabilen, konstanten Werten ähnlich profitieren können. Abgesehen von diesen Details wirkt sich die Optimierung nicht nur auf native Systemaufruf-Bindungen aus.
Jede Anwendung, die einfache oder komplexe unveränderliche Maps mit Strings als Schlüsseln verwendet, kann potenziell von der Verbesserung profitieren. Gerade Frameworks, Serveranwendungen oder komplexe Datenverarbeitungssysteme, die intensiv auf unveränderliche Datenstrukturen setzen, sehen hier Vorteile in der Laufzeitperformance und der Ressourcen-Effizienz. Entwickler werden zudem ermutigt, die Verfügbarkeit von JDK 25 zu nutzen und Ihre eigenen Anwendungen mit der neuen Java-Version zu testen. Oft werden Performance-Engpässe erst mit realen Lasttests sichtbar, weshalb das Upgrade neue Möglichkeiten eröffnet, Applikationen schlanker und schneller zu gestalten. Durch die Offenheit von OpenJDK und die unterstützende Community ist zudem eine breite Verbreitung und kontinuierliche Verbesserung zu erwarten.