Edited at

GraalVMを試してみた

More than 1 year has passed since last update.

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


GraalVMをインストール

Community EditionだとLinuxのみサポートのためVirtual BoxでサクっとCentOS7のVMを作成した。

作成したVMにSSHログインしてGraalVMをインストールする。

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

[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 Initializrwebだけ含めたGradleのパッケージをダウンロードして以下のソースを作成。

グループIDとアーティファクトIDはデフォルトのcom.example:demoのまま。

ダウンロードしたdemo.zipをVM上にscpして解凍。

コントローラクラスを実装する。

unzip -q demo.zip

cd demo
vi src/main/java/com/example/demo/DemoController.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してみる。

[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をビルドする。



[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コマンドを実行。

[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

再チャレンジ。

[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がないと怒られたので入れる。

yum install -y zlib-devel

再々チャレンジ。

[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という概念は達成できるかもしれない。(本当にできるとは思ってないけど)