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