Suche
Debatte um den "Big Kill Switch" im Jigsaw-Projekt

Java 9 Kill-Schalter wird kommen: (k)eine gute Idee?

Hartmut Schlosser
Java 9 Kill Switch

(c) Shutterstock / Mmaxer

 

Oracle hat angekündigt, in Java 9 einen „Big Kill Switch“ einzuführen. Damit soll es möglich werden, die im Rahmen des Java-9-Projektes Jigsaw vorgesehene Kapselung interner APIs zu umgehen. Wir haben uns mit Java-9-Kenner Uwe Schindler darüber unterhalten, wie sinnvoll diese Maßnahme ist.

JAXenter: Oracle will einen sogenannten Kill Switch in Java 9 einbauen. Was hat es damit auf sich?

Mit dem Kill Switch läuft alter Code wie vorher.

Uwe Schindler: Java 9 bekam mit dem letzten großen Update des Jigsaw-Modulsystems eine weitere Einschränkung verpasst, welche Module noch weiter kapselt. Die Kapselung bezieht sich aber nicht nur auf Module untereinander, sondern vor allem auch auf Code aus dem herkömmlichen Classpath.

Vorher waren nur interne Runtime-Klassen im Zugriff beschränkt (wie solche aus dem Packages “sun.*”). Mit Java 9 Build 148 wurde ein weiteres Schlupfloch geschlossen, was trotz Modulsystem weiter offen stand: Da alle Klassen in den öffentlichen Packages (wie java.lang, java.io,…) sichtbar sind, konnte man “private” Funktionalität via Reflection zugreifbar machen. So war es möglich, z.B. auch auf private Felder in der String-Klasse zuzugreifen.

Man kann beispielsweise seit Java 1.1 auf das char[]-Array hinter einem String direkt zugreifen und somit die Eigenschaft von Zeichenketten in Java, nämlich unveränderlich zu sein, umgehen. Dazu musste man nur via Reflection einen Lookup auf das private Feld machen. Danach konnte man die Methode Field.setAccessible() aufrufen, um auf das Feld von außen zugreifen zu können.

Mehr zum Thema: Oracle plant den „Java 9 Kill-Schalter“

Dahinter war zwar ein Check des Java Security Managers, aber dieser wird im Server-Umfeld recht selten eingesetzt. Eigentlich war diese Funktionalität ursprünglich nur dafür gedacht, in Debugging-Tools die Feldinhalte anzeigen zu lassen. Aber dieses Feature wurde teilweise dafür mißbraucht, um in die Internas der Java-Runtime einzudringen. Bekanntestes Beispiel ist sun.misc.Unsafe, wo man eine Instanz nur über einen solchen Trick bekommen konnte.

Der geplante Kill Switch setzt diese Einschränkung für die Reflection wieder außer Kraft und alter Code läuft wie vorher – zumindest mit Java 9, denn in Java 10 wird es den Kill Switch definitiv nicht mehr geben. Allerdings wird man bei dessen Benutzung mit Runtime-Warnings zugeschüttet, was bei einiger Software, die ohne diesen Schalter überhaupt nicht mehr lauffähig wäre, richtige Log-Flooding-Ausmaße annehmen kann.

Beispiel hierfür ist das bekannte Build-System Gradle und die darunter liegende Programmiersprache Groovy, welche beide derzeit gar nicht mit Java 9 laufen, ohne dass man diesen Killswitch an der Java-Kommandozeile aktiviert: –permit-illegal-access

Anstatt des Kill Switches kann man aber auch einzeln für alle Packages, wo illegal Accesses nötig sind, mit –add-opens eine Ausnahme für das UNNAMED-Modul machen (der Classpath). Aber das produziert keine Warnungen und ist viel umständlicher.

Selbstverständlich können auch Module selbst bestimmen, auf welchen ihrer Packages diese Art von Reflection möglich sein soll. Interessanterweise macht das die Laufzeitumgebung automatisch für das Paket sun.misc, so dass aus Kompatibilitätsgründen weiterhin Unsafe erreichbar ist – ohne Warnungen. Das liegt daran, dass Unsafe auf der offiziellen Liste der weiterhin unterstützten APIs steht (JEP 260).

JAXenter: Denkst du, ein solcher Kill Switch wäre eine gute Idee?

Uwe Schindler: Darüber kann man streiten! Fakt ist, dass seit Java 9 Build 163 der Kill-Switch eingebaut ist, und da es der erste “Feature-Complete Developer Preview” Build ist, ändert sich nun auch nichts mehr daran. Der Vorteil ist sicherlich, dass man Software wie Gradle schnell lauffähig machen kann. Aufgrund der ausgegebenen Warnungen kann man schnell eine Liste mit problematischen Zugriffen erzeugen, welche dann an die betroffenen Bibliotheken als “Critical Bugs” gemeldet werden können, so dass diese baldmöglichst gefixt werden.

Man hat ja nur bis Java 10 Zeit, danach ist der Ofen aus! Einerseits wird der Kill Switch die Adoption von Java 9 schneller erlauben, aber andererseits wird das meiner Meinung nach Cowboy-Style-artige “setAccessible everywhere” noch länger am Leben gehalten – und wird weiterhin die Sicherheit unserer Anwendungen untergraben.

JAXenter: Oracle schreibt ja in der Begründung zum Kill-Switch, dass man die geworfenen IllegalAccessException oder InaccessibleObjectException zwar umgehen kann, das aber bei großen Anwendungen  keineswegs trivial sei. Hältst du das Argument für stichhaltig?

Uwe Schindler: Bei zahlreichen Monster-Applikationen werden so viele 3rd-Party Bibliotheken benutzt, dass man bei solchen Dingen schnell den Überblick verliert. Daher stimmt das Argument: Bei der Entwicklung von Apache Solr und Elasticsearch, den Search-Servern auf der Basis von Apache Lucene, haben wir festgestellt, dass der laxe Zugriff auf Internas leider in hunderten auf Maven Central gehosteten Bibliotheken benutzt wird.

Da sind auch einige Bekannte dabei, deren Entwickler leider teilweise uneinsichtig darauf reagieren. Wir verbieten bei Apache Lucene/Solr und Elasticsearch jeglichen Zugriff auf setAccessible() und unterbinden das auch über einen Java Security Manager. In Folge dessen haben wir uns auch von vielen Bibliotheken einfach getrennt.

Lesen Sie auch: Alle Java 9 Features erklärt: Oracle veröffentlicht JDK9-Dokumentation

Ich halte im Gegenzug das Argument von Oracle daher auch nicht wirklich stichhaltig. Keiner wird am 27. Juli sofort Java 9 für seine großen Software-Installationen einsetzen! Bis das die ersten machen, ist schon wieder soviel Zeit vergangen, dass die Bibliothekenhersteller auf Druck der Entwickler und der Öffentlichkeit reagiert haben und auch hier Updates gefahren haben. Es wird also nur Early-Adopter treffen und leider auch solche, die von Bibliotheken abhängig sind, für die es keine Updates mehr gibt. Aber hier hilft der Kill-Switch auch nur temporär.

JAXenter: Sollte der Zugriff auf interne APIs deiner Meinung nach völlig ausgeschlossen werden? Oder welche Lösung präferierst du?

Vom Sicherheits-Standpunkt aus ist Java 9 mit Jigsaw ein großer Schritt nach vorne!

Uwe Schindler: Vom Sicherheits-Standpunkt aus halte ich Java 9 mit Jigsaw für einen großen Schritt nach vorne! Durch das Early Testing habe ich so viele Bibliotheken gefunden, die das Java API leichtfertig untergraben und damit die Sicherheit unserer Anwendungen gefährden. Daher ist meiner Meinung nach der Zugriff auf interne APIs zu unterbinden!

Und wenn man diesen irgendwo wider Erwarten doch benötigt, muss das genau dokumentiert sein und der Anwender muss darüber entscheiden können, ob er das will. Ohne Java 9 und dem Jigsaw-Modulsystem hat man heute schon gar keine Kontrolle mehr, was Drittanbieter-Dependencies so anrichten können! Da sind Abstürze durch JVM-Crashes nur die Spitze des Eisbergs.

JAXenter: Wie geht Apache Lucene mit dem Thema interne APIs um?

Uwe Schindler: Wir testen Apache Lucene / Solr schon seit den ersten Preview Builds von Java 9. Daneben haben wir aber auch schon früh damit angefangen, den Code frei von Hacks mit privaten APIs zu halten (seit etwa 2010).

Ehrlich gesagt braucht man die in den meisten Fällen auch nicht, denn manche solcher Hacks wurden früher oft nur aus Performancegründen gemacht. Die JVM und der Hotspot-Compiler sind aber inzwischen so gut, dass man solche Hacks seit spätestens Java 8/9 wirklich nicht mehr braucht.

Wir können das mit Performanz-Tests nachweisen: Es gibt keinen Grund dafür, für Off-Heap-Anwendungen Unsafe zu benutzen! Seit Java 9 ist zum Beispiel der Zugriff auf einen API-konformen Off-Heap-ByteBuffer genauso schnell wie ein normaler Array-Zugriff, Unsafe bringt da gar nichts mehr. Apache Lucenes Performanz hängt zum großen Teil davon ab. Daher empfehlen wir jetzt schon unseren Nutzern, sofort nach dem Release von Java 9 mit Lucene-basierten Anwendungen auf Java 9 zu wechseln. Unsere User sind mit Sicherheit unter den ersten Early-Adopter – und sie können es auch problemlos!

Ermöglicht wird das durch das strikte Verbot von allen “Hacks” in der Codebasis von Apache Lucene/Solr: Wir lassen allen unseren Code mit statischer Code-Analyse nach setAccessible() durchsuchen und verbieten jeden Zugriff auf Klassen außerhalb des öffentlichen Java API. Wir verwenden dazu das Tool Forbidden-APIs.

Mehr zum Thema: 5 Java-9-Features, die die Softwareentwicklung verändern werden

Es gibt in Apache Lucene derzeit eine einzige “legitime” Stelle, welche auf interne APIs zugreifen muss. Diese ist aber offiziell dokumentiert und der Benutzer kann das auch abschalten (durch einen Security Manager, natürlich dann mit Geschwindigkeitseinbußen).

Selbstverständlich war diese Codestelle auch von den in Java 9b148 eingeführten Änderungen betroffen. Aber durch unsere enge Zusammenarbeit mit Oracle ist der Code jetzt auch ohne Kill-Switch lauffähig, und eine Lösung für Java 10 ist in Sicht!

Um Fehler in 3rd-party Libraries zu finden, haben wir schon frühzeitig damit angefangen, die Tests (und bei Elasticsearch auch den ganzen produktiven Server beim Endkunden) innerhalb eines Java Security Managers laufen zu lassen, welcher jeglichen unerlaubten Zugriff unterbindet und nur durch ein Policy-File für bestimmte Bibliotheken Ausnahmen macht. Alle nicht mehr lauffähigen Bibliotheken wurden durch bessere oder eigene Implementierungen ersetzt, sofern die Developer nicht kooperativ waren. Wer mehr darüber erfahren will, kann gerne meinen Vortrag beim letzten Java 9 Meetup in München ansehen:

So funktioniert dein Code auch mit Java 9 und Jigsaw

 

Uwe Schindler ist Mitglied des Project Management Committee im Apache-Lucene-Projekt. Er ist mit seiner Consulting-Firma SD DataSolutions GmbH in Bremen ansässig und kümmert sich am Zentrum für Marine Umweltwissenschaften (MARUM) um die Suche nach geowissenschaftlichen Daten in der Umweltdatenbank PANGAEA.Blog: http://blog.thetaphi.de. Twitter: @ThetaPh1

 

Geschrieben von
Hartmut Schlosser
Hartmut Schlosser
Hartmut Schlosser ist Redakteur und Online-Koordinator bei Software & Support Media. Seine Spezialgebiete liegen bei Java-Enterprise-Technologien, JavaFX, Eclipse und DevOps. Vor seiner Tätigkeit bei S & S Media studierte er Musik, Informatik, französische Philologie und Ethnologie.
Kommentare
  1. R.Möller2017-04-10 13:44:51

    Von weitem sieht alles einfach aus. Die Wahrheit ist, daß viele Dinge einfach nicht mehr gehen werden. Oracle arbeitet zwar daran, die funktionalen Lücken zu schliessen, aber die Welt ist eben kompliziert und es gibt jede Menge Anwendungsfälle von denen man nicht mal träumt :).
    Kein Entwickler benutzt internes API aus "Spass", sondern weil er es muss (bsp.: custom serialization, runtime byte code/class generierung, low level code [unmap MMAP'ed buffers, low level bulkoperations], zero allocation code).

    Es ist fraglich ob OSS Entwickler in der Breite bereit sind die mit einem JDK9-Port verbundenen Aufwände zu leisten. Zudem ist ein hoher Prozentsatz der verfügbaren libs nicht mehr in "aktivem" Support (aber trotzdem weiterhin nützlich). Im schlechtesten Fall verliert Java einen seiner grössten Pluspunkte: das riesenhafte Library Ökosystem.

    Auch wenn die Motivation hinter Jigsaw nachvollziehbar und schlüssig ist, würde ich in Frage stellen, daß pre-Jigsaw die Probleme tatsächlich derart gross waren um einen solchen Umbau/Aufwand zu rechtfertigen, der zudem einige generische/Meta-Konzepte nahezu verunmöglicht.
    Zudem glaube ich (kann es natürlich nicht faktisch unterlegen), daß Jigsaw als Massnahme in der Java-OSS community nicht mehrheitsfähig ist/war, es gäbe vieles was wichtiger gewesen wäre als Jigsaw (weitere Sprachmodernisierung, eine GC die mit large Heaps >> 100GB umgehen kann, static compilation, Value types etc., async/await, JS-interop, ... ).

    Aus der Sicht eines Projektes/Bibliothek-Autors bringt Java 9

    * nur Aufwand ohne konkreten Nutzen.

    * nach erfolgter Portierung ist der Code im besten Falle gleich schnell in vielen Fällen aber deutlich langsamer.

    * sind manche auf Reflection und Code-waving basierende Konzepte nicht mehr (oder nur mit Mehraufwand) umsetzbar.

    * viele Libraries können nicht mehr genutzt werden bzw. müssen in Eigenarbeit auf JDK9 gehoben werden.

    * es müssen Multi-JDK-target versionen gepflegt werden

    Es wäre daher klüger gewesen, Jigsaw mit "nützlichen" Features zu bündeln (etwa Lambdas zusammen mit Jigsaw).

    Meine Prognose:

    * der "Kill-Switch" wird für immer da bleiben :)
    * der Anreiz neue Projekte auf weniger dogmatischen Plattformen zu bauen (golang, ES6/V8, Kotlin, ..) wird erhöht, wir werden u.U. einen schleichenden Exodus sehen.

  2. TestP2017-04-10 19:14:48

    Ich sehe das nicht ganz so kritisch. :)

    Das Modul-System ist in gewisser Weise eine konsequente Weiterführung von statischer Typisierung: es minimiert die Wahrscheinlichkeit für Fehler, die erst zur Laufzeit auftreten. Ich habe schon öfter mit ClassDefNotFound-Exceptions etc. zu kämpfen gehabt. Sei es weil ein jar im Classpath fehlte oder die falsche Version geladen wurde etc. Sowas ist jetzt sofort sichtbar. Man kann jetzt auch mehrere Versionen einer Bibliothek laden, ohne dass diese sich ins Gehege kommen.

    Dann dürfte es kein Problem für die gängigen IDEs sein automatisch passende module-info.java zu erstellen. Das Modulsystem unterscheidet sich an sich kaum von Maven. Da beschreibt man auch Abhängigkeiten und was das Modul so alles exportiert. Sprich, Erfahrung ist da reichlich vorhanden.

    Und was Reflection angeht, das trifft doch nur die Software, die sich irgendwelche internen Klassen krallt, welche sowieso nie für normalen Gebrauch gedacht waren. Denn wenn sie es wären dann wären sie nicht intern. Reflektiver Zugriff auf offene/offizielle Klassen/APIs dürfte weiterhin problemlos funktionieren.

    Grüße

  3. R.Möller2017-04-10 19:47:48

    najut, vielleicht habe ich etwas übertrieben, aber es gibt einige Fälle wo das strikte Accessmanagement wirklich zu Problemen führt. Hierbei geht es weniger um den Zugriff auf interne API als um die Möglichkeit generisch auf Objektgraphen zu operieren (zugriff auf private Felder, Instantiierung privater Klassen).

    Beispiel sind Serialisierungsbibliotheken wie kryo oder fast-serialization, generische Bindings für cross-language Schnittstellen, Abstraktionsschichten a'la Hibernate, etc. .

  4. Christoph Engelbert2017-04-11 08:07:51

    @TestP "Man kann jetzt auch mehrere Versionen einer Bibliothek laden, ohne dass diese sich ins Gehege kommen."

    Irrglaube, kann man nämlich genau nicht. Der Module Path ist per default flat, ergo landet alles in einem ClassLoader. Selbst 2 Module mit dem selben private! Package sind nicht ohne weiteres möglich.

    Was geht, ist Mut Layern entsprechend Separations zu bauen, das kann ich aber auch mit ClassLoader oder bequemer per OSGi.

    Ansonsten stimme ich R.Möller zu, dass eine Menge Libraries (nicht nur opensource auch ex commercial) nicht mehr gepflegt aber weiterhin zu Hauf eingesetzt wird (hier vor allem die gekauften). Allerdings denke ich auch, dass Jigsaw an sich eine gute Sache ist, nur eben nicht das, als was man es uns verkaufen will, das Beste seit geschnitten Brot.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.