GraalVMを試してみた

Oracleが発表した複数言語の共通VM `GraalVM` を試してみた。

## GraalVMをインストール

Community EditionだとLinuxのみサポートのためVirtual BoxでサクっとCentOS7のVMを作成した。
作成したVMにSSHログインしてGraalVMをインストールする。

```bash
curl -sSL https://github.com/oracle/graal/releases/download/vm-1.0.0-rc1/graalvm-ce-1.0.0-rc1-linux-amd64.tar.gz > graalvm-ce-1.0.0-rc1-linux-amd64.tar.gz

tar zxf graalvm-ce-1.0.0-rc1-linux-amd64.tar.gz
mv graalvm-1.0.0-rc1 /var/lib/

cat <<-'__EOT__' > /etc/profile.d/graalvm.sh
#!/bin/sh
export GRAALVM_HOME=/var/lib/graalvm-1.0.0-rc1
export JAVA_HOME=$GRAALVM_HOME
export PATH=$GRAALVM_HOME/bin:$PATH
__EOT__

. /etc/profile
```

```console
[root@centos-local demo]# java -version
openjdk version "1.8.0_161"
OpenJDK Runtime Environment (build 1.8.0_161-12)
GraalVM 1.0.0-rc1 (build 25.71-b01-internal-jvmci-0.42, mixed mode)
[root@centos-local demo]#
```

インストール完了。簡単。

## Spring Bootを試してみる

`Spring Initializr`で`web`だけ含めたGradleのパッケージをダウンロードして以下のソースを作成。
グループIDとアーティファクトIDはデフォルトの`com.example:demo`のまま。

ダウンロードした`demo.zip`をVM上に`scp`して解凍。
コントローラクラスを実装する。

```bash
unzip -q demo.zip
cd demo
vi src/main/java/com/example/demo/DemoController.java
```

```java:src/main/java/com/example/demo/DemoController.java
package com.example.demo;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
    @GetMapping("/")
    public String demo() {
        return "demo";
    }
}
```

`bootRun`してみる。

```console
[root@centos-local demo]# ./gradlew bootRun
...
2018-04-20 11:34:05.673  INFO 2814 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-04-20 11:34:05.775  INFO 2814 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2018-04-20 11:34:05.780  INFO 2814 --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 13.727 seconds (JVM running for 15.251)
```

起動したらブラウザから `http://VMのIPアドレス:8080` にアクセスして確認。
成功。

## native-imageによるコンパイルを試す

まずは実行可能な`jar`をビルドする。

```console

[root@centos-local demo]# ./gradlew  assemble
Starting a Gradle Daemon, 1 busy Daemon could not be reused, use --status for details

BUILD SUCCESSFUL in 26s
3 actionable tasks: 2 executed, 1 up-to-date
```

`native-image`コマンドを実行。

```console
[root@centos-local demo]# native-image -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Build on Server(pid: 2664, port: 26681)*
   classlist:   3,887.24 ms
       (cap):     739.46 ms
       setup:   1,694.07 ms
fatal error: com.oracle.svm.core.util.VMError$HostedError: java.io.IOException: Cannot run program "gcc" (in directory "/tmp/SVM-5836691607051554173"): error=2, そのようなファイルやディレクトリはありません
	at com.oracle.svm.core.util.VMError.shouldNotReachHere(VMError.java:68)
	at com.oracle.svm.hosted.c.codegen.CCompilerInvoker.compileAndParseError(CCompilerInvoker.java:83)
	at com.oracle.svm.hosted.c.CAnnotationProcessor.compileQueryCode(CAnnotationProcessor.java:124)
	at com.oracle.svm.hosted.c.CAnnotationProcessor.process(CAnnotationProcessor.java:80)
	at com.oracle.svm.hosted.c.NativeLibraries.finish(NativeLibraries.java:340)
	at com.oracle.svm.hosted.NativeImageGenerator.processNativeLibraryImports(NativeImageGenerator.java:1294)
	at com.oracle.svm.hosted.NativeImageGenerator.doRun(NativeImageGenerator.java:518)
	at com.oracle.svm.hosted.NativeImageGenerator.lambda$run$0(NativeImageGenerator.java:381)
	at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.io.IOException: Cannot run program "gcc" (in directory "/tmp/SVM-5836691607051554173"): error=2, そのようなファイルやディレクトリはありません
	at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
	at com.oracle.svm.hosted.c.codegen.CCompilerInvoker.startCommand(CCompilerInvoker.java:114)
	at com.oracle.svm.hosted.c.codegen.CCompilerInvoker.startCompiler(CCompilerInvoker.java:96)
	at com.oracle.svm.hosted.c.codegen.CCompilerInvoker.compileAndParseError(CCompilerInvoker.java:61)
	... 11 more
Caused by: java.io.IOException: error=2, そのようなファイルやディレクトリはありません
	at java.lang.UNIXProcess.forkAndExec(Native Method)
	at java.lang.UNIXProcess.<init>(UNIXProcess.java:247)
	at java.lang.ProcessImpl.start(ProcessImpl.java:134)
	at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
	... 14 more
Error: Processing image build request failed
```

`gcc`がないと怒られたので入れる。

```
yum install -y gcc
```

再チャレンジ。

```console
[root@centos-local demo]# native-image -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Build on Server(pid: 2664, port: 26681)
   classlist:     819.55 ms
       (cap):     610.39 ms
       setup:     930.25 ms
error: Error compiling query code (in /tmp/SVM-1219850078254730049/PosixDirectives.c). Compiler command  gcc /tmp/SVM-1219850078254730049/PosixDirectives.c -o /tmp/SVM-1219850078254730049/PosixDirectives output included error: [/tmp/SVM-1219850078254730049/PosixDirectives.c:60:18: 致命的エラー: zlib.h: そのようなファイルやディレクトリはありません,  #include <zlib.h>,                   ^, コンパイルを停止しました。]
    C file contents around line 60:
    /tmp/SVM-1219850078254730049/PosixDirectives.c:59: #include <unistd.h>
    /tmp/SVM-1219850078254730049/PosixDirectives.c:60: #include <zlib.h>
    /tmp/SVM-1219850078254730049/PosixDirectives.c:61: #include <arpa/inet.h>
Error: Processing image build request failed
```

`zlib`がないと怒られたので入れる。

```bash
yum install -y zlib-devel
```

再々チャレンジ。

```console
[root@centos-local demo]# native-image -jar build/libs/demo-0.0.1-SNAPSHOT.jar
Build on Server(pid: 2664, port: 26681)
   classlist:     590.30 ms
       (cap):   1,855.36 ms
       setup:   3,616.40 ms
    analysis:  20,212.31 ms
error: unsupported features in 3 methods
Detailed message:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported constructor java.lang.ClassLoader.<init>(ClassLoader) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime. The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing java.security.SecureClassLoader.<init>(SecureClassLoader.java:76)
Call path from entry point to java.security.SecureClassLoader.<init>(ClassLoader):
	at java.security.SecureClassLoader.<init>(SecureClassLoader.java:76)
	at java.net.URLClassLoader.<init>(URLClassLoader.java:100)
	at org.springframework.boot.loader.LaunchedURLClassLoader.<init>(LaunchedURLClassLoader.java:50)
	at org.springframework.boot.loader.Launcher.createClassLoader(Launcher.java:74)
	at org.springframework.boot.loader.Launcher.createClassLoader(Launcher.java:64)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:49)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported field java.net.URL.handlers is reachable
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime. 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:392)
	at org.springframework.boot.loader.jar.JarFile.registerUrlProtocolHandler(JarFile.java:382)
	at org.springframework.boot.loader.Launcher.launch(Launcher.java:48)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/oracle/svm/core/code/CEntryPointCallStubs;.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Unsupported method java.security.ProtectionDomain.getCodeSource() is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
To diagnose the issue, you can add the option -H:+ReportUnsupportedElementsAtRuntime. The unsupported element is then reported at run time when it is accessed the first time.
Trace:
	at parsing org.springframework.boot.loader.Launcher.createArchive(Launcher.java:118)
Call path from entry point to org.springframework.boot.loader.Launcher.createArchive():
	at org.springframework.boot.loader.Launcher.createArchive(Launcher.java:117)
	at org.springframework.boot.loader.ExecutableArchiveLauncher.<init>(ExecutableArchiveLauncher.java:38)
	at org.springframework.boot.loader.JarLauncher.<init>(JarLauncher.java:35)
	at org.springframework.boot.loader.JarLauncher.main(JarLauncher.java:51)
	at com.oracle.svm.reflect.proxies.Proxy_1_JarLauncher_main.invoke(Unknown Source)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:199)
	at Lcom/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
```

今度は`java.lang.ClassLoader`を生成できないと怒られた。

どうやら`native-image`ではSpringのように動的にクラスをロードする仕組みはサポートしていない(できない)らしい。
https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md

Hibernateとか他のORマッパーも普通は動的なクラスローディングでインスタンスを生成しているはずなので、ほとんどのフレームワークで使えないな。

VM上での実行は普通にできるので、`native-image`を使わなければユニバーサルVMという概念は達成できるかもしれない。（本当にできるとは思ってないけど）