Angelika Langer - Training & Consulting
HOME | COURSES | TALKS | ARTICLES | GENERICS | LAMBDAS | IOSTREAMS | ABOUT | CONTACT | Twitter | Lanyrd | Linkedin
 
HOME 

  OVERVIEW

  BY TOPIC
    JAVA
    C++

  BY COLUMN
    EFFECTIVE JAVA
    EFFECTIVE STDLIB

  BY MAGAZINE
    JAVA MAGAZIN
    JAVA SPEKTRUM
    JAVA WORLD
    JAVA SOLUTIONS
    JAVA PRO
    C++ REPORT
    CUJ
    OTHER
 

GENERICS 
LAMBDAS 
IOSTREAMS 
ABOUT 
CONTACT 
Effective Java

Effective Java
Überblick über Java 9
Teil 5: JDK-interne Packages wie sun.misc.* und deren Ersatz
 

Java Magazin, Januar 2017
Klaus Kreft & Angelika Langer

Dies ist die Überarbeitung eines Manuskripts für einen Artikel, der im Rahmen einer Kolumne mit dem Titel "Effective Java" im Java Magazin erschienen ist.  Die übrigen Artikel dieser Serie sind ebenfalls verfügbar ( click here ).

 

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.
 
 
 

 
 

If you are interested to hear more about this and related topics you might want to check out the following seminars:
Seminars
Java Module System - Names, Unnamed, Automatic Modules, Module Descriptors, Tool Chain
1 day seminar ( open enrollment and on-site)
Java 8 & 9 - Lambdas & Stream, New Concurrency Utilities, Date/Time API, Module System
4 day seminar ( open enrollment and on-site)
 

 

  © 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