Gedanken zum aktuellen Log4j-Problem

Viele haben sich schon zu unterschiedlichen Aspekten des aktuell in der Sonne der Aufmerksamkeit stehenden Log4j-Remote-Code-Execution-Problems-durch-clever-formulierten-User-Input-der-geloggt-wird geäußert. Mir fällt dabei vor allem auf, dass die meisten Kommentatoren zwar kleine Teilaspekte des zugrundeliegenden Problems meist korrekt beschreiben, es aber an abwägendem sowohl-als-auch eher mangelt. Denn m.E. ist diese Misere keins von den Problemen, die eine einfache Lösung haben.

Ich kann mit vielen der bereits von Anderen genannten Teilaspekte mitgehen. Ja, in Java kann man z.B. durch fortgeschrittene Featuritis und Libraritis und Frameworkitis sehr komplexe Lösungen bauen, die keiner mehr durchschaut. Aber das geht in praktisch jeder Programmiersprache, außer in denen, die schon unhandlich werden, sobald man versucht mäßig komplexe Probleme zu lösen. Hier ein Java-Spezifikum zu sehen ist komplett absurd. Ja, 3rd-party-Abhängigkeiten die man nicht durchverstanden hat sind potenziell gefährlich. Aber selbstgebaute Lösungen sind ebenfalls gefährlich. Ja, der Zeitdruck und die inadäquate Bezahlung in vielen Softwareprojekten ist eine der vielen Problemursachen. Aber auch ohne Zeitdruck und mit unendlichen Mitteln können Katastrophen programmiert werden. Ja, man sollte danach streben, möglichst einfache Lösungen zu bauen. Aber einfache Lösungen, die gleichzeitig problemadäquat sind, fallen auch nicht vom Himmel und brauchen Zeit und Geld – Vereinfachung ohne es zu einfach zu machen ist eine hohe Kunst.

Beim gerade aktuellen Problem kann man es sich einfach machen und sagen “was kann an Logging schon so schwer sein”. Ja dann nehmt doch einfach System.out.println, wenn das ausreichend ist. Kurze Zeit später werdet ihr feststellen, dass es ganz nützlich wäre, mehrere Logkanäle zu bedienen. Den Loglevel von außen konfigurierbar zu machen. Beim Schreiben von Logfiles jedes einzelne File nicht zu groß werden zu lassen. Auf Effizienz beim Loggen achten, also am besten asynchron arbeiten. Und die Log-Datenbank xy anbinden. Für das Loganalysetool der Wahl den Timestamp der Logmeldung parametrierbar zu machen. Ich glaube nicht, dass eine signifikante Anzahl der Log4j-Features “einfach so” eingebaut wurden, da steckt schon jeweils ein valider Usecase dahinter. Und es hat seinen Grund, warum es nicht nur Log4j gibt, sondern auch JUL oder Logback oder tinylog – oder der gute alte IBM LogManager, falls den noch jemand kennt. Wer die Features eines Log4j nicht will oder braucht, versteckt sich hinter slf4j und überlässt dem Kunden die Auswahl der finalen Logimplementierung – aber öffnet gleichzeitig einen interessanten neuen Angriffsvektor, wenn Schadcode beim Deployment als slf4j-implementierendes Jar eingeschleust wird. Und wenn ich dann nach aufwändiger Analyse zum Schluss komme, dass Logback alle meine Wünsche erfüllt, kommt morgen eine neue Anforderung um die Ecke, die nur durch Log4j abgebildet werden kann – was dann? Anforderung ablehnen? Lib wechseln? Von vorne anfangen?

Man soll sich keinen Illusionen hingeben. Was bei Log4j passiert ist, kann mit jeder Programmiersprache, auf jedem Betriebssystem, ja sogar auf jeder CPU passieren. Spectre konnte über JavaScript im Browser ausgenutzt werden – noch so viele Schichten zwischen Quellcode und Ausführung haben uns nicht vor den Auswirkungen eines CPU-Bugs geschützt. Schon im guten alten Z80 gab es “illegale” Opcodes, die trotzdem etwas Vernünftiges gemacht haben, als Seiteneffekt der Implementierung. Nur 8500 Transistoren, und trotzdem ein solcher “Bug” (oder “Feature”, je nachdem wen man fragt). Der aktuelle Apple M1 Max besteht aus 57 Milliarden Transistoren. Natürlich nicht direkt vergleichbar, weil jede Menge Cache damit realisiert ist, aber auch durch Caches können ja interessante Effekte entstehen (Stichwort Seitenkanalangriff). Jedenfalls ein geeignetes Beispiel für die immens gewachsene Komplexität auf allen beteiligten Ebenen.

Ich bin auch ein Fan davon, mit möglichst wenigen Abhängigkeiten in meinen Java-Anwendungen auszukommen. Aber diese Strategie trägt eben auch nicht unendlich weit. Beispiel: wenn Persistenzierung in einer Datenbank gefragt ist, kann man von Hand JDBC machen oder Hibernate verwenden oder JPA oder JDO oder was auch immer das Framework der Wahl so mitbringt. Oder man speichert seine Daten in einer simplen Datei. Oder doch über BerkeleyDB oder SQLite? Wenn die Anforderung lautet, einen Webservice zu schreiben, beginnt man dann mit seinem eigenen Server und seiner eigenen Security-Schicht, oder nimmt man doch was von der Stange und vertraut darauf, dass die Profis es entwickelt haben? Ist eine Bibliothek, die hundert unnütze Funktionen zusätzlich zu den für mich nützlichen zehn bietet, denn notwendigerweise die falsche Wahl, wenn die Konkurrenzbibliothek mit nur zwanzig unnützen Funktionen z.B. closed source ist oder viel weniger Nutzer hat oder eine viel kleinere Community? Oder alles drei? Ist es wirklich sinnvoll, sich auf den Featureumfang der Java-Plattform zu beschränken und sklavisch auf 3rd-party-Libs zu verzichten – ist denn sichergestellt, dass die “Bordmittel” wirklich von höherer Qualität sind als Drittbibliotheken?

Auf Basis von Post-hoc-Erkenntnissen klug daher schwätzen (“hättet ihr mal nicht Log4j verwendet” – ersetze Log4j durch “Intel-CPU” oder “OpenSSL” oder “PHP” oder “Linux” oder “Docker” oder “IIS” oder “Applets”) kann jeder. Aber was ist die “richtige Strategie” im Angesicht des Ungewissen, wenn man vor einer erheblichen zu implementierenden Komplexität steht, die nicht mal schnell ein Profi-Entwickler im Alleingang und minimalen Abhängigkeiten hindengelt (und dieser Profi dann auch dauerhaft für die Wartung zur Verfügung stehen muss)? Es wird letztlich immer eine Abwägungsfrage bleiben. Tritt kein Problem auf, war die Abwägung tendenziell richtig – oder man hat einfach nur Glück gehabt.

Vermutlich ist das die einzige vielversprechend Strategie. Einfach Glück haben.

Da fällt mir ein: schon 2015 habe ich unter der schönen Wortschöpfung “Abstraktionskaskade” diese Problematik beleuchtet. Und schon damals war ich nicht der erste.

Goodbye, Schily – ein Nachruf

Die traurige Nachricht von Jörg Schillings viel zu frühem Tod hat nun auch mich erreicht. “Schily”, so sein Kürzel beispielsweise im Heise-Forum oder generell bei den Releases seiner zahlreichen nützlichen Tools, starb in viel zu jungen Jahren an Krebs.

Ich hatte nie persönlichen Kontakt mit ihm, obwohl wir beim Thema “Brennersoftware” ja größere Überschneidungen hatten. Sein cdrecord ist nach wie vor der Gold-Standard unter den freien Brennprogrammen. Ja, ich hatte sogar in der Anfangszeit mal einen Blick in die Sourcen geworfen, um herauszufinden, wie man diese Yamaha CDR100/102-Brenner und den Teac CD-R50S zum Brennen überreden könnte. Aus potenziellen Lizenzunverträglichkeitsgründen habe ich das aber mit Beginn der CDBurn-Entwicklung fürderhin unterlassen – nicht etwa nur, weil das Lesen von C-Code irgendwann das Hirn verschwurbelt. Ich muss zugeben, ich war immer ein wenig neidisch darauf, dass er es geschafft hatte, dass diverse Firmen wie Sanyo, LG, Sony, Plextor und Yamaha ihm kostenlos und dauerhaft Laufwerke zu Test- und Entwicklungszwecken zur Verfügung zu stellten. Was aber nur gerecht war, denn ein portierbares Multi-OS-Brenntool ist im großen Spiel der Dinge ganz sicher von größerem Interesse als eine kommerzielle Implementierung auf einem Randgruppenbetriebssystem wie RISC OS.

Irgendwann Ende der 90er oder Anfang der 00er Jahre hatte ich mal eine kurze Unterredung mit einem GPL-/Linux-Fan, der mich überreden wollte, doch CDBurn unter der GPL und für Linux zu veröffentlichen. Auf meine überraschte Nachfrage, warum das im Angesicht von cdrecord eine gute Idee sein sollte, erfuhr ich zum ersten Mal von der eher GPL-ablehnenden Haltung von Schily und seiner auch sonst meinungsstarken und damit wenig kompromissbereiten Persönlichkeit. Und nach näherer Beschäftigung mit seinen Argumenten konnte ich Schilys Standpunkten letztlich zustimmen, ebenso den meisten seiner Einlassungen in diversen Foren zu diesem Thema. Auch wenn ich es nie zu seinem Grad der GPL-Ablehnung geschafft habe – meine kritische Einschätzung der GPL und der grundsätzlichen Schwierigkeiten bei der Interpretation dessen, was diese Lizenz in welchem Szenario nun zulässt oder nicht, ist für immer geblieben.

Immer streitbar und trotzdem produktiv. Ich werde Schily in guter Erinnerung behalten. Session closed.

Zwischenprojekt: KVMPortSwitcher

Meine Entwicklungsstrategie für private Projekte folgt meist einer Stack-Strategie: je älter die Idee, desto tiefer vergraben, ständig kommt neues Zeugs oben drauf, und in seltenen Fällen findet das jüngste Projekt direkt zur Reife. Und so geschah es mit dem jüngsten Spross meines bunten Straußes an Java-Projekten: KVMPortSwitcher. Wer nur den technischen Sermon lesen und den Code sehen will, darf direkt zum Projekt auf GitHub abbiegen.

Um was geht es? Seit einiger Zeit besitze ich einen “TESmart 16-port HDMI KVM Switch” (das exakte Modell gibt es offenbar nicht mehr, aber hier ist der Nachfolger zu finden). TESmart ist der einzige mir bekannte Hersteller, der KVM-Switches mit 8 Ports und mehr zu einigermaßen bezahlbaren Preisen anbietet und trotzdem problemlos funktioniert – meine Anforderungen damals waren “4K, HDMI, 8 Ports oder mehr, keine Spezialkabel”. Dadurch schrumpfte die Zahl der zur Auswahl stehenden Geräte nach ausführlicher Recherche auf “1”.

Nun gehört alles mit 8 Ports und mehr offenbar üblicherweise der Profi-Fraktion an, und so hat dieser Switch zusätzlich einen Ethernet-Anschluss, mit dem man ihn ins LAN integrieren kann, um ihn so über IP steuern zu können. Warum würde man das wollen? Zum einen zwecks Konfiguration des Geräts. Aber vor allem zur Steuerung des ausgewählten Ports. Denn die anderen Wege, den Port zu wechseln, sind mit verschiedenen Problemen und Nicklichkeiten verbunden, die einem ganz schön auf die Nerven gehen können.

Man kann die Knöpfe an der Front des KVM-Switches verwenden. Prinzipiell keine schlechte Idee, aber beim 16-Port-Modell braucht man dabei für die Geräte 10-16 zwei Tastendrücke. Oder sogar drei, denn wenn der Display-Timeout zugeschlagen hat, dient der erste Tastendruck gar nicht dem Umschalten, sondern dem Aktivieren. Also zwei oder drei Tastendrücke, und man muss natürlich die Hand von der Tastatur nehmen.

Wie wäre es mit der IR-Fernbedienung? Anders, aber nicht besser. Wenn man sie denn mal gefunden hat (vorsichtshalber steht nur “Remote Control” drauf, damit man sie ja nicht klar zuordnen kann), kann man zwar die Ports 2-9 durch einen Tastendruck auswählen, die Ports 1 und 10-16 hingegen nicht – was besonders bei “1” sehr dumm ist, da drückt man auf “1” und wundert sich, dass nix passiert, bis dem Gerät klar wird, dass keine Eingabe mehr erfolgt. Wer jetzt denkt, er könne durch zwei Tastendrücke “0” und “1” das Gerät überlisten, sieht sich getäuscht. auch “1” gefolgt von “OK” hilft nicht. Immerhin braucht es den “Aktivierungstastendruck” wie bei den Front-Panel-Buttons nicht.

Wie wäre es mit der eingebauten Möglichkeit, über die Tastatur den Port zu wechseln? Im Prinzip eine gute Idee, aber: es funktioniert nur mit den im Gerät hartcodierten Shortcuts, und die sind eher komisch, aber natürlich so gewählt, dass sie möglichst nicht mit anderen gängigen Tastatursteuerungen kollidieren: zwei Mal Scroll Lock (aka “Rollen”), dann 1-n. Man erkennt direkt das Problem für die 16-Port-Variante: bei Rollen-Rollen-1 gibt es die von der Fernbedienung bekannte Gedenksekunde, bis klar ist, dass man wirklich “1” meint und nicht “10” oder “13” und nur langsam tippt. Und noch ein Haken: das funktioniert natürlich nur, wenn die Tastatur im dafür vorgesehenen spezifischen USB-Port steckt, der diese Logik enthält – dieser Port ist aber nicht kompatibel mit allen Tastaturen dieser Welt, und so muss man manchmal auf den “nackten” USB-Port ausweichen. Und schon ist es Essig mit der Hotkey-Funktionalität.

Insgesamt gibt es also ausreichend Gründe, alternative Möglichkeiten zum Umschalten der Ports zu suchen. Eine davon bietet TESmart selbst an mit einem kleinen Tool, das eine IP-Verbindung aufbauen kann, dadurch den aktiven Port feststellt und dann per Mausclick das Umschalten erlaubt. Schön, aber natürlich eher keine Verbesserung zu den anderen drei Varianten. Tatsächlich dauert es doch recht lange, bis das Tool endlich den “Connect” hergestellt hat – keine Ahnung, warum. Mehrere Sekunden jedenfalls.

Nun muss man TESmart zugutehalten, dass sie das Protokoll über die IP-Schnittstelle zur Ansteuerung der Funktionalität öffentlich dokumentiert haben – leider muss man solche Dinge ja positiv vermerken, da es sich nicht um eine Selbstverständlichkeit handelt. Jedenfalls ist es denkbar einfach: IP-Verbindung herstellen, ein paar Bytes senden, fertig ist die Laube.

Ich hatte also so ein Projekt schon länger im Hinterkopf – meine Brot-und-Butter-Implementierungssprache Java schien da aber ungeeignet, weil man dort keine systemweiten Keyboard-Shortcuts beim Betriebssystem registrieren kann, und eine weitere grafische Oberfläche, wo man per Click den KVM-Switch bedienen kann, wollte ich nun auch nicht bauen. Zufällig bin ich bei meinen Streifzügen durch das Internet dann aber über eine Bibliothek namens JNativeHook gestolpert, die für alle gängigen Betriebssysteme und CPU-Architekturen native code zusammen mit etwas Java-Glue-Code zur Verfügung stellt, um solche globalen Hotkey-Listener zu implementieren. Kurz getestet, tut, also frisch ans Werk.

Der Code an sich und die UI war natürlich trivial. Kurz noch ein TrayIcon dazugeschraubt (denn für eine Anwendung, die normalerweise im Hintergrund läuft, ist es immer geschickt, noch einen Haken zur Kontrolle derselben zu haben), ausprobiert. Funktionierte – meistens. Denn es gab ein interessantes Phänomen, dass der NativeKey-Event-Dispatcher manchmal, wenn der Port umgeschaltet wurde, munter weiter denselben Event wieder und wieder feuerte. Ich bin dem noch nicht auf den Grund gegangen, aber habe einen klassischen Workaround gefunden: asynchrone Verarbeitung, der Listener löst nicht direkt den Port-Switch aus, sondern scheduled einen extra Thread, der kurz wartet und dann erst umschaltet. Das scheint das Problem zuverlässig gelöst zu haben.

Wer bis hierhin durchgehalten hat, darf jetzt zum GitHub-Projekt abbiegen. Es gibt auch ein runnable jar nebst inkludiertem jnativehook-Jar als Release 0.1.0 zum Download bei GitHub, Java 8 wird zur Ausführung benötigt. Wer also zufällig einen TESmart HDMI-KVM-Switch sein Eigen nennt und ihn in sein Heimnetz integriert (hat), kann direkt loslegen.

Auf der GitHub-Projektseite kann man auch nachlesen, was noch die TODOs sind. Konfigurierbare Port-Namen (bei 16 Ports kann man schon mal den Überblick verlieren), konfigurierbare Keyboard-Shortcuts, i18n (bis jetzt ist alles hartcodiert englisch). Und vielleicht auch noch für die old-school-Generation die Steuermöglichkeit über die serielle Schnittstelle des TESmart-KVM-Switches?

Java. Swing. Dem Wahnsinn nahe.

Seit Swing 1.0.2, als das Package noch com.sun.java.swing hieß und Java 1.1 gerade der heiße Scheiß war, programmiere ich UIs mit Java und Swing. Es ist so eine Art Hassliebe. Nach wie vor halte ich es für den einfachsten und besten Weg, Cross-Platform-Anwendungen zu schreiben, die mehr oder weniger problemlos unter Windows und Linux und MacOS funktionieren. Und an sonnigen Tagen auch unter AIX und Solaris.

Aber manchmal…manchmal…graue Haare, Stirnrunzeln beim Debugging, dem Wahnsinn nahe. Ich baue gerade an einem kleinen Projekt dessen tieferer Sinn oder Unsinn hier nichts zur Sache tun soll. Jedenfalls wird dort als Anzeigekomponente die allseits beliebe JEditorPane mit HTML-Inhalt verwendet. Das ist von jeher etwas kitzelig weil ein etwas merkwürdiges HTML-3.2-Sub-und-Superset-mit-etwas-CSS unterstützt wird, und man sollte sehr genau wissen welches HTML man reinsteckt und was unfallfrei gerendert werden kann. Immer spannend war das Thema Grafiken. Im Prinzip sollte es ja ein img-Tag mit passender URL dahinter tun. Blabla.class.getResource(“/schnickschnack.png”) liefert ja schon eine lokale File-URL zurück, was kann da schon schiefgehen.

Eine ganze Menge. Und was ist, wenn man gar keine Datei hat, sondern ein beliebiges Image in-memory? Weil das so ist, haben schlaue Menschen einen Weg ersonnen, wie man der JEditorPane – oder besser gesagt dem HTMLDocument, das über den HTMLEditorKit beim ContentType “text/html” bekanntlich entsteht – ein beliebiges Image unterjubeln kann, indem man es in einen “imageCache” unter der im img-Tag hinterlegten URL steckt.

Hier ein Link zu Sourcecode, der illustriert, wie das prinzipiell geht. Sehr heikel, weil es sich auf ein Implementierungsdetail verlässt, aber Swing ist bekanntlich tot…äh…stabil, und so wird das wohl auch in Zukunft problemlos funktionieren. Dachte ich.

Wenn man nun aber obigen Sourcecode mal in der IDE der Wahl compiliert und zur Ausführung bringt, erlebt man leider eine Überraschung: mit Java 7 funktioniert das noch problemlos, mit Java 8 und neuer aber nicht. Ein beherztes Debugging an der richtigen Stelle – javax.swing.text.html.ImageView.loadImage – brachte keinen Aufschluss – das Image wird korrekt aus dem Cache geholt, seine Größe wird inspiziert, es wird layoutet, und dann…wird nichts angezeigt.

Ich will es nicht zu spannend machen, denn es gibt einen Workaround. Auch wenn ich gerade überhaupt nicht verstehe, warum der was ändert, weil genau das, was er eventuell ändern könnte, im Debugging nicht nach dem Problem ausgesehen hat.

Wenn man die Zeile

"<img src=\""+localImageSrc+"\">\n" +

ändert in

"<img src=\""+localImageSrc+"\" width=100 height=50>\n" +

dann funktioniert es plötzlich prima von Java 7 bis einschließlich Java 16.

Wer auch immer das im Detail analysieren will – Freiwillige vor.

SwingHelpViewer – ein einfacher Online-Hilfe-Viewer

Seit einiger Zeit schraube ich an diversen Anwendungen auf Basis von Java Swing, die wirklich demnächst die Release-Reife erreichen sollten. Die meisten aus der Reihe “für RISC OS, aber nicht unter RISC OS lauffähig” (da Java-basiert, und es unter RISC OS keine kompetente JVM gibt). Hier kann man ein paar wenige Details zu diesen geplanten Anwendungen nachlesen.

Wie dem auch sei, solche klassischen FatClient-Anwendungen brauchen typischerweise eine Online-Hilfe. Allein um dem Benutzer mitzuteilen, welche 3rd-Party-Bibliotheken genutzt werden und unter welcher Lizenz diese stehen.

Standard unter Java ist für solche Zwecke JavaHelp, vor Urzeiten von Sun entwickelt, aber seit fast so langer Zeit auch nicht weiter gepflegt. Diverse ungefixte Bugs, unklare Lizenzsituation (alle Sourcefiles reden von GPLv2, irgendwo steht “GPLv2 with Classpath Exception”, und es gibt auch Hinweise, dass es dual lizenziert ist sowohl unter GPLv2 als auch unter CDDL), merkwürdige indexbasierte Suche die außer Englisch recht wenig versteht, unflexibel in der Integration. Aber durchaus mit coolen Features für die damalige Zeit, inklusive einer JSP-basierten Server-Variante.

Allein die Lizenzsituation bewog mich, schnell mal was Eigenes zu bauen. Mit dem typischen “wie schwer kann das schon sein” auf den Lippen begann ich während eines langweiligen Fußballspiels (es könnte der VfB Stuttgart involviert gewesen sein), schnell mal einen Hilfe-Viewer ähnlich des JavaHelp-Viewers zu bauen. Ungefähr 10 Stunden später hatte ich einen Viewer, der ein JavaHelp-HelpSet (.hs-Datei) parsen und sowohl TOC als auch Index eines solchen HelpSets erfolgreich darstellen. Es war also tatsächlich nicht so schwer, am längsten hat noch das XML-Gefrickel gedauert, weil ich mir in den Kopf gesetzt hatte, ganz ohne 3rd-Party-Dependencies auszukommen, und das DOM-Zeugs in Java SE ist halt sehr mit-der-Hand-am-Arm.

Code aufräumen, Icons malen und jetzt seit neuestem eine einfache Volltextsuche dazuzubauen hat nochmal ein paar Stunden gekostet, alles in allem schätze ich mal 3 PT an Gesamtaufwand. Swing ist wohl doch nicht so schlecht wie viele seit etwa 1999 nicht müde werden zu behaupten.

Das Ergebnis dieser Bemühungen kann man auf GitHub inspizieren. Und damit es wirklich von jedem auf welche Weise auch immer verwendet werden kann, habe ich als Lizenz die “Unlicense” ausgewählt. Ist bestimmt auch wieder inkompatibel mit irgendwas, aber es ist die freieste Lizenz, die ich gefunden habe.

Wird es für den SwingHelpViewer eine Weiterentwicklung geben? Natürlich liegen einige Erweiterungen auf der Hand (indexbasierte Suche z.B. über Apache Lucene, ein Bookmark-System, Unterstützung für weitere JavaHelp-Features wie “merged help sets” und Direkthilfe-Action und Hilfe-Cursor und so, Unterstützung für andere Hilfedaten wie CHM, HLP oder Eclipse Help, Einbindung anderer Anzeigekomponenten die mehr von HTML und CSS verstehen als JEditorPane), aber der jetzige Zustand dürfte für meine Zwecke wohl reichen.

Wer die Herausforderung sucht, könnte z.B. mal schnell eine Java 1.2-kompatible Version des Codes bauen. Im Moment läuft alles ab Java 7, wenn man den Diamond Operator rauswirft müsste Java 5 gehen, wenn man die Generics rauswirft Java 1.4, und wenn man das XML-Parsing anders löst (wer erinnert sich noch an crimson.jar…), dürfte man bei Java 1.2 angekommen sein. Der Code ist sehr “old-school”, und Swing ist ja im Prinzip seit Java 1.2 unverändert.

Was GitHub angeht, ist SwingHelpViewer mein erstes dort publiziertes Projekt. Wenn ich die jetzige Geschwindigkeit beibehalte, werden meine über die Jahre entstandenen programmiertechnischen Ergüsse spätestens bis 2050 komplett auf GitHub gelandet sein.

Apple setzt auf ARM

Auf der WWDC (die jährliche Apple Entwickler-Konferenz) wurde in der Keynote wie schon seit Längerem allgemein erwartet (erste Gerüchte etwa seit 2011) verkündet, dass zukünftige Macs als CPU ein selbstentwickeltes SoC auf ARM-Basis enthalten werden. Höchstwahrscheinlich ein performanceoptimierter Abkömmling der A12Z-und-Nachfolger-Reihe aus dem iPad Pro.

Viel wurde schon geschrieben über Apples erneuten Pferdewechsel bei der CPU für seine Desktoprechnerlinien – MOS 6502 im Apple I und II, Motorola 68xxx im Mac der ersten Generation, Motorola/IBM PowerPC in den PowerMacs ab Mitte der 90er, und schließlich Intel x86/x64 seit Mitte der Nullerjahre. Und jetzt also ARM. Für alle historisch interessierten schließt sich damit der Kreis, der mit der Ausgründung von ARM Ltd. aus Acorn als Joint Venture mit Acorn und VLSI Anfang der 90er begann, und Apple den ARM610 als CPU im Newton PDA einsetzte – damals mit mageren 20 MHz getaktet, der finale Newton hatte dann einen schnellen StrongARM intus.

Meine Einlassung hier soll auch nicht als Expertenmeinung im MacOS-Universum eingestuft werden – ich habe wenig Plan von und Einblick in MacOS und der derzeit dort so eingesetzten Software. Der Presse entnehme ich, dass im Prinzip ausschließlich mit den MacBooks der Gewinn eingefahren wird und der Rest eher so nebenher läuft. Das traditionelle Profilager der Kreativen, von DTP über Illustrationen bis Bitmap-Bearbeitung scheint im großen Spiel der Dinge inzwischen eher nachrangig. Photoshop, Final Cut Pro, InDesign, Illustrator, QuarkXPress, Premiere Pro, Finale…sowieso allesamt auch unter Windows erhältlich und wohl nicht mehr der entscheidende Faktor bei der Useranzahl und damit auch dem Umsatz. Die Lifestyle-Kundschaft, die einfach alles aus dem Apple-Universum kauft was nicht bei drei auf dem Baum ist – von MacBook über iMac, Apple Watch, HomeKit, iPhone bis iPad und Apple TV – ist der Umsatztreiber.

Was treibt also Apple zu diesem Wechsel, der ja nicht ohne Risiko ist? Ich kann da einige Gründe ausmachen. Wie man die jeweils gewichtet, liegt im Auge des Betrachters. Wir werden erst hinterher schlauer sein.

Zunächst ganz schnöde: der Wechsel spart vermutlich einen Haufen Geld. Intel-CPUs sind ja doch eher teuer, und Apple musste immer Chipsätze und Zusatzchips drumrum entwickeln oder zukaufen. Die ARM-Entwicklung erfolgt ja sowieso aufgrund von iPhone und iPad, warum dann nicht etwas nach oben skalieren. In Zeiten von “viele Cores helfen viel” ist ein ARM-Core da sicher keine unbedingt schlechte Basis. Es heißt ja, dass die letzte Inkarnation des iPad Pro CPU- und GPU-technisch mit den mobilen Core i-CPUs locker mithalten kann.

Wichtig erscheint mir auch, dass Intel in den letzten Jahren seinen ehemaligen Vorsprung bei der Prozesstechnik eingebüßt hat. Die Auftragsfertiger wie TSMC oder Samsung haben Intel mindestens eingeholt, eher überholt. Angeblich hat TSMC den 3nm-FinFET-Prozess 2022 produktionsreif. Allerdings ist das nicht ohne Risiko, denn andere große Auftragsfertiger haben beim großen Technologierennen schon abreißen lassen müssen – Globalfoundries dürfte der bekannteste Fall sein, früher ganz vorne dabei als Ausgründung von AMD, aber inzwischen nur noch Massenfertiger ohne Spitzenfertigungstechnologie. Jedenfalls ist die Verfügbarkeit von Auftragsfertigern mit topaktueller Prozesstechnologie eine Voraussetzung für den Erfolg von “fabless CPU designers” wie ARM, Qualcomm, Apple oder AMD. Dass dieses Pendel mal wieder in Richtung Intel zurückschwingt ist jetzt nicht völlig ausgeschlossen.

Viel wird über die Vereinheitlichung der Plattformen iOS und macOS geschrieben. Aus meiner Sicht ist das ein schwaches Argument, denn wo genau ist denn hier die zugrundeliegende CPU-Architektur entscheidend? Entscheidend ist doch hier vielmehr, ob sich die Benutzeroberflächen angleichen und das Software-Ökosystem aus Bibliotheken und Frameworks möglichst identisch ist. Ob dann am Ende der Compiler x86-Code oder ARM-Code draus macht, ist doch weitgehend irrelevant. Vielleicht werden die allermeisten Programme bis in 10 Jahren sowieso alle im Browser laufen und in JavaScript und WebAssembly geschrieben sein, dann haben sich solche Details eh erübrigt.

Ein aus meiner Sicht wichtigeres Argument ist, dass sich Apple der einfachen Vergleichbarkeit – besonders was den Preis angeht – seiner Hardwareplattform entzieht. Heutzutage ist komplett transparent, dass Apple gegenüber der x86-Welt für seine MacBooks einen nicht unerheblichen Aufpreis nimmt, der sich nicht in erhöhter CPU- oder GPU-Leistung niederschlägt. Der Wechsel zu ARM nützt hier zweifach: zum einen kann Apple sehr einfach Coprozessoren aller Art im SoC unterbringen, die speziell auf die neueste Modeerscheinung der IT hingedengelt wurde – sei es Spracherkennung, Bildverarbeitung, Video-Encoding oder irgendwas-mit-KI. Zum anderen kann Apple zukünftige Herausforderungen durch einen cleveren Mix aus Software und Hardware erschlagen, ohne dass jemand genau abgrenzen kann, ob jetzt die geniale Programmierung oder die geniale Hardware letztlich verantwortlich ist.

Dadurch, dass Apple von ARM eine Architektur-Lizenz hat, also völlig eigenständige Cores entwickelt und nicht auf die fertigen Cortex-Axx- oder Neoverse-Cores zurückgreifen muss, können die CPUs für MacOS und den muss-nicht-unbedingt-allzu-stromsparend-sein-Anwendungsfall maßgeschneidert werden. Das ist bei iOS ja auch passiert – während die Android-Fraktion eher auf viele Core gesetzt hat, hat Apple eher die Single-Core-Leistung in den Vordergrund gestellt. Welche Reserven die Architektur hat, wenn man die TDP in Richtung Intel Core i9 erhöhen kann, bleibt abzuwarten. Der A12Z mit 8 Cores in big.LITTLE-Konfiguration, je 128 KiB 1st level instruction und data cache und 8 MiB 2nd level cache ist ja noch eher auf Sparsamkeit und lüfterloses Design ausgelegt. Bechmarks sahen den A12Z ja ungefähr auf Augenhöhe eines Mobile-i5 mit integrierter Intel-Grafik. Technisch ist das der Stand von 2018, da der A12Z nur ein leicht erweiterter A12X ist. Mal sehen, mit was Apple um die Ecke kommt, wenn es ernst wird mit dem ersten MacBook-on-ARM.

Apple hat in der Vergangenheit durch das Backen ganz eigener Brötchen ja große Erfolge gefeiert und ein immer geschlosseneres Ökosystem geschaffen. iOS, Objective-C, Swift, Metal, Apple Store, iTunes, Apple TV, HomeKit – solange die Nutzeranzahl eine kritische Masse überschreitet und das Ökosystem technisch und aus Anwendersicht plausibel bleibt, ist es eine clevere Art von “Vendor Lock-In”.

Ein Risiko der Apple-Strategie steckt natürlich in dem, was in der Industrie unter “zu große Fertigungstiefe” läuft. Apple muss alles selbst entwickeln, vom Betriebssystem (iOS und MacOS) über CPU, GPU und Zusatzprozessoren bis hin zu Compilern (Objective-C, Swift) und natürlich der Hardware selbst. Das birgt eine irre Komplexität und hohen Aufwand in sich, um in jedem einzelnen Bereich Spitze oder zumindest nahe dran zu sein. Firmen, die das früher mal versucht haben, waren dauerhaft nicht erfolgreich (Sun, SGI, Acorn, mit Abstrichen auch die klassischen Heimcomputerhersteller wie Commodore und Atari).

Eine gewisse Ironie steckt im Zeitpunkt des jetzt angekündigten Wechsels. Gerade hat – dank AMD – die x86-Welt wieder deutlich an Schwung gewonnen nach fast einem Jahrzehnt gefühlter Stagnation. Ein wenig vergleichbar mit dem damaligen Wechsel von PowerPC zu Intel, als kurze Zeit später IBM ein wahres Feuerwerk an High-Performance-PowerPCs abbrannte. Aber wie wird es sich längerfristig entwickeln? Apple hat auf jeden Fall genügend finanzielle Ressourcen, um seine ARM-SoCs auf Augenhöhe mit den x86-CPUs von Intel und AMD weiterzuentwickeln. Aber das alleine ist keine Garantie, wie man gerade an Intel sieht, die trotz gigantischer finanzieller Ressourcen eher alt gegen AMD aussehen. Kopf schlägt Kapital, zumindest manchmal – oder wie jeder Fußballfan weiß: “Geld schießt keine Tore”, und trotzdem heißt der Deutsche Meister auch 2020 wieder Bayern München. Und andererseits ist Freiburg auf einem einstelligen Tabellenplatz in der Bundesliga, während der Hamburger SV, der VfB Stuttgart und Hannover 96 in der zweiten Liga spielen. Es bleibt also spannend.

Stümpern mit Niveau

Ab und zu – die Frequenz ist uneinheitlich, die Rahmenbedingungen unbekannt – neige ich dazu, irgendein IT-Thema mal genauer zu betrachten und ein paar Experimente durchzuführen. Quasi um immer wieder das “innere Komplexitätsgefühl” neu zu justieren. Ich stelle eine gewisse Häufung beim Thema “Protokolle” fest, vielleicht die masochistische Ader die mich schon zur Implementierung einer CD-/DVD-/BluRay-Brenner-Software gebracht hat. Ziel ist immer, bei einer Sache von Quasi-Null-Wissen auf mindestens 3 zusätzliche Seiten im persönlichen Handbuch des nutzlosen Wissens zu kommen. Quasi qualifiziertes Halbwissen, das jede Fachdiskussion unter einer Stunde locker überlebt.

Diesmal das Thema: IPP, das “Internet Printing Protocol”, das im Rahmen einer Diskussion im Kontext meines Lieblingsbetriebssystems RISC OS eher zufällig aufgekommen ist. Früher dachte ich, IPP sei quasi nur “Druckdatenstrom per HTTP an den Drucker übermitteln”. Sowas wie “JetDirect über HTTP”. Oh Mann, lag ich falsch. Oder besser: es ist eine sehr simplifizierte Beschreibung der Wirklichkeit, und qualifizierte mich damit als “Quasi-Null-Wissenden”.

In Wahrheit hat IPP interessantes Potenzial für die RISC OS-Welt, nämlich “driverless printing”. Man kann per standardisierten Requests den Drucker zu seinen Eigenschaften befragen (Papierzufuhroptionen, Nachbearbeitungsoptionen, Duplex oder nicht und wenn ja wie), zu seinem Status (Tonerstand/Tintenstand, Papierstau etc.) und es gibt ein plattformübergreifendes Rasterformat (“PWG Raster Format” – PWG ist die “Printer Working Group”, die hinter IPP steht), das zum Druck verwendet wird. Zusätzlich vermelden netzwerkverbundene Drucker ihre Anwesenheit über das Bonjour-Protokoll, aber auch direkt verbundene USB-Drucker werden unterstützt. Das Gesamtkunstwerk nennt sich IPP Everywhere. Also die Lösung aller Probleme der Art “klar gibt es da diesen Drucker XYZ, aber für Betriebssystem ABC gibt es leider keine Treiber” – heute behilft man sich da (zumindest im RISC OS-Markt) mit der oberen Preisklasse der PostScript- und PDF-fähigen Drucker, muss aber immer noch tricksen um mit speziellen Druckerfeatures arbeiten zu können.

Die IT wäre nicht die IT, wenn nicht zwei konkurrierende andere Lösungen gleichzeitig um die Gunst des Anwenders (interessiert den das eigentlich?) buhlen würden: AirPrint (von Apples Gnaden) und Wi-Fi Direct Print. So weit, so ärgerlich. Zurück zu IPP. Dessen Charme ist die Verfügbarkeit eines gut dokumentierten Standards, bei den beiden Konkurrenten sieht es da nicht ganz so gut aus, dafür waren diese früher am Markt und sind (deshalb?) deutlich weiter verbreitet – IPP Everywhere wird heute eigentlich nur von einer Reihe von HP-Druckern unterstützt.

Wie auch immer, ich fasste ein Ziel ins Auge: mit meinem IPP-fähigen (aber natürlich nicht IPP Everywhere-kompatiblen, weil Baujahr 2011) Oki C530 ein PDF zu drucken, indem über IPP der PDF-Datenstrom an den Drucker geschickt wird.

Wie anfangen? Der IPP-Guide erzählt von 40 Spezifikationen mit insgesamt fast 2000 Seiten, das schien jetzt etwas ambitioniert. Aber es gibt JIPP, eine Java-Implementierung von IPP von HP, verfügbar auf GitHub. Unter der guten MIT-Lizenz. Da waren Beispiele dabei. Wie schwer kann es also sein?

Erste Hürde: das Github-Projekt hostet nur Source-Releases. Kein Problem, man kann ja selber bauen. Stellt sich raus: ist Kotlin-Code, gebaut mit Gradle. Hm. Bin ich jetzt für beide Dinge nicht so der Experte, vielleicht ein Experiment für ein anderes Mal. Google führt mich zu einer sinnvollen Paketierung des Codes inklusive aller seiner Abhängigkeiten (wie der Kotlin-Runtime – auch nicht gerade schmal das Dingens). Also schnell ein IDE-Projekt aufgesetzt und los geht’s. Ich arbeite mit Version 0.6.16 – nur falls jemand ein paar Jahre später über diesen Artikel stolpert und bis hierher durchgehalten hat.

Das jprint-Beispiel (inzwischen übrigens – ohne meinen Input und erst vor wenigen Stunden! – signifikant verbessert, an einigen der Stolperfallen) erst mal als Basis genommen. Ein PDF an die bekannte IPP-URL des Druckers geschickt. Eine dubiose Exception war das Ergebnis (aus dem HTTP-OutputStream: java.io.IOException: Error writing request body to server) – wies darauf hin, das irgendwas mit der URL nicht stimmte. OK. Ein paar Varianten durchprobiert (offenbar gibt es typische Implementierungen der Form ipp://irgendneip/ipp und ipp://irgendneip/ipp/lp und http://irgendeineip/ipp und http://irgendeineip:631/ipp – an dieser Stelle die Nebenerkenntnis, dass IPP schlicht HTTP-mit-631-als-Default-Port ist). Manchmal den gleichen Fehler, manchmal einen anderen Fehler (java.net.SocketException: Software caused connection abort: recv failed). Dubios. Weiter rumprobiert. Stellt sich raus: auch bei derselben URL kommt manchmal die eine Exception, manchmal die andere. Aha. Dann doch kein Connection-/URL-Problem? Mal den Code genauer anschauen. Als Transportmechanismus wird eine Klasse namens “IppPacket” verwendet. Da gibt es einen alternativen Konstruktor, der auch eine “ippVersion” (ein int wie es sich gehört) entgegen nimmt. Könnte es sein, dass mein Drucker (aus der Zeit von IPP v1.1) mit dem Default von JIPP (IPP v2.x) einfach nicht klarkommt? Mehrfaches Ausführen des Codes mit stets derselben URL führt tatsächlich manchmal zu einer Exception-losen Verarbeitung des Kommandos mit der Fehler-Rückmeldung “server-error-version-not-supported”, was den Verdacht erhärtet.

Etwas später ist klar: dummerweise erlaubt der Konstruktor von IppPacket, der eine explizite Versionsangabe erlaubt, nicht dieselben Typen an Restparametern, insbesondere nicht den “Operation”-Enum, sondern auch nur einen int. OK, ein paar RFCs zum IPP gelesen und herausgefunden, dass das Kommando getPrinterAttributes 0x0B ist und printJob 0x02. Minuten später sehe ich, dass der Enum innen drin die ints eh auch definiert hat. Grmpf. Ist bestimmt so ein Kotlin-Automatismus. Bleibt die Frage: wie spezifiziert man jetzt in einem int eigentlich “1.1” als Version? Na per 8+8 Bit natürlich. Also 0x0101 für meine Zwecke. Prima. Erstmal getPrinterAttributes probieren statt gleich das PDF zu schicken. Das Kommando wird jetzt zuverlässig verschickt, aber JIPP kann das Resultat nicht unfallfrei parsen – ob das eine Schwäche von JIPP ist oder eine Abweichung von der Spezifikation seitens meines Druckers ist noch ungeklärt, aber Gott sei Dank hat der JIPP-Programmierer bei solchen Parse-Fehlern daran gedacht, die Rohdaten des Replys, der nicht parsebar waren, herauszudumpen. Also konnte ich einfach inspizieren, was mein Drucker so als Fähigkeiten zurückmeldet.

Nachdem das Kommando-Schicken nun völlig problemlos funktionierte, machte ich mich an die eigentliche Aufgabe, den Druck eines PDFs – der Oki unterstützt sowohl direktes Drucken per PostScript als auch per PDF, und PDFs finden sich auf der Platte entschieden leichter. Ein einseitiges Dokument identifiziert und per offiziellem PDF-MIME-Type “application/pdf” zum Drucker geschickt. Kommt zurück: “client-error-attributes-or-values-not-supported”. Eine sprechende Fehlermeldung! Sehr schön. Inspektion der bei getPrinterAttributes zurückgemeldeten Fähigkeiten zeigt: “application/pdf” wird auch gar nicht akzeptiert, nur “application/octet-stream”, “application/vnd.hp-PCL” oder “application/postscript”. Einfach mal “application/octet-stream” geraten, und siehe da: es wird gedruckt.

Na das war ja einfach.

Zu klärende offene Fragen: 1. Nützt mir das jetzt was für IPP unter RISC OS? 2. Wie erzeuge ich möglichst einfach das PWG-Raster-Format aus einem beliebigen Quellformat? 3. Warum drucke ich nicht einfach PDF über einen CUPS-Druckerserver, der ja auch IPP out-of-the-box kann?

Antworten: 1. Noch nicht. 2. Sieht nicht so schwierig aus, auch keine ultrakomplexe Kompression, sondern simples PackedBits-Format und anständig dokumentiert. 2a. Aber auch JPEG wird als Format im Standard zwingend vorausgesetzt – noch einfacher! 3. Weil das ja jeder kann.

Interessanter Beifang: AppImage als Paketiervariante für Linux-Anwendungen. Spannend, kannte ich noch nicht.

Graal(VM) – Versuch eines Überblicks

Die Themen Graal bzw. GraalVM beschäftigen mich schon eine ganze Weile, und wie ich erneut auf dem Java Forum Stuttgart 2019 bemerkt habe, hat das Graal-Universum derart viele Facetten, die sich zudem alle in einem unterschiedlichen Stand der Nützlichkeit und Nutzbarkeit befinden, so dass ich hier mal versuchen werde, einen Überblick zu geben.

Das Graal-Universum (auch gerne “GraalVM ecosystem” genannt) ist ein Oracle-Projekt, hervorgegangen aus zahlreichen Forschungsprojekten der Oracle Labs in Kooperation mit der Uni Linz – früher hieß das Projekt “Metropolis” (und noch früher Maxine VM) und hatte ursprünglich neben Forschung angeblich nur das Ziel, einen JIT-Compiler für die Hotspot-JVM in Java neu zu schreiben, weil vor allem der in C++ geschriebene “C2” (auch als “HotSpot-Compiler der Server-JVM” bekannt) das Ende seiner Erweiterbarkeit und Wartbarkeit erreicht hatte. Inzwischen ist sehr viel mehr daraus geworden.

Übrigens steht “Graal” für “General Recursive Applicative and Algorithmic Language”. Was ein Glück, dass niemand auf die daraus eigentlich resultierende Schreibweise “GRAAL” besteht. Ein weiterer wichtiger Hinweis: das Wording wurde über die Zeit verändert, bitte nicht verwirren lassen von Graal vs. GraalVM vs. Graal-Compiler vs. Graal-JIT. Derzeit scheint man sich auf “GraalVM” für das Gesamtpaket entschieden zu haben, mit “Graal” als Kurzform, wenn man drüber spricht.

Die (JVM-)Welt vor GraalVM

Viel wurde geschrieben zur JVM, zu Bytecode, zu Interpretern, zu JIT-Compilern und der dynamischen Recompilierung à la HotSpot. Vor GraalVM war die HotSpot-JVM von Sun/Oracle die übliche Laufzeitumgebung für Bytecode aller Art (Dinge wie Excelsior JET, IBM/Eclipse J9, Azul Zing oder sogar gcj aus früheren Zeiten waren und sind ja eher Exoten). Die HotSpot-JVM hat im Prinzip drei Möglichkeiten, um Bytecode zu behandeln: entweder er wird interpretiert, oder er wird mit dem C1-Compiler (auch “client compiler” genannt) in Maschinencode übersetzt (das geht schnell, ist aber wenig optimierter Code), oder er wird mit dem C2-Compiler (auch “server compiler” genannt) in Maschinencode übersetzt (das dauert etwas länger, ist aber sehr gut optimiert, und kann zur Laufzeit, wenn eine Methode beispielsweise sehr häufig aufgerufen wird, weiter optimiert werden – das ist der “HotSpot”-Aspekt der JVM). Das Zusammenspiel aus C1-C2 wird oft auch “tiered compilation” genannt.

Nun ist das C++-Duo C1-C2 schon gut 20 Jahre alt und nach verschiedenen Aussagen diverser Beteiligter am Ende von Weiterentwicklung und Wartbarkeit angekommen. C2 wurde in den letzten Jahren nur noch mit spitzen Fingern angefasst, es wurden keinerlei Verbesserungen an der Architektur oder der prinzipiellen Optimierungsmöglichkeiten vorgenommen, nur Bugfixing und diverse Optimierungen im Bereich Intrinsics (also das Erkennen spezifischer Codepatterns und Verwendung von optimiertem Maschinencode um diese direkt zu ersetzen), was aber als Backend-Optimierung für jede unterstützte Prozessorarchitektur separat gemacht werden musste (oder der Einfachheit halber gleich unterblieb).

Man könnte argumentieren, dass das (also Stabilität und wenige Änderungen) auch gut so ist: durch die Ausführung als native code in der JVM besteht schließlich immer die Gefahr, bei einem unerwarteten Fehler die komplette JVM mit einem Coredump nach einem Segmentation Fault herunterzureißen. Und schließlich muss man ja zugeben, dass der C2 insbesondere Java-Bytecode in wirklich effizienten Maschinencode überführt, der für viele Anwendungen den Performancevergleich mit statisch compilierten Sprachen wie C oder C++ nicht zu scheuen braucht, und teilweise aufgrund seiner dynamischen Natur auf Basis von Informationen, die erst zur Laufzeit zur Verfügung stehen, diese sogar performancetechnisch übertreffen kann.

Diese uns allen bekannte JVM-Welt hat aber natürlich auch bekannte Schwächen, so dass das Festhalten am Status Quo kaum als eine valide Vorgehensweise für die Zukunft erscheint. Da ist zum einen der nicht zu vernachlässigende Performancenachteil beim Startup einer Anwendung – es dauert seine Zeit, bis die entscheidenden “heißen” Codepfade durch C2 entsprechend optimiert wurden, ein Prozess, der oft als “heating up the JVM” oder ähnlich bezeichnet wird. Und während dieser Zeit genehmigt sich die JVM ja durchaus auch erkleckliche Mengen an Speicher, und der Garbage Collector darf regelmäßig aufräumen. So schön das Prinzip einer virtuellen Maschine auch sein mag, in gewissen Anwendungsszenarien wie z.B. Desktop-Anwendungen oder Cloud-Services sind die Nachteile unübersehbar.

Dazu kommt, dass sich mit der Zeit herausgestellt hat, dass die Techniken des C2 zu nah an Java und der Art und Weise, wie javac Bytecode erzeugt, gebaut sind. Das führt zu suboptimalen Ergebnissen bei der Ausführung von Bytecode aus anderen Quellen, sei es Scala, JRuby, Jython oder Clojure.

Und dann ist da natürlich das Problem des “Systembruchs” bei der Weiterentwicklung des JITs – man muss immer zwei Welten verstehen, entwickeln, analysieren und debuggen – die native C++-Welt und die Java-Welt. Qualifiziertes Personal, das in beiden Welten zuhause ist, ist naturgemäß schwer zu finden.

Jedenfalls kann man nicht wegdiskutieren, dass das Großthema Performance in den Teilbereichen “startup” und “footprint” mit der bisherigen Strategie kaum ausreichend gelöst werden kann, vor allem wenn im Serverbereich nicht mehr länger große, langlaufende Prozesse das übliche Architekturmuster sind, sondern stattdessen kurzlaufende, containerisierte, über ihre schiere Anzahl skalierende Microservices. Bisher wurden diese Themen nur im Rahmen des gewohnten Umfelds angegangen – Class Sharing, Modularisierung des JDK, AOT seit Java 9. Mit begrenztem Erfolg.

Graal der JIT-Compiler aka “GraalVM compiler”

Angeblich beginnt die Geschichte von Graal(VM) etwa 2009 mit einer Portierung von C1/C2 nach Java durch Thomas Würthinger. Heute kann man den Graal-JIT-Compiler recht einfach in JDK 8 oder JDK 11 integrieren, kann also von jedem Java-Entwickler sehr einfach ausprobiert werden. So kann man in Ruhe für seinen spezifischen Workload herausfinden, ob die fortgeschrittenen Optimierungstechniken tatsächlich einen Laufzeitvorteil bedeuten, und wie groß dieser gegebenenfalls ausfällt.

Vor geraumer Zeit (Java 8, zunächst aber nicht öffentlich dokumentiert, offiziell dabei seit Java 11) wurde das JVMCI (JVM Compiler Interface, JEP 243) eingeführt, eine (hoffentlich langzeit-)stabile Schnittstelle der JVM zum Andocken alternativer JIT-Compiler. Graal kann genau diese Schnittstelle nutzen.

Wo bietet Graal nun Vorteile gegenüber dem bisherigen C2? Insbesondere die sogenannte “Escape Analysis” scheint sehr viel leistungsfähiger zu sein. D.h. der Compiler ist viel besser beim Erkennen von Objekten, deren Gültigkeitszeit z.B. auf Methodenebene beschränkt ist und deshalb problemlos auf dem Stack statt auf dem Heap allokiert werden kann. Das hat doppelten Nutzen, denn die Stack-Allokation ist schneller und der GC muss hinterher nicht aufräumen. Besonders bei Scala-Code scheint diese Verbesserung für erhebliche Performanceverbesserungen zu sorgen. Aber auch das Inlining von Code soll mit Graal besser funktionieren.

Die Tatsache, dass Graal in Java geschrieben ist, hat interessante Konsequenzen. Zum einen wird nun der JIT-Compiler selbst in der JVM ausgeführt und optimiert sich so mit der Zeit selbst. Speicherallokation für den Compiler findet auf dem JVM-Heap statt anstelle aus dem Native-Pool zu kommen. Da nun auch die Performance des Compilers einer Warm-Up-Zeit unterliegt, verändert sich auch das Performance-Profil zur Ausführungszeit. Und der GC ist natürlich potenziell stärker beschäftigt, weil er sich nun sowohl mit der eigentlichen Anwendung als auch mit dem Compiler beschäftigen muss.

Um dieses Szenario zu nutzen, kann man einfach die GraalVM oder einen der OpenJDK-Builds vom GraalVM-Team nutzen und entsprechend konfigurieren. Im Prinzip ein “drop-in replacement” für das gewohnte JDK.

Und was ist libgraal?

libgraal ist der Versuch, den Graal-JIT-Compiler mittels GraalVM-native-image (dazu später mehr) in eine Bibliothek zu überführen, die das Performance-Problem des Compile-Vorgangs des Graal-JIT-Compilers in der JVM lösen soll. Da schon alles in nativem Code vorliegt, muss sich der JIT-Compiler nur noch um den Anwendungscode kümmern und nicht mehr um sich selbst. Im Prinzip wird dadurch Graal zu einem – was das Laufzeitverhalten angeht – sehr ähnlichen JIT-Compiler wie der klassische C1 und C2 in der HotSpot-JVM. Langsame JIT-Compilierung während der JVM-Warm-Up-Phase wird dadurch verhindert.

Die aktuellen Versionen von GraalVM sowie die GraalVM-OpenJDK-Builds enthalten allesamt libgraal out-of-the-box.

GraalVM und die AOT-Compilierung

Seit Java 9 gibt es im JDK ein als experimentell markiertes Tool namens jaotc, das (eine recht frühe Version von) Graal als Backend verwendet. Die Implementierung erfolgte im Rahmen von JEP 295, dort kann man viele der Einschränkungen nachlesen. Im Prinzip gibt man jaotc vor, welcher Code vorcompiliert werden soll, und es wird ein Binär-Blob erzeugt, der dann den sonst durch den JIT-Compiler erzeugten Maschinencode enthält. Man spart also die Zeit, die der JIT-Compiler sonst zur Laufzeit braucht, um den Code zu compilieren.

Es gibt bei jaotc jede Menge Einschränkungen, die den Einsatz in der Praxis wirkungsvoll verhindert haben – zumindest in der Breite. Immerhin gab es bei Java 10 einige Bemühungen, um diverse gravierende Einschränkungen wie “funktioniert nur unter Linux-x64” aufzuheben. GraalVM geht heute aber noch einen großen Schritt weiter mit “native-image”.

GraalVM und das “Native Image”-Feature

Um Memory-Footprint und Startup-Zeit zu minimieren, unterstützt GraalVM die Möglichkeit, ein “Native Image” zu erzeugen. Simpel gesagt: ein standalone ausführbares Programm, das keine separate JVM benötigt. Die bisherigen Releaseversionen von GraalVM inklusive der derzeit aktuellen 19.1.0 erlauben die Erstellung eines solchen “Native Images” für Linux und macOS. Dazu wird neben der GraalVM-Distribution auch noch eine native Toolchain für das Betriebssystem benötigt, für Linux beispielsweise gcc nebst Bibliotheken wie glibc und zlib. Für die noch sehr experimentelle Windows-Unterstützung wird eine Installation von “Windows SDK for Windows 7” benötigt.

Es gibt ein paar Dinge neu zu durchdenken, wenn man ein “Native Image” erzeugt. Beispielsweise die Frage, was eigentlich schon zur Compilezeit aufgelöst wird und was erst zur Laufzeit. Stichwort “Initialisierung statischer Member”. Man überlege sich, inwieweit sich die Semantik des Programms ändert, wenn in einem static initializer eine Variable mit “new Date()” belegt wird und dies einmal zur Compilezeit passiert. Für solche Fälle werden Compile-Time-Switches angeboten.

Prinzipbedingt gibt es einige Limitierungen beim native-image-Feature. Wichtig ist die “closed-world assumption”. GraalVM muss bei der statischen Compilierung schließlich entscheiden können, welche Klassen für das Image eingebunden werden müssen. Das schließt übliche Java-Mittel wie dynamisches Nachladen von Klassen zur Laufzeit aus, deren Notwendigkeit auch erst zur Laufzeit auftaucht – typische Service-Lookup-Konstruktionen, beispielsweise im OSGi-Umfeld, gehören zu diesem Problemkreis. Ein Feature, um über einen Tracing Agent auch zur Laufzeit dynamisch nachgeladene Klassen sicher zu identifizieren, befindet sich derzeit in der Entwicklung. Aber auch das würde natürlich vollständig dynamisches Nachladen nur dann erfassen, wenn alle entscheidenden Codepfade auch tatsächlich durchlaufen werden, während der Tracing Agent aktiv ist.

Erstaunlicherweise ist trotzdem partieller Support für Reflection verfügbar, was uns zum nächsten Teilthema führt…

GraalVM und die Substrate VM

Auch nativer Code braucht natürlich, wenn die Semantik eines Java-Programms möglichst unangetastet bleiben soll, eine gewisse Laufzeitumgebung. Die wird im Falle eines GraalVM-erzeugten “Native Image” durch die eingebettete Substrate VM zur Verfügung gestellt. Dazu gehören Runtime-Routinen und der Garbage Collector. Die Komponenten der Substrate VM sind in Java geschrieben.

GraalVM und Truffle

Bisher waren die Punkte sehr Java-spezifisch, oder genauer gesagt Bytecode-spezifisch – alles problemlos nutzbar für das JVM-Bytecode-Ökosystem, also neben Java auch Kotlin, Scala, Groovy, Clojure und Konsorten. So weit, so unspektakulär. Andere Programmiersprachen haben häufig Bytecode-spezifische Forks als Implementierung wie Jython oder JRuby, und das JDK hatte bekanntlich seinen eigenen JavaScript-Interpreter intus namens Rhino (ab Java 6) und Nashorn (ab Java 8).

Aber GraalVM bietet mehr. Das Truffle-Framework erlaubt die einfache Implementierung von Interpretern, die dann vollautomatisch zu einem Bestandteil der GraalVM werden können – quasi eine automatische Generierung eines Compilers aus einem Interpreter.

Interpreter für Sprachen wie JavaScript, Ruby, R und Python sind bereits heute in verschiedenen Stadien der Fertigstellung bereit zu Experimenten. Die JavaScript-Implementierung scheint am weitesten fortgeschritten und umfasst bereits den Sprachumfang von ECMAScript 2019. Das war übrigens das Hauptproblem mit den Java-internen JavaScript-Lösungen – sie konnten mit der schnellen Weiterentwicklung von ECMAScript nicht mithalten und waren schnell veraltet und schwer wartbar.

Eine gute Zusammenfassung in wenigen Worten: “Truffle makes it easy, Graal makes it fast.” Wer schon mal versucht hat, für eine einfache Sprache einen Interpreter zu entwickeln, weiß, dass das durchaus machbar ist. Ein optimierter Interpreter ist schon schwieriger. Aber einen gescheit optimierenden Compiler dafür zu schreibe, ist sauschwer. Soll der auch noch mit in anderen Sprachen geschriebenem Code kommunizieren, wird es langsam wirklich komplex. Das GraalVM-Ökosystem verspricht hier eine einfach handhabbare und trotzdem sehr leistungsfähige und performante Lösung.

GraalVM als polyglotte VM

Nun wäre es ja schon ein echter Fortschritt, die GraalVM als universelle Laufzeitumgebung für allerlei Sprachen zur Verfügung zu haben. Beispielsweise könnte man sich für die gängigen Interpreter-Sprachen wie eben Ruby oder Python oder auch Lua die zeitaufwändige Entwicklung einer effizienten Laufzeitumgebung schlicht sparen – warum immer wieder neu einen JIT-Compiler implementieren, wenn es in GraalVM schon einen gibt? Aber wenn das nun schon möglich ist, wie wäre es denn dann, wenn man die Sprachen beliebig mischen könnte?

Das wäre natürlich phantastisch, und GraalVM bietet genau das. Es existieren Schnittstellen, um die Sprachen kontrolliert miteinander verbinden zu können. Und zwar, weil alle in demselben VM-Kontext laufen, quasi ohne zusätzlichen Overhead. So werden Java-Objekte für JavaScript-Skripte zugreifbar, Methoden aufrufbar, das volle Programm. Die technische Magie dazu steckt im “polyglot interoperability protocol” des Truffle-Frameworks, das genau diese Dinge standardisiert und damit diese Interoperabilität automatisch sicherstellt.

Der alte Traum von pick-and-mix bezüglich unterschiedlicher Programmiersprachen mit ihren spezifischen Stärken und leistungsfähigen Bibliotheken, hier wird er Wirklichkeit.

Wirklich cool ist beispielsweise eine für GraalVM erweiterte Variante von VisualVM. Man kann damit den laufenden Code in Ruby, R oder JavaScript genau untersuchen hinsichtlich der Threads und der Objekte auf dem Heap.

Und was ist Sulong?

Sulong ist eine weitere Schicht der GraalVM-Welt – ein High-Performance-Interpreter für LLVM Bitcode. Das erlaubt es der GraalVM, Programmcode, der in diversen Sprachen auf Basis der LLVM-Compiler-Infrastruktur erzeugt wird (besonders prominent: C, C++, Objective-C, Swift und Rust), ebenfalls auszuführen. Mit voller Interoperabilität mit allen anderen unterstützten Sprachen des GraalVM-Universums.

Ich erkenne hier eine leichte Ironie, schließlich hat Azul Systems für ihre Zing JVM einen neuen JIT-Compiler namens “Falcon” entwickelt, der auf der LLVM-Compiler-Infrastruktur basiert. Quasi genau andersrum.

Community Edition vs. Enterprise Edition

Oracle ist nicht gerade bekannt dafür, Projekte aus reiner Menschenfreundlichkeit kostenlos unter die Leute zu bringen. Demzufolge strebt man auch im Graal-Universum einen RoI an, und der heißt “GraalVM Enterprise Edition”. Es gibt einige Optimierungen, die nur in der Enterprise-Variante zur Verfügung stehen, Stand heute scheint das vor allem die Autovektorisierung zu sein. Man kann die Enterprise-Variante jederzeit zu Evaluierungszwecken herunterladen und so prüfen, ob das beim eigenen Workload etwas bringt – die Evaluierungsversion darf jedoch nicht in Produktion verwendet werden.

Ob und wann Optimierungen von der Enterprise-Version in die Community-Version wandern, muss abgewartet werden. Oracle wird sicherlich anstreben, immer einen Respektabstand zwischen den beiden Versionen zu wahren und vor allem Optimierungen für die Großen der Branche möglicherweise exklusiv für die Enterprise Edition reservieren. Letztlich muss der Performance-Gewinn durch die Enterprise-Edition ja im realen Leben gegen eingesparte Kosten z.B. im Cloud-Umfeld gerechnet werden – der Business-Case muss möglichst leicht nachvollziehbar sein, wenn man Lizenzen verkaufen will.

Bezüglich der Lizenzsituation bleibt festzuhalten, dass praktisch alle GraalVM-Komponenten unter GPL2 mit Classpath-Exception stehen.

Fazit, Status Quo, Ausblick, Glaskugel

Das Potenzial ist gewaltig – viele Schwächen des Java-Ökosystems können mit GraalVM gelöst oder zumindest gemildert werden. Dazu kommt mit der Idee der polyglotten VM der alte Traum der völligen Austauschbarkeit von Programmiersprachen und Bibliotheken in greifbare Nähe. Mit der “Native Image”-Möglichkeit wird Java auch wieder (oder zum ersten Mal?) eine sehr gute Lösung für klassische Apps und Anwendungen neben den unbestreitbaren Vorteilen für servserseitig kurzlaufende Services in einem containerisierten Cloudumfeld.

Was hält uns davon ab, genau jetzt vollständig in die GraalVM-Welt einzutauchen? Aus meiner Sicht ist das GraalVM-Ökosystem im Moment in einem uneinheitlichen Zustand. Während das Szenario “GraalVM als JIT-Plugin für die HotSpot-JVM” stabil ist, ist der Rest eher von wechselhafter Stabilität. Vor allem das Feature “native image” scheint im Moment eher limitiert und zurecht als “Early Adopter technology” gekennzeichnet, nicht zuletzt durch die Beschränkung auf Linux und macOS – Windows ist für diesen Anwendungsfall als “experimental” markiert. Wenn man sich die GitHub-Issues zu diesem Thema durchliest, wird klar, dass da noch eine Menge Feinarbeit zu leisten ist. AOT hingegen scheint weitgehend stabil zu sein, ist es doch seit Java 9 Teil des JDK.

Was die Polyglot-Fähigkeiten angeht, scheint die JavaScript-Implementierung recht stabil und zudem vollständig zu sein – Node.js ist ein sehr wichtiges Zielszenario, und man braucht durch die Abkündigung der Nashorn-Engine in Java 11 auch beizeiten einen Ersatz dafür. Bei Ruby, R, Python und LLVM-Bitcode steht eher noch ein Fragezeichen bezüglich Vollständigkeit, Performance und Stabilität. Vom technischen Standpunkt aus ist eine Out-of-the-box-Lauffähigkeit von realen Python-Programmen natürlich auch deutlich schwerer zu bewerkstelligen als bei JavaScript, weil das Python-Ökosystem bei vielgenutzten Bibliotheken voller native code zwecks besserer Performance steckt. NumPy und SciPy seien hier als Beispiele genannt. Bei R ist es ähnlich, wo die performance-relevanten Berechnungen oft in nativem Fortran-basierten Code passiert.

Angesichts der kürzlich erfolgten Abkündigung von Nashorn, der JavaScript-Engine im JDK, dürfte eine stabile und performante JavaScript-Fähigkeit ganz oben auf der Prioritätenliste zu sein. Außerdem scheint GraalVM als Ausführungsplattform für das Node.js-Universum auch attraktiv zu sein, vor allem natürlich wegen der einfachen und performanten Interoperabilität zwischen Java und JavaScript.

Die Native-Image-Fähigkeit ist wie gemacht für Microservices, und so gibt es bereits drei seriöse Frameworks, die GraalVM zu diesem Zweck nutzen: Quarkus, Micronaut und Helidon. Die Benchmarks bezüglich Memory-Footprint und Startup-Zeiten sehen schon beeindruckend aus.

Sehr interessant ist die Möglichkeit, die Native-Image-Fähigkeit für Plattformen zu verwenden, wo keine virtuelle Maschine zulässig ist – OK, “Plattformen” ist hier meines Wissens zu Unrecht die Mehrzahl, denn ich kenne nur iOS, das unter dieser willkürlichen Apple-Entscheidung zu leiden hat. Hier ein Artikel, der die Möglichkeiten beleuchtet. Und zurecht anmerkt, dass es schon viele vergebliche Versuche gab, iOS für das Java-Ökosystem einfach zugänglich zu machen.

Nicht zuletzt wäre die Native-Image-Fähigkeit für die wenigen verbliebenen Java-Entwickler, die Desktop-Anwendungen schreiben, eine schöne Sache. Eine einfache exe hat bei der Distribution von Software einfach Vorteile gegenüber dem Bundling einer kompletten Java-Runtime-Umgebung, möglicherweise nebst Einsatz von Obfuscator und exe-Wrapper.

Ein weiterer hier nicht weiter beleuchteter Anwendungszweck ist das Embedden von GraalVM-Technologie in andere Plattformen. Es werden die Oracle-Datenbank und MySQL derzeit genannt, mit diesem Anwendungsfall habe ich mich nicht näher beschäftigt.

Die Entwicklung schreitet im Moment zügig voran – wer am Ball bleiben will, sollte regelmäßig die Release Notes für neue Versionen durchlesen, man bekommt dann ein ganz gutes Gefühl für die Reife von manchem Szenario.

In der optimal vergraalten Welt der Zukunft wäre es ein Leichtes, mit dem Truffle-Framework seine persönliche Lieblingssprache in verhältnismäßig kurzer Zeit aus dem Boden zu stampfen und sofort eine komplette Toolchain inklusive hochoptimierender VM und nativen Code erzeugendem Compiler zur Verfügung zu haben. Inklusive voller Interoperabilität mit den Ökosystemen aller relevanten Programmiersprachen von Java über C++ bis Python. Die Grundbausteine dafür sind vorhanden und durchaus schon von hoher Qualität, aber es fehlt noch reichlich Feinschliff. Ich fürchte aber, dass die Lösung des Gesamtproblems der 80-20-Regel folgt. Wenn nicht gar 90-10.

Aber es wäre in einem ausgereiften Endzustand schon nahe am heiligen Gra(a)l. Ich hoffe, man verzettelt sich nicht in der Vielfalt der Möglichkeiten. Jedenfalls wäre Manpower in der GraalVM-Ecke m.E. deutlich besser aufgehoben als bei mancher heißdiskutierter Spracherweiterung seit Java 9. Ich sage mal “Text Blocks”. “Type Inference”. Private Methoden in Interfaces. Oder JDK-Erweiterungen wie jshell oder Epsilon GC, deren überragende Nützlichkeit sich auf den ersten und auch zweiten Blick nicht direkt erschließt.

Andere Quellen

Erster Anlaufpunkt ist natürlich die GraalVM-Homepage. Sehr empfehlenswert sind YouTube-Videos von Thomas Würthinger und Christian Thalinger (als früherer Entwickler des C2 natürlich spezialisiert auf den GraalVM-JIT aka GraalVM compiler, der früher verwirrenderweise Graal JIT oder Graal compiler hieß).

Wer ganz tief einsteigen will – die Sourcen werden auf GitHub gehostet.

Anlass für diesen Blog-Eintrag war ein Übersichtsartikel auf Heise Developer, der es doch etwas an Tiefe vermissen lies und durch Vermischung der unterschiedlichen Aspekte beim Uneingeweihten möglicherweise für Verwirrung sorgt. Ich überlasse es dem geneigten Leser zu entscheiden, ob mein Artikel eventuell noch wirrer und verwirrender ist.

Java Forum Stuttgart 2019

Über meine Gründe, das Java Forum Stuttgart zu besuchen, habe ich bereits 2017 was geschrieben, und daran hat sich nichts geändert. 2018 habe ich aufgrund wenig vielversprechender Themenauswahl mal sausen lassen, also war es 2019 mal wieder an der Zeit.

Die Rahmenbedingungen waren dieselben: Liederhalle, top organisiert, Verpflegung OK (auch wenn ich wohl nie verstehen werde, warum jemand Coke Light anbietet statt Coke Zero, aber über Geschmack kann man nicht streiten – vielleicht war es ja wenigstens billiger), abwechslungsreiches Vortragsprogramm. Und ein Code of Conduct scheint heutzutage wohl unvermeidlich, natürlich in gendergerechter Sprache anstatt in richtigem Deutsch. Man könnte den ganzen Sermon inhaltlich auch schneller auf den Punkt bringen: “Es gelten die Regeln der westlichen Zivilisation und des allgemeinen Anstands”. Oder “Verhalte Dich stets so, dass Deine Mutter auf Dich stolz wäre.”

Zum eigentlichen Thema. Gab es diesmal einen der typischen Themenschwerpunkte, den klassischen Hype, auf den sich alle stürzen und zu dem es zig Vorträge gibt? Immerhin vier Mal war Graal(VM) (Teil-)Thema, was m.E. zeigt, dass das Thema “Performance” insbesondere im Bereich Startup-Zeiten und Speicherverbrauch in der Java-Welt wie schon vor fast 25 Jahren topaktuell bleibt. Ansonsten scheint jeder – auch ohne Vorliegen zwingender Gründe – sein Heil in der Microservice-Welt – gerne auch in der Cloud – zu suchen. Der Mobile- und IoT-Hype schein hingegen etwas abgeflacht zu sein.

Zum Vortrag “Cross-Platform Mobile-Apps mit Java – 2019” von Dr. Daniel Thommes, Geschäftsführer der NeverNull GmbH. Ein Technologieüberblick, durchaus ins Detail gehend, zur ewig aktuellen Frage “write once, run anywhere, und bitte plattformübergreifend single source und an jede Plattform optimal angepasst”. Daran ist bekanntlich schon Swing gescheitert. Und die Zeichen stehen ehrlich gesagt nicht besonders gut, dass es für so divergierende Anforderungen wie “App auf iOS, App auf Android, und Desktop auf Windows und Linux” jemals eine einheitliche und schöne Lösung geben könnte. Aber der Stand der Dinge hat mich schon etwas erschreckt, denn die “Lösungen” wie Transpiler, portierte Runtimes oder halbgare eigene UI-Toolkits sind doch eher ein Armutszeugnis. Es war ein Non-Sponsored-Talk, was den Vortragenden erkennbar in Gewissensprobleme stürzte und er den Eindruck, die eigene Lösung “MobileUI” seiner Firma zu sehr in den Vordergrund zu stellen, gewissenhaft zu vermeiden suchte. Tut so einem Vortrag nicht gut. Wobei man die Basics durchaus gut beherrscht, wie die “Java Forum Stuttgart”-App – natürlich auf relativ niedrigem Komplexitätsniveau, aber eine Lösung, die die einfachen Sachen sehr gut und sicher beherrscht ist ja auch wertvoll – zeigt. Ebenfalls fehlte erkennbar Tiefe für die diversen altbekannten Fallstricke der Cross-Plattform-UI-Entwicklung. Wenn etwas leidlich Komplexes in Demos nicht angeschnitten wird, liegt das erfahrungsgemäß daran, dass es nicht anständig funktioniert. Aber das ist natürlich nur wilde Spekulation kraft meiner 20-jährigen Erfahrung mit UI-Toolkits aller Art.

Michael Wiedeking war – fast selbstverständlich – auch wieder dabei, diesmal mit “Der eilige Graal” mit einer gewohnt locker präsentierten Reise durch die wunderbare Welt von JIT-Compilern, Hotspots, Bytecode und natürlich Graal. Der Vortrag krankte etwas an der unscharfen Abgrenzung zwischen Graal, GraalVM, Graal-JIT, Graal AOT und Graal native image-Feature. Für den Uneingeweihten ging es da manchmal zu sehr durcheinander, dank ausreichender Vorbildung konnte ich jedoch folgen. Aber selbst die Graal-Erfinder selbst tun sich da oft schwer, eben weil das Graal-Universum so viele Zielrichtungen verfolgt. Wie man das in 45 Minuten überhaupt vorstellen könnte – ich weiß es auch nicht.

Auch Stephan Frind versuchte sich unter dem Titel “Graal oder nicht Graal –ist das wirklich eine Frage?” an diesem Themenkomplex. Deutlich high-leveliger als Michael Wiedeking, und deshalb erfolgreicher beim Vermitteln des allgemeinen Überblicks. Leider aber dadurch eben auch fehlende Tiefe. Die Compilerbau-Details und die C1-C2-Abgrenzung in der JVM waren nicht immer ganz korrekt, aber das tat nicht weiter weh. Details, die nur einem passionierten i-Dipfeles-Scheißer auffallen. Die Abgrenzung Graal vs. GraalVM war auch hier eher unscharf, Schwächen der derzeitigen native image-Lösung kamen etwas zu kurz. Am Ende der Hinweis “Testen! GraalVM etwas schwieriger” könnte in der Kategorie “Untertreibung des Jahres” durchaus weit vorne landen. Man schaue sich die diversen Issues zum Thema native image auf GitHub an, um ein Gefühl für den präzisen Stand der Dinge bezüglich Stabilität und Umfang zu bekommen.

Aufgrund sparsamer Konkurrenz im Zeitslot verschlug es mich auch in “Aus eins mach zehn: Neuentwicklung mit Microservices” von Lars Alvincz und Bastian Feigl (andrena objects ag). Im Abstract war schon angekündigt, dass auch “Null vermeiden” ein Thema sein sollte, und das störte doch gewaltig – wie kann man etwa 10 Minuten der wertvollen Zeit auf so eine Nichtigkeit verschwenden, wo es doch um das Thema “wir lösen ein monolithisches System durch Microservices ab” ging? Mit all seinen interessanten Fragestellungen von “sind Microservices der richtige Ansatz” über “was hat die Neuentwicklung veranlasst” bis zu “das war der Ressourcenbedarf an CPU und Speicher vorher und nachher”. Neben den Klassikern “wie schneide ich die Services richtig” und “wie schaffe ich es, transaktionales DB-Verhalten mit dem Microservice-Umfeld zu verheiraten” natürlich. Es wurde nicht mal ansatzweise versucht, hier mehr als ein paar Worte zu den interessanten Teilgebieten zu verlieren. Besonders ärgerlich, weil die dargestellten Anforderungen von Kundenseite keineswegs eine Microservice-Architektur nahelegten oder gar erzwangen. Warum bei einer eher trivialen Standardanwendung, zudem mit zwingender Datenmigration und klarer Vorgabe an Funktionalität ein “Big Up Front-Design nicht sinnvoll/möglich” gewesen sein soll – behauptet wurde es, aber begründet nicht. Zusätzlich fiel mir wieder auf, dass die Lösung mit zwei Vortragenden nur ganz selten wirklich gut funktioniert. Auch die Themen “Versionsverwaltung des Codes” und “Trunk-based development vs. Feature Branches” wurde kurz gestreift, ohne dass klar wurde, warum das in diesem Kontext unbedingt Erwähnung finden musste. Insgesamt kein überzeugender Vortrag. In keinem der behandelten Unterthemen.

Da mein tägliches Brot immer noch Java 8 ist (böse Zungen würden behaupten, dass man in Java 8 natürlich immer noch genauso wie in Java 1.4 programmieren kann und schon die Einführung von Generics ein Fehler war) und ich die Neuerungen aus Java 9 bis Java 13 zwar kenne, aber nicht seriös im produktiven Einsatz habe, habe ich mir “Power Catch-up: Alles Praktische und Wichtige aus Java 9 bis 12” von Benjamin Schmid angeschaut. Sehr komprimiert, ohne Füllstoff, kompakt und doch mit Mehrwert gegenüber dem bloßen Lesen von Artikeln zum Thema. Ich war zufrieden. Auch wenn einzelne Themen wie die neuen GC-Varianten oder Project Loom natürlich in 45 Minuten nicht annähernd in gebührender Ausführlichkeit behandelt werden können. Der Vortragende hat sich aber mit potenziell verwirrenden Details angenehm zurückgehalten. Sehr gut.

Und das Beste kommt zum Schluss. “RxJava2: von 0 auf 100(?) in 45 Minuten” von und mit Johannes Schneider. Sensationell. Einer der besten Vorträgem egal zu welchem Thema, den ich je auf einer Konferenz gehört habe. Genau getimed auf die 45 Minuten, professionell und trotzdem humorvoll vorgetragen, voller Anregungen und Informationen. Didaktisch vom Feinsten. Ich muss gestehen, dass das den 2017er Vortrag zur reaktiven Programmierung, den ich anno dazumal gar nicht so schlecht fand, im Nachhinein ziemlich blass wirken lässt, Mein Kompliment an Johannes Schneider.

Was bleibt hängen? Zwei Dinge sollte man sich (ich mir) dringend im Detail anschauen: natürlich Graal(VM), und RxJava/ReactoveX, gerne auch im Swing-Kontext (auch wenn meine heimische Library natürlich die allermeisten Dinge schon auf anderem Wege erreicht – aber mit RxJava gäbe es hier die Möglichkeit, die Code-Lesbarkeit zu verbessern). Dafür kann man glatt seine Vorbehalte gegen 3rd-party-Abhängigkeiten über Bord werfen.

Beknackte Oberflächen – heute: Windows 10 Timeserver konfigurieren

Mein Klagelied über mein “Übergangsauto” lässt noch auf sich warten, deshalb heute zur Erholung ein weiteres Kleinod aus der Reihe “Beknackte Oberflächen”.

Die Vorgeschichte; ein Phänomen zeigte sich auf einem einzigen Windows 10-Rechner in meinem Heimnetz: die Uhrzeit war regelmäßig falsch. Also nicht nur ein paar Minuten, sondern Stunden oder wie heute ein ganzer Tag. Mal vor, mal nach. Besonders bei E-Mails, die man schreibt, kann das zu Irritationen führen.

Man konnte das Problem für den Moment stets beheben, wenn man in den Uhrzeit-Einstellungen die automatische Synchronisation mit einem Internet-Timeserver kurz abschaltet und wieder anschaltet. Das bewährte IT-Rezept – “have you tried to turn it off and on again” – in voller Blüte.

In den Einstellungen gab es hingegen keinen offensichtlichen Weg, einen spezifischen Timeserver einzustellen, denn offenbar war der Default ja ein sehr unzuverlässiges Exemplar seiner Gattung. Eine kurze Internet-Recherche später stellt sich raus: der Weg zur Einstellung des Timeservers ist nicht nur nicht offensichtlich, er ist extrem unoffensichtlich. Man muss den Link “Uhren für unterschiedliche Zeitzonen hinzufügen” klicken, und schon öffnet sich ein guter alter Windows-XP-Style-Dialog, der auch einen Reiter “Internetzeit” hat. Dort gibt es dann einen Button, der einen weiteren Dialog öffnet (klar, dass man für diese kritische Aktion Admin-Rechte braucht). Flugs die FritzBox eingetragen, scheint zu funktionieren. Mal sehen wie lange. Besser als mit dem alten Server (natürlich einer unter microsoft.com – sehr beruhigend, das Betriebssystem eines Herstellers einzusetzen, der nicht mal einen zuverlässigen Timeserver betreiben kann) wird es auf jeden Fall sein.