GraalVM

Was ist die GraalVM

Die GraalVM ist eine neuartige Virtuelle Maschine die hochoptimierten Code erzeugen kann. Die VM ist Polyglot, das heißt sie ist nicht auf eine Programmiersprache begrenzt und auch nicht auf ein Zielsystem festgelegt. Es lassen sich JavaScript Programme für Oracle Datenbank kompilieren und es ist auch schon MySQL in Planung.

Die Version 1.0.0 ist kurz vor dem Release und es ist Release Candidate 9 aktuell. Da GraalVM völlig OpenSource ist, ist auch jeder der interesse an dem Projekt hat, aufgerufen das auszuprobieren und Fehler und Verbesesserungsvorschläge zu melden.

In diesem Betrag stelle ich kurz einige der Möglichkeiten von GraalVM vor. Ich beleuchte Polyglotte Programmierung und das Kompilieren in native Code. Auf Details zur Implementierung der VM und andere technische Hintergründe gehe ich in diesem Beitrag nicht ein.

Installation

Wie immer hier die nötigen Pakete die unter Manjaro/Arch Linux zu installieren sind.

yaourt --noconfirm -S graal-bin graalpython-bin truffleruby-bin npm
yaourt --noconfirm -S fastr-bin

Installation überprüfen

$ sudo archlinux-java status
Available Java environments:
  java-10-openjdk
  java-11-openjdk
  java-8-graal
  java-8-openjdk (default)

Es muss noch die Default VM geändert werden.

sudo archlinux-java set java-8-graal

Nun sollte auch das Graal Component Updater (gu) im Pfad vorhanden sein.

$ gu
GraalVM Component Updater v1.0.0

Usage: 
        gu info [-cFlprstuv] <param>      prints info about specific component (from file, URL or catalog)
        gu available [-lv] <expr>         lists components available in catalog
        gu install [-0cfFnorvyxY] <param> installs a component package
        gu list [-clv] <expression>       lists installed components, or components from catalog
        gu uninstall [-0fxv] <id>         uninstalls a component
        gu rebuild-images                 rebuilds native images. Use -h for detailed usage

Common options:
...

Mit dem Graal Updater lassen sich Komponenten nachinstallieren und updaten. Da wir für die Installation den Paketmanager Pacman verwendet haben und die Installation im Systemverzeichnis liegt, schlägt ein Update oder eine Installation fehl.

Polyglottes Beispiel

Auf der Homepage von Graal gibt es ein paar Beispiele. Ein Beispiel was mich am meisten fasziniert, ist das Beispiel wo die Anwendung der Polyglotten Programmierung gezeigt wird.

Vorbereitung

Zunächst müssen die Quelltexte ausgecheckt werden. Dazu wird die übliche Prozedur mit GIT clone verwedendet. Danach rufen wir das Buildskript auf, dass per NPM die benötigten Dependencies installiert.

git clone https://github.com/graalvm/graalvm-demos
cd graalvm-demos/polyglot-javascript-java-r
./build.sh

Quelltext des Servers

Die server.js ist sehr kurz und daher zeige ich hier den gesamten Quelltext des Servers.

/*
 * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

const express = require('express')
const app = express()

const BigInteger = Java.type('java.math.BigInteger')


app.get('/', function (req, res) {
  var text = 'Hello World from Graal.js!<br> '

  // Using Java standard library classes
  text += BigInteger.valueOf(10).pow(100)
          .add(BigInteger.valueOf(43)).toString() + '<br>'

  // Using R methods to return arrays
  text += Polyglot.eval('R',
      'ifelse(1 > 2, "no", paste(1:42, c="|"))') + '<br>'

  // Using R interoperability to create graphs
  text += Polyglot.eval('R',
    `svg();
     require(lattice);
     x <- 1:100
     y <- sin(x/10)
     z <- cos(x^1.3/(runif(1)*5+10))
     print(cloud(x~y*z, main="cloud plot"))
     grDevices:::svg.off()
    `);

  res.send(text)
})

app.listen(3000, function () {
  console.log('Example app listening on port 3000!')
})

Was heißt Polyglotte Programmierung?

Wenn man den Quelltext des Server sich anschaut, dann erkennt man das Polyglot.eval() aufgerufen wird. Mit .eval können Ausdrücke von anderen Sprachen (hier R) evaluiert werden und das Ergebnis kann normal weiterverarbeitet werden. Auch der Zugriff auf Java Klassen funktioniert, ohne eine spezielle Anweisung.

Starten des NodeJS Server

Der Server der mitgelieferten NodeJS Version in dem graal-bin Package gestartet. Der Parameter –polyglot ermöglicht das die Polyglotte Ausführung möglich wird. Der Parameter –jvm sorgt dafür, dass GraalVM zum Zuge kommt und die Optimierungen durchgeführt werden.

/usr/lib/jvm/java-8-graal/jre/bin/node --polyglot --jvm server.js

Nach dem Starten kann man die Anwendung unter http://localhost:3000 aufrufen.

Debuggen der Anwendung

Die Anwendung läßt sich im Chrome Devtools Debugger debuggen. Man muss den Server nur mit dem Parameter –inspect starten.

$ /usr/lib/jvm/java-8-graal/jre/bin/node --inspect --polyglot --jvm server.js
Debugger listening on port 9229.
To start debugging, open the following URL in Chrome:
chrome-devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ea4a92b-3aa8163241f

Nun sehen wir in der Konsole die URL mit der man die Anwendung debuggen kann.

Hinweis: Aktuell kennt KDE das Custom Protocol nicht, so dass die URL aus der Konsole heraus nicht direkt geöffnet werden kann. Es läßt sich bestimmt ein Protocol Handler registieren, so dass die URI direkt geöffnet werden kann.

Jetzt muss man nur den Ordner mit den Quelltexten in den Workspace ziehen und kann dann den Quelltext des server.js öffnen und zum Beispiel einen Breakpoint setzen und Variablen inspizieren.

Nativer Code

Die GraalVM kann aus einer JavaVM und einem JAR-File ein Blob erzeugen, der sich genauso verhält wie eine Anwendung die in einer “normalen” Java VM läuft. Die Startupzeiten sind dann extrem gering, im Vergleich zu den Startupzeiten einer HotSpot VM. Dieses macht Java Anwendungen noch interessanter in Containern, wo schnell eine neue Instanz einer Anwendung gespawnt werden muss.

Hierfür ist ein Sub Projekt der GraalVM zuständig. Es nennt sich SubstrateVM. JVM Ökosystem muss noch an GraalVM angepasst werden. Für Spring ist mit der Version 5.1 initialer Support für die neue VM vorhanden.

Übersetzen eines JAR

Für die Übersetzung wird in der GraalVM ein Programm native-image mitgeliefert, welches eine JAR untersucht und dann ein Blob erzeugen kann. Der Blob kann dann agnz normal ausgeführt werden.

native-image -jar /Pfad/zum/JAR

Limitierungen

Die SubstrateVM befindet sich noch in der aktiven Entwicklung und daher werden noch nicht alle Funktionen vollständig unterstützt (siehe hier).

In nativen Code übersetzen

Das bauen von nativen Anwendungen mit native-image funktioniert mit Spring Boot Anwendungen noch nicht, weil das dynamische laden von Klassen wohl (noch) nicht voll umfänglich unterstützt wird.

Hier die Fehlermeldung, wenn versucht ein Spring Boot Application zu übersetzen.

$ native-image -jar ./build/libs/spring-data-jdbc-demo-0.0.1-SNAPSHOT.jar 
Build on Server(pid: 8784, port: 42925)
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]    classlist:     334.59 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]        (cap):   3,266.94 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]        setup:   3,846.85 ms
[spring-data-jdbc-demo-0.0.1-SNAPSHOT:8784]     analysis:   4,875.69 ms
error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Trace: 
        at parsing java.net.URL.setURLStreamHandlerFactory(URL.java:1118)
Call path from entry point to java.net.URL.setURLStreamHandlerFactory(URLStreamHandlerFactory): 
        at java.net.URL.setURLStreamHandlerFactory(URL.java:1110)
        at org.springframework.boot.loader.jar.JarFile.resetCachedUrlHandlers(JarFile.java:401)
        at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:391)
        at org.springframework.boot.loader.Launcher.launch(Launcher.java:48)
        at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
        at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:164)
        at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)

Error: Processing image build request failed