|
|||||||||||||||||||
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | | | | |||||||||||||||||||
|
Effective Java
|
||||||||||||||||||
JDK-interne Packages wie sun.misc.* und deren Ersatz
Im Folgenden geht es um JDK-interne Low-Level-APIs,
die üblicherweise in der Mainstream-Programmierung eher nicht verwendet
werden. Allerdings werden sie in vielen Bibliotheken und Frameworks benutzt,
so dass sie den durchschnittlichen Java-Entwickler am Ende vielleicht beim
Umstieg auf Java 9 doch indirekt betreffen könnten, weil die im Projekt
benutzten Bibliotheken nicht mit Java 9 zurecht kommen. Worum geht es?
Im JDK gibt es eine Reihe von Packages, die
ausschließlich für interne Zwecke gedacht waren, nämlich für die Implementierung
anderer JDK-Klassen. Dazu gehören viele der
sun.*
-Packages.
Da es ohne das Modulkonzept keine Möglichkeit gab, sie wirksam vor dem
Zugriff beliebiger Benutzer zu schützen, wurden Klassen wie z.B.
sun.misc.Unsafe
in zahlreichen Bibliotheken und Frameworks verwendet, obwohl sie dafür
nicht gedacht waren.
Diese internen APIs werden beginnend mit
Java 9 allmählich verschwinden. Das bedeutet, dass sie entweder der
allgemeinen Benutzung entzogen oder sogar komplett entfernt und durch neue
APIs ersetzt werden.
Einige Klassen aus
sun.misc
gibt es schon im JDK 9 nicht mehr. Es betrifft die Klassen
sun.misc.BASE64Encoder
und
sun.misc.BASE64Decoder
. Man muss
stattdessen die Klasse
java.util.Base64
verwende, die bereits im JDK 8 hinzugefügt wurde.
Andere
interne
sun.*
-Klassen existieren noch,
sind aber in einen speziellen internen Modul mit dem Namen
jdk.unsupported
verlagert worden, um klar zu machen, dass man sie nach Möglichkeit nicht
mehr benutzen, sondern sich nach Ersatz umsehen sollte. Das betrifft
u. a. die Klassen
sun.misc.
Signal
,
sun.misc.
SignalHandler
,
sun.misc.Unsafe
und die Methoden
getCallerClass
()
aus
s
un.reflect.Reflection
und
newConstructorForSerialization
()
aus
sun.reflect.ReflectionFa
ctory
.
Für eine Übergangszeit kann man die Schutzmechanismen des Modulkonzepts
über die Aufruf-Option
--add-exports
(für
javac
und
java
)
aushebeln und die Klassen zumindest in Java 9 noch benutzen, aber langfristig
muss man sie ersetzen.
Seit Java 8 gibt es schon das Tool
jdeps
,
welches Abhängigkeiten zwischen Packages und Klassen analysieren kann.
Das Werkzeug ist als Unterstützung bei der Modularisierung von Applikationen
gedacht, damit man die Abhängigkeiten erkennen und sie im Rahmen der Modularisierung
entflechten kann. Das
jdeps
-Tool hat
eine Option
-jdkinternals
, mit der man
sich zeigen lassen kann, welche internen APIs verwendet werden. Man bekommt
dann sofort den Hinweis, dass die internen APIs demnächst nicht mehr unterstützt
werden und bekommt, falls vorhanden, Vorschläge, was man als Ersatz verwenden
soll.
Insbesondere das Verschwinden der Klasse
sun.misc.Unsafe
hat Entsetzen und Protest hervorgerufen, weil die Benutzung ihrer Features
über den JDK hinaus weit verbreitet ist. Zahlreiche Bibliotheken und
Frameworks sind betroffen, z.B. Akka, Grails, Guava, Hadoop, Hibernate,
JRuby, LMAX Disruptor, Netty, Scala, Spring, um nur einige zu nennen. Sie
alle müssen für eine Übergangszeit die Schutzmechanismen des Modulkonzepts
mit Hilfe der bereits erwähnten
java
-
bzw.
javac
-Aufruf-Option
--add-exports
aushebeln und langfristig die
sun.misc.Unsafe
-Features
durch andere Mittel ersetzen.
Es gibt schon seit Java 8 eine Replacement-Tabelle
für verschiedene interne APIs (siehe /
JDPS
/).
In der Tabelle ist aufgelistet, welche internen APIs durch welche anderen
APIs zu ersetzen sind. Neu in Java 9 kommt als Ersatz für die Methode
getCallerClass
()
aus
s
un.reflect.Reflection
das
Stack-Walking API (siehe /
STCK
/)
hinzu. Einige Aspekte der Klasse
sun.misc.Unsafe
werden in Java 9 durch Variable Handles (siehe /
VARH
/)
ersetzt. Aber für viele der internen Features wird es erst im Laufe
von JDK 10 oder 11 Ersatz geben.
Schauen wir uns zunächst das Stack-Walking API und die Variable Handles an. Danach geben wir einen Ausblick, wie es mit dem Rest der sun.misc.Unsafe -Features in der Zukunft aussehen wird. Stack-Walking API
Die Methode
getCallerClass()
der JDK-internen Klasse
sun.reflect.Reflection
ist eine native Methode, die das
Class
-Objekt
des aktuellen Aufrufers liefert. Sie wurde an einigen Stellen im JDK
verwendet, vorwiegend im Zusammenhang mit Reflection, z. B. in der
getMethod()
-Methode
der Klasse
Class
, um zu prüfen, ob der
Aufrufer überhaupt berechtigt ist, eine
private
oder
protected
Methode aufzurufen.
Die Methode
getCallerClass()
wurde aber
auch in den AtomicUpdater-Klassen aus dem
java.util.concurrent.atomic
-Package
oder beim Class-Loading benutzt. Frameworks wie z.B. Log4j oder das Groovy-Laufzeitsystem
benutzen es, um interne Stackframes zu überspringen und den eigentlichen
Aufrufer aus dem Benutzer-Code zu finden.
Als Ersatz gibt es nun die Klasse
java.lang.StackWalker
.
Sie hat zwei wesentliche Methoden:
Class<?> getCallerClass()
Das ist der direkte Ersatz für die
getCallerClass()-
Methode.
und
<T> T walk(Function<Stream<StackFrame>, T> function)
Es wird ein Stream von Stackframes des aktuellen Threads erzeugt und
man übergibt eine Funktion, die den Stream abarbeitet.
Damit bekommt man alle Stackframes z.B. so:
StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
List<StackWalker.StackFrame> stack = walker.walk(s->s.collect(Collectors.toList()));
Die
getCallerClass
()
-Methode
ist eine reine Convenience-Methode, denn der Aufrufer ist natürlich im
StackTrace enthalten und man bekommt ihn mit Hilfe der
walk()
-Method
so:
Class<?> caller =
walker
.walk(s->s.findFirst()).map(f->f.getDeclaringClass()).orElse(null);
oder mit Hilfe der Convenience-Methode
getCallerClass()
so:
Class<?> caller =
walker
.getCallerClass()
Variable Handles aka Enhanced Volatiles
Der atomare Zugriff mit Ordering- und Visibility-Garantien
auf Felder von Objekten und Elemente von Arrays ist in Java nicht optimal
unterstützt. Zwar kann man Felder von Klassen als
volatile
deklarieren, aber es sorgt lediglich für atomare Lese- und Schreibzugriffe
auf die betreffenden Felder. Komplexere Operationen wie ein Inkrementieren
oder andere Read-Calculate-Write-Sequenzen sind auf
volatile
-deklarierten
Feldern nicht atomar. Elemente von Arrays kann man in Java nicht einmal
als
volatile
deklarieren.
Deshalb wurden für komplexere, atomare Operationen
in Java 5 die Atomic-Klassen im Package
java.util.atomic
eingeführt. Beispielsweise hat ein
AtomicLong
eine atomare Methode zum Inkrementieren und es gibt ein
AtomicArray
,
das atomare Lese- und Schreibzugriffe auf die Array-Elemente unterstützt.
Aber die Atomics sind Wrapper um die eigentlichen Daten mit entsprechendem
Space-Overhead und dem Performance-Verlust durch die zusätzliche Indirektion.
Dieser Overhead lässt sich mit dem
AtomicFieldUpdater
etwas reduzieren, aber das Ganze bleibt unbefriedigend - zumindest für
solche Situationen, in denen Performance oberste Priorität hat.
Aus diesem Grunde haben insbesondere die
Framework-Entwickler auf Methoden der JDK-internen Klasse
sun.misc.Unsafe
zurückgegriffen. Sie sind schneller, aber eben fehleranfällig - die
Klasse heißt nicht ohne Grund
Unsafe
- und sie sind plattform-abhängig und nicht portabel. In modularisierten
JDK 9 ist die
Unsafe
-Klasse in einem
speziellen internen Modul untergebracht mit dem erklärten Ziel, sie in
zukünftigen Versionen des JDK verschwinden zu lassen.
Den Ersatz für die hochperformanten atomaren
Zuriffe, die über
sun.misc.Unsafe
möglich
waren, liefern im JDK 9 nun die Variable Handles, implementiert als Klasse
VarHandle
im Package
java.lang.invoke
(siehe /
VARH
/).
Die Variable Handles ähneln den Method Handles, die in Java 7 neu hinzugekommen
waren. Ein Method Handle ist eine Referenz auf eine Methode oder einen
Getter oder Setter für ein Feld oder ein Array-Element; sie werden u.
a. als performante Alternative zu gewissen Teilen der Reflection verwendet.
Ganz analog ist ein Variable Handle eine
Referenz auf ein statisches oder ein nicht-statisches Feld einer Klasse
oder ein Element eines Arrays. Über das Variable Handle kann man in
unterschiedlichen Modi auf die referenzierte Speicherstelle zugreifen.
Diese Zugriffsmodi sind den Memory-Order-Policies des C++11-Memory-Modells
nachempfunden,
wo es seit 2011 bereits eine Vielzahl von Speicherzugriffen mit unterschiedlichen
Visibility- und Ordering-Garantien gibt.
In der Klasse
VarHandle
gibt es zum Beispiel die folgenden Zugriffmethoden:
getRelaxed() / setRelaxed()
Das ist ein atomarer Lese- oder Schreibzugriff
ohne Ordering-Garantien. Es entspricht einem Zugriff auf einen
int
,
so wie er schon immer in Java war: der Zugriff auf primitive Typen (außer
long
und
double
) war schon immer ununterbrechbar,
aber man weiß nicht, ob man den aktuellen Wert zu sehen bekommt, weil
es keinerlei Ordering-Garantien gibt, die dafür sorgen würden, dass man
den Wert sieht, den ein anderer Thread zuvor gesetzt hat. (Dieser Zugriff
entspricht der
memory_order_relaxed
in
C++.)
getVolatile() / setVolatile()
Das entspricht dem atomaren Lese- oder Schreibzugriff
mit Ordering-Garantien, wie es ihn schon immer für
volatile
Felder in Java gab: der Zugriff ist ununterbrechbar und die Lese- und Schreibzugriffe
sind geordnet, d.h. man sieht beim Lesen den aktuellen Wert, den ein anderer
Thread zuvor gesetzt hat. (Dieser Zugriff entspricht der
memory_order_seq_cst
in C++.)
getAcquire()
Das ist in etwa der lesende Anteil eines
volatile
-Zugriffs:
nachfolgende Lese- und Schreibzugriffe auf die Variable durch andere Threads
können nicht vor diesem
getAcquire()
-Zugriff
auftreten. Das heißt, man liest atomar und sieht den aktuellen Wert,
den andere Threads gesetzt haben. (Dieser Zugriff entspricht der
memory_order_acquire
in C++.)
setRelease()
Das ist in etwa der schreibende Anteil eines
volatile
-Zugriffs:
vorangegangene Lese- und Schreibzugriffe auf die Variable durch andere
Threads können nicht nach diesem
get
Release
()
-Zugriff
auftreten. Das heißt, man schreibt atomar und macht den neuen Wert für
andere Threads sichtbar. (Dieser Zugriff entspricht der
memory_order_
releas
e
in C++.)
getOpaque() / setOpaque()
Das ist ein Lese- oder Schreibzugriff ohne
Ordering-Garantieren. Es ist lediglich garantiert, dass der Zugriff erfolgt
und nicht etwa wegoptimiert wurde. Eine Beziehung zu den Lese- und Schreibzugriffen
durch andere Threads gibt es nicht. (Dieser Zugriff entspricht am ehesten
der Semantik von
volatile
in C++, die
schon immer ganz anders war als in Java.)
Darüber hinaus gibt es Kombinationen dieser
Operationen wie z. B.
getAndSet()
.
Außerdem hat das
VarHandle
CAS-Operationen
wie
compareAndSet()
,
compareAndExchangeVolatile()
,
compareAndExchangeAcquire()
,
compareAndExchangeRelease()
,
usw.
Wie man sieht, ist diese Low-Level-Schnittstelle sehr komplex und inspiriert vom Memory-Modell in C++11, denn auch das Java Memory Modell soll überarbeitet werden (siehe / JMM2 /). Es soll klarer gefasst und besser formalisiert werden; z.B. die weakCompareAndSet() -Methoden sind derzeit nicht ausreichend präzise spezifiziert. Und man möchte kompatibel zum C++ Memory Modell werden. Das ist aber eine größere Anstrengung, die erst mit Java 10 oder 11 kommen wird. Die Zukunft von sun.misc.Unsafe
Bei Oracle hat man sich angesehen, wofür
sun.misc.Unsafe
außerhalb des JDK verwendet wird. Dabei hat man sinnvolle Verwendungen
gefunden, für die man Ersatz schaffen will. Dazu gehört die Verwendung
-
als
bessere Atomics (wie oben diskutiert),
-
im
Zusammenhang mit der Serialisierung von Objekten,
-
für
effizientes Memory Management, und
-
beim
Zugriff auf nativen Speicher und native Methoden.
Man hat aber auch Verwendungen gefunden, die selten vorkommen und fragwürdig sind. Für diese Fälle wird es keinen Ersatz geben. Dazu gehören Dinge wie - Checked Exceptions als Unchecked Exceptions werfen, - abenteuerliche Casts zwischen beliebigen Typen, die nichts miteinander zu tun haben, - die Berechnung der Größe eines Objekts im Speicher,
-
den
Heap-Speicher kaputt machen, und anderer Unfug.
Schauen wir kurz auf die sinnvollen Verwendungen. Die Verwendung als effiziente Alternativen zu den Atomics haben wir oben bereits besprochen. Dafür wurden Methoden wie Unsafe.compareAndSwap () oder Unsafe.get/setVolatile () benutzt. Als Ersatz gibt es im JDK 9 die Klasse VarHandle im Package java.lang.invoke mit analogen Methoden.
Im Zusammenhang mit der Serialisierung werden Methoden wie Unsafe.allocateInstance() verwendet, um die Serialisierung performanter zu gestalten oder für das effinziente Erzeugen von Mock-Objekten oder Proxies. Alternativen dafür wird es erst mit einer überarbeiteten "Serialization 2.0" geben in einer zukünftigen Version vom JDK.
Für effizientere
Zugriffe auf den Speicher werden Methoden wie
Unsafe.allocate/freeMemory
()
or
Unsafe.get/put
()
benutzt.
Beispielsweise werden in der Guava-Bibliothek aus Performancegründen mit
Hilfe von
sun.misc.Unsafe
beim Array-Vergleich
byte
-Arrays
als
long
-Arrays verarbeitet, weil man
auf diese Weise gleich 8 Bytes auf einmal vergleichen kann. Ein anderes
Beispiel: es gibt derzeit keine "Big Arrays" mit 64-Bit-Indizes. Wer
riesengroße Array benötigt, behilft sich mit
sun.misc.Unsafe
.
Wer nativen Speicher benutzen oder Kernel-Methoden aufrufen will, ist auf
das heutige Java Native Interface JNI oder eben auch auf
Unsafe.get/put
()
angewiesen.
Auf den Ersatz für die betreffenden
sun.misc.Unsafe
-Features
wird man eine Weile warten müssen, denn es gibt ein ganze Reihe von Anstrengungen:
Da ist das "Project Panama", bei dem es um eine Überarbeitung des Java
Native Interface JNI geht (siehe /
PANA
/).
Es gibt einen JEP für die Anbindung von nativen Funktionen und den direkt
Zugriff auf den nativen Speicher (siehe /
JFFI
/).
Es gibt das "Project Valhalla", mit dem Value Types und Generics Over Primitives
kommen sollen (siehe /
VALH
/;
/
VALT
/
und /
GENP
/).
Und es gibt Überlegungen zum Thema "Arrays 2.0"; man will Arrays mit 64-Bit-Indizes
unterstützen, aber auch sogenannte "frozen" Arrays (siehe /ARR2/). Da
ist eine Menge in Arbeit, aber es ist alles "Zukunftsmusik" und wird erst
mit Java 10, 11 oder später Realität werden.
|
|||||||||||||||||||
© Copyright 1995-2018 by Angelika Langer. All Rights Reserved. URL: < http://www.AngelikaLanger.com/Articles/EffectiveJava/91.Java9.What-is-new-in-Java-9/90.java-9.1.overview.ready_7.html> last update: 26 Oct 2018 |