Die Zukunft von Java’s Unsafe-Klasse: Alternativen und Herausforderungen
Die sun.misc.Unsafe
-Klasse von Java wird seit 2002 verwendet. Sie bietet wichtige Low-Level-Methoden, die Framework-Entwickler nutzen, um ansonsten unerreichbare Funktionen und Leistung zu liefern. Leider hat Unsafe
auch langjährige Probleme im Zusammenhang mit der Wartbarkeit der JVM. Und wie der Name schon sagt, ist es nicht gerade sicher zu verwenden. Ein neuer JEP schlägt vor, die Speicherzugriffsmethoden von sun.misc.Unsafe
in einer zukünftigen Java-Version zu entfernen. Die Frage bleibt jedoch, was wird sie ersetzen?
In diesem Artikel werden die Unsafe
-Klasse, die Gründe für die Entfernung einiger ihrer Methoden und die möglichen Alternativen für Entwickler untersucht.
Warum die Unsafe-Methoden von Java veraltet sind
Die sun.misc.Unsafe
-Klasse von Java führt einige besondere Dinge aus, die sonst nicht erlaubt sind. Ihre Befugnisse fallen in zwei allgemeine Kategorien:
- Niedrigstufiger, pointerähnlicher Speicherzugriff und -zuweisung
- Konstruktion von Klassen außerhalb der normalen Mittel (sogar über die Reflexion hinaus)
Bei der Ausübung dieser Befugnisse brechen einige Methoden der Unsafe
-Klasse die standardmäßigen Schutzmaßnahmen und Speicherverwaltung, die in die JVM eingebaut sind. Aufgrund dessen variieren die JVM-Implementierungen in der Art und Weise, wie sie Unsafe
aufnehmen. Der Code ist dadurch brüchiger und möglicherweise nicht portabel über JVM-Versionen oder Betriebssysteme hinweg. Die Verwendung hindert auch die Entwickler daran, die internen Strukturen der JVM weiterzuentwickeln.
Warum Entwickler Unsafe verwenden
Baeldungs Leitfaden zur sun.misc.Unsafe
enthält eine Übersicht darüber, was Entwickler mit der Unsafe
-Klasse tun können:
- Klasseninstanziierung ohne Konstruktoren
- Direkte Manipulation von Klassenfeldern
- Werfen von überprüften Ausnahmen, wenn sie nicht im Bereich behandelt werden
- Direkter Zugriff auf Heap-Speicheroperationen
- Zugriff auf die Vergleichen-und-Austauschen (CAS)-Operationen
Das Vergleichen-und-Austauschen (CAS) in Java ist ein gutes Beispiel dafür, warum es eine Unsafe
-Klasse gibt. CAS ist eine Anweisung auf Hardwareebene, die atomaren Speicherzugriff ermöglicht. Atomarität bringt erhebliche Leistungsvorteile für gleichzeitige Threads, da sie es ermöglicht, den Speicher zu modifizieren, ohne zu blockieren. Traditionell konnten Standard-Java-APIs diese Funktion nicht nutzen, da sie spezifisch für das Betriebssystem ist.
Hier ist ein Ausschnitt einer Vergleichen-und-Austauschen-Operation unter Verwendung von Unsafe
, aus der Einführung von Oracle:
public final class AtomicCounter implements Counter { private static final Unsafe unsafe; private static final long valueOffset; private volatile int value = 0; static { try { Field f = Unsafe.class.getDeclaredField("theUnsafe"); // (1) f.setAccessible(true); // (2) unsafe = (Unsafe) f.get(null); // (3) valueOffset = unsafe.objectFieldOffset (AtomicCounter.class.getDeclaredField("value")); // (4) } catch (Exception ex) { throw new Error(ex); } } @Override public int increment() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; // (5) } @Override public int get() { return value; }}
Selbst der Zugriff auf Unsafe
ist komplex. Es ist ein statisches Klassenmitglied, das zunächst mit Reflexion zugegriffen werden muss, dann manuell auf accessible
gesetzt wird und dann erneut mit Reflexion referenziert wird.
Mit der Unsafe
-Klasse in der Hand erhalten wir den Offset des Wertfeldes in der aktuellen AtomicCounter
-Klasse. Der Offset zeigt uns an, wo sich das Feld im Speicher in Bezug auf die Klassenspeicherzuweisung befindet. Dies entspricht der direkten Arbeit mit Zeigern, die Java-Entwicklern möglicherweise nicht vertraut sind.
Zur Ausführung der CAS-Anweisung wird die Funktion getAndAddInt()
aufgerufen. Dies teilt dem Betriebssystem mit, die atomare Anweisung unter Verwendung von this
als Bezugspunkt, an der Stelle valueOffset
und mit dem „Delta“ von 1 auszuführen.
Refactoring von sun.misc.Unsafe
Der Einsatz von Unsafe
birgt mehrere Nachteile. Falsche Speicheroperationen können dazu führen, dass die JVM „hart abstürzt“, ohne eine Plattformausnahme zu werfen. Eine schlechte Speicherverwaltung kann auch zu Speicherlecks führen, die schwer zu diagnostizieren und zu beheben sind. Direkter Speicherzugriff kann auch Sicherheitslücken wie Pufferüberläufe öffnen.
Die Verwendung von Unsafe
führt auch zu implementierungsspezifischem Code. Das bedeutet, dass die Programme, die sie verwenden, möglicherweise nicht so portabel sind. Es macht es auch schwieriger für eine JVM, ihre Implementierungen in diesen Bereichen zu ändern.
Aus diesen Gründen arbeiten die Entwickler von Java daran, eine plattformverordnete Möglichkeit einzuführen, die Aktionen, die derzeit mit der Unsafe
-Klasse verbunden sind, auszuführen. Der Code, der sun.misc.Unsafe
verwendet, ist weit verbreitet und schwer zu refaktorisieren.
Alternativen zu sun.misc.Unsafe
Die Entwickler von Java sind derzeit dabei, die Funktionen von Unsafe
durch standardmäßige, weniger problematische Versionen zu ersetzen. Einige dieser Alternativen wurden bereits in Java 9 eingeführt.
VarHandles
Variable Handles werden in JEP 193 beschrieben. Diese Funktion deckt eine der größten Verwendungen von Unsafe
ab, nämlich den direkten Zugriff und die Manipulation von Feldern im Heap. Es lohnt sich, die Ziele des JEP zu lesen, um zu verstehen, was diese Funktion tut, aber hier ist eine Zusammenfassung:
- Es darf nicht möglich sein, die JVM in einen beschädigten Speicherzustand zu versetzen.
- Der Zugriff auf ein Feld eines Objekts folgt denselben Zugriffsregeln wie bei den Bytecodes
getfield
undputfield
, zusätzlich zur Einschränkung, dass einfinal
-Feld eines Objekts nicht aktualisiert werden kann. - Die Leistungsmerkmale müssen denen der entsprechenden
sun.misc.Unsafe
-Operationen entsprechen oder ähnlich sein. - Die API muss besser sein als die
sun.misc.Unsafe
-API.
Im Allgemeinen bietet VarHandles eine sicherere, bessere Version der Funktionen von Unsafe
, ohne die Leistung zu beeinträchtigen. Sie unterstützen eine Vielzahl von Operationstypen, darunter auch Vergleichen-und-Austauschen.
Die Verwendung von VarHandle
-Instanzen ist eine saubere, sichere Möglichkeit, viele Niedrigstufe-Operationen direkt auf Feldern auszuführen, ohne die Risiken, die wir bei Unsafe
haben, einzugehen.
MethodHandles
Die Klasse MethodHandles (JEP 274) enthält eine Lookup-Klasse, die die Verwendung von Reflexion zum Erlangen und Ändern von Berechtigungen auf Feldern ersetzt. Mit dieser Klasse werden Berechtigungen gemäß dem Zugriff des enthaltenden Codes gewährt, anstatt direkt mit Reflexion gesetzt zu werden.
Die Stack-Walking-API
Das ursprüngliche Unsafe.getCallerClass()
wird teilweise durch die in JEP 259 bereitgestellte Stack-Walking-API ersetzt. Sie bietet eine sicherere Möglichkeit, auf die Aufruferklasse zuzugreifen.
Fazit
Die sun.misc.Unsafe
-Klasse hat in der Java-Welt eine bedeutende Rolle gespielt, aber ihre Nachteile und Risiken haben die Notwendigkeit hervorgehoben, sicherere Alternativen zu entwickeln. Mit der Einführung von VarHandles, MethodHandles und der Stack-Walking-API sind bereits Schritte in diese Richtung unternommen worden. Diese neuen Werkzeuge bieten eine sicherere und standardisierte Möglichkeit, viele der Low-Level-Operationen auszuführen, die bisher mit Unsafe
möglich waren, ohne deren Risiken einzugehen.