开场白
GraalVM正成为JVM生态系统中最流行的论题之一。它许诺以尽可能高的速度运转基于JVM的程序(编译为本机映像时),同时占用较小的内存。听起来很风趣,能够试一试。今日,我将在编译成独立的本机映像后运转简略的Groovy程序。
这篇博客文章记录了运转编译成GraalVM本机映像的Groovy代码的十分简略的用例。它会让你很好地了解怎么开端,以及怎么处理当你开端玩自己的比如时,你可能会面临的问题。
环境装置
我将运用以下东西:
- GraalVM (20.0.0)
- Groovy (2.5.9)
让咱们保证运用正确的GraalVM Java发行版。
- 检查java版别
zzw:Home zzw$ java -version
openjdk version "1.8.0_242"
OpenJDK Runtime Environment (build 1.8.0_242-b06)
OpenJDK 64-Bit Server VM GraalVM CE 20.0.0 (build 25.242-b06-jvmci-20.0-b02, mixed mode)
从19.0.0版别开端,GraalVM不包括native-image东西作为发行版的默认部分,因而咱们需求运用GraalVM组件更新程序装置它。
- 运用gu(GraalVM发行版附带的东西)装置native-image东西
zzw:Home zzw$ gu install native-image
Downloading: Component catalog from www.graalvm.org
Processing Component: Native Image
Downloading: Component native-image: Native Image from github.com
Installing new component: Native Image (org.graalvm.native-image, version 20.0.0)
- 检查groovy版别
rzzw:blog zzw$ groovy -v
Groovy Version: 2.5.9 JVM: 1.8.0_242 Vendor: Oracle Corporation OS: Mac OS X
详细请检查GraalVM装置文档
实例
在这个实验中,咱们将运用一个简略的Groovy程序来对数字进行求和和和和乘法:
RandomNumber.groovy
class RandomNumber {
static void main(String[] args) {
def random = new Random().nextInt(1000)
println "The random number is: $random"
def sum = (0..random).sum { int num -> num * 2 }
println "The doubled sum of numbers between 0 and $random is $sum"
}
}
GraalVM更喜欢静态编译,因而咱们需求将Groovy从动态编译切换到静态编译。咱们能够简略地将@groovy.transform.CompileStatic注释放在RandomNumber.groovy文件中的类定义上,或者创立一个编译器装备文件,将此注释添加到所有类中。
compiler.groovy
withConfig(configuration) {
ast(groovy.transform.CompileStatic)
}
咱们运用groovyc来编译代码:
zzw:groovy_and_graalvm zzw$ ls
RandomNumber.groovy compiler.groovy
zzw:groovy_and_graalvm zzw$ groovyc --configscript compiler.groovy RandomNumber.groovy
让咱们检查下履行结果
zzw:groovy_and_graalvm zzw$ groovy RandomNumber
The random number is: 507
The doubled sum of numbers between 0 and 507 is 257556
它跑得很平稳!您可能注意到了一个小推迟-结果没有立即打印到操控台上。咱们能够再次运转程序,但这次添加了time指令来测量履行时间。
zzw:groovy_and_graalvm zzw$ time groovy RandomNumber
The random number is: 81
The doubled sum of numbers between 0 and 81 is 6642
real 0m2.334s
user 0m4.612s
sys 0m0.512s
花了2334毫秒才完成。是慢的仍是快的?这要看情况。假如咱们将它与同一个程序进行比较,可是在Groovy解说器指令行东西中履行,Java程序大约快2.5倍。
zzw:groovy_and_graalvm zzw$ time java -cp ".:$GROOVY_HOME/lib/groovy-2.5.9.jar" RandomNumber
The random number is: 545
The doubled sum of numbers between 0 and 545 is 297570
real 0m0.782s
user 0m1.589s
sys 0m0.201s
让咱们看看GraalVM的native-image是否能做得更好。
创立native-image
GraalVM最风趣的特性之一是它能够从给定的Java字节码(Java.class或.jar文件)创立独立的本机二进制文件。
在JVM中运转咱们的示例很好,可是GraalVM供给了更多。咱们能够创立一个独立的native-image,它将消耗更少的内存,并在一瞬间履行。咱们试试看:
编译大约需求60秒,这是咱们应该期望的输出。
zzw:groovy_and_graalvm zzw$ native-image --allow-incomplete-classpath --report-unsupported-elements-at-runtime --initialize-at-build-time --initialize-at-run-time=org.codehaus.groovy.control.XStreamUtils,groovy.grape.GrapeIvy --no-fallback --no-server -cp ".:$GROOVY_HOME/lib/groovy-2.5.9.jar" RandomNumber
[randomnumber:6194] classlist: 11,048.19 ms, 1.42 GB
[randomnumber:6194] (cap): 6,565.97 ms, 1.42 GB
[randomnumber:6194] setup: 10,826.18 ms, 1.42 GB
[randomnumber:6194] (typeflow): 43,086.61 ms, 2.24 GB
[randomnumber:6194] (objects): 38,040.58 ms, 2.24 GB
[randomnumber:6194] (features): 1,546.67 ms, 2.24 GB
[randomnumber:6194] analysis: 84,102.69 ms, 2.24 GB
[randomnumber:6194] (clinit): 845.98 ms, 2.24 GB
[randomnumber:6194] universe: 3,034.29 ms, 2.24 GB
[randomnumber:6194] (parse): 11,970.45 ms, 2.55 GB
[randomnumber:6194] (inline): 9,875.36 ms, 2.56 GB
[randomnumber:6194] (compile): 77,254.46 ms, 4.28 GB
[randomnumber:6194] compile: 102,050.59 ms, 4.28 GB
[randomnumber:6194] image: 7,570.92 ms, 4.28 GB
[randomnumber:6194] write: 2,697.79 ms, 4.28 GB
[randomnumber:6194] [total]: 222,450.30 ms, 4.28 GB
运转独立的native-image文件
检查编译后的文件,24M的大小
zzw:groovy_and_graalvm zzw$ ls -alh
total 48752
drwxr-xr-x 7 zzw staff 224B 3 1 00:49 .
drwxr-xr-x 6 zzw staff 192B 3 1 00:05 ..
-rw-r--r-- 1 zzw staff 1.6K 3 1 00:34 RandomNumber$_main_closure1.class
-rw-r--r-- 1 zzw staff 2.7K 3 1 00:34 RandomNumber.class
-rw-r--r-- 1 zzw staff 298B 2 23 00:00 RandomNumber.groovy
-rw-r--r-- 1 zzw staff 70B 2 23 00:02 compiler.groovy
-rwxr-xr-x 1 zzw staff 24M 3 1 00:49 randomnumber
直接履行
zzw:groovy_and_graalvm zzw$ ./randomnumber
The random number is: 114
Exception in thread "main" groovy.lang.MissingMethodException: No signature of method: RandomNumber$_main_closure1.doCall() is applicable for argument types: (Integer) values: [0]
Possible solutions: findAll(), findAll(), isCase(java.lang.Object), isCase(java.lang.Object)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:255)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at groovy.lang.Closure.call(Closure.java:405)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6648)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6548)
at RandomNumber.main(RandomNumber.groovy:7)
有过错被中断了。随机数的榜首行是:114被正确打印,但在测验调用RandomNumber$_main_closure1.doCall(int)时失利。怎么回事?
此办法表明传递给(0..random).sum()办法的闭包。问题是doCall(int)办法查找的过程运用反射。而且,虽然native-image支持运转时反射,但在某些情况下,它无法正确地找到它,因而需求用户供给额外的装备。
反射装备
GraalVM本机映像的手动反射装备相当简略。咱们所要做的就是创立一个JSON装备文件并将-H:ReflectionConfigurationFiles=…添加到指令行。咱们能够运用帮助器选项(如allDeclaredMethods)装备将被反射运用的类,也能够手动供给希望运用反射调用的办法(及其参数)的列表。为了使这个比如简略,咱们将运用榜首种办法。
reflections.json
[
{
"name": "RandomNumber$_main_closure1",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
]
反射装备能够参考这儿
从头编译native-iamge
zzw:groovy_and_graalvm zzw$ native-image --allow-incomplete-classpath --report-unsupported-elements-at-runtime --initialize-at-build-time --initialize-at-run-time=org.codehaus.groovy.control.XStreamUtils,groovy.grape.GrapeIvy --no-fallback --no-server -cp ".:$GROOVY_HOME/lib/groovy-2.5.9.jar" -H:ReflectionConfigurationFiles=reflections.json RandomNumber
[randomnumber:6555] classlist: 12,353.97 ms, 1.56 GB
[randomnumber:6555] (cap): 3,546.12 ms, 1.56 GB
[randomnumber:6555] setup: 8,947.81 ms, 1.56 GB
[randomnumber:6555] (typeflow): 40,152.46 ms, 2.47 GB
[randomnumber:6555] (objects): 36,473.16 ms, 2.47 GB
[randomnumber:6555] (features): 1,347.55 ms, 2.47 GB
[randomnumber:6555] analysis: 79,091.59 ms, 2.47 GB
[randomnumber:6555] (clinit): 950.82 ms, 2.47 GB
[randomnumber:6555] universe: 2,944.74 ms, 2.47 GB
[randomnumber:6555] (parse): 12,665.35 ms, 2.50 GB
[randomnumber:6555] (inline): 9,530.42 ms, 2.81 GB
[randomnumber:6555] (compile): 73,834.22 ms, 3.39 GB
[randomnumber:6555] compile: 99,606.00 ms, 3.39 GB
[randomnumber:6555] image: 7,761.15 ms, 3.41 GB
[randomnumber:6555] write: 2,626.08 ms, 3.41 GB
[randomnumber:6555] [total]: 214,830.88 ms, 3.41 GB
再次履行
zzw:groovy_and_graalvm zzw$ ./randomnumber
The random number is: 558
java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$521
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.ClassLoader.loadClass(ClassLoader.java:229)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:101)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.proxy(GeneratedMetaMethod.java:93)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.isValidMethod(GeneratedMetaMethod.java:78)
at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3226)
at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3188)
at groovy.lang.MetaClassImpl.getNormalMethodWithCaching(MetaClassImpl.java:1399)
at groovy.lang.MetaClassImpl.getMethodWithCaching(MetaClassImpl.java:1314)
at groovy.lang.MetaClassImpl.getMetaMethod(MetaClassImpl.java:1229)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1082)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6655)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6548)
at RandomNumber.main(RandomNumber.groovy:7)
Exception in thread "main" groovy.lang.GroovyRuntimeException: Failed to create DGM method proxy : java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$521
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:106)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.proxy(GeneratedMetaMethod.java:93)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.isValidMethod(GeneratedMetaMethod.java:78)
at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3226)
at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3188)
at groovy.lang.MetaClassImpl.getNormalMethodWithCaching(MetaClassImpl.java:1399)
at groovy.lang.MetaClassImpl.getMethodWithCaching(MetaClassImpl.java:1314)
at groovy.lang.MetaClassImpl.getMetaMethod(MetaClassImpl.java:1229)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1082)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6655)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6548)
at RandomNumber.main(RandomNumber.groovy:7)
Caused by: java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$521
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.ClassLoader.loadClass(ClassLoader.java:229)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:101)
... 12 more
又失利了。这次它找不到org.codehaus.groovy.runtime.dgm$521类。这是一个表明Groovy动态办法的类——用新办法扩展JDK类的办法。这个类也能够通过reflection访问,添加到reflection.json装备文件中。
reflections.json
[
{
"name": "RandomNumber$_main_closure1",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
},
{
"name": "org.codehaus.groovy.runtime.dgm$521",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
]
再次编译履行看看是否有效果
zzw:groovy_and_graalvm zzw$ ./randomnumber
The random number is: 689
java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$1180
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.ClassLoader.loadClass(ClassLoader.java:229)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:101)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.proxy(GeneratedMetaMethod.java:93)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.isValidMethod(GeneratedMetaMethod.java:78)
at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3226)
at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3188)
at groovy.lang.MetaClassImpl.getNormalMethodWithCaching(MetaClassImpl.java:1399)
at groovy.lang.MetaClassImpl.getMethodWithCaching(MetaClassImpl.java:1314)
at groovy.lang.MetaClassImpl.getMetaMethod(MetaClassImpl.java:1229)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1082)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6655)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6548)
at RandomNumber.main(RandomNumber.groovy:7)
Exception in thread "main" groovy.lang.GroovyRuntimeException: Failed to create DGM method proxy : java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$1180
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:106)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.proxy(GeneratedMetaMethod.java:93)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.isValidMethod(GeneratedMetaMethod.java:78)
at groovy.lang.MetaClassImpl.chooseMethodInternal(MetaClassImpl.java:3226)
at groovy.lang.MetaClassImpl.chooseMethod(MetaClassImpl.java:3188)
at groovy.lang.MetaClassImpl.getNormalMethodWithCaching(MetaClassImpl.java:1399)
at groovy.lang.MetaClassImpl.getMethodWithCaching(MetaClassImpl.java:1314)
at groovy.lang.MetaClassImpl.getMetaMethod(MetaClassImpl.java:1229)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1082)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1041)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6655)
at org.codehaus.groovy.runtime.DefaultGroovyMethods.sum(DefaultGroovyMethods.java:6548)
at RandomNumber.main(RandomNumber.groovy:7)
Caused by: java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$1180
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:60)
at java.lang.ClassLoader.loadClass(ClassLoader.java:229)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:101)
... 12 more
又失利了。这次找不到org.codehaus.groovy.runtime.dgm$1180类。将其添加到reflections.json装备文件中。
[
{
"name": "RandomNumber$_main_closure1",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
},
{
"name": "org.codehaus.groovy.runtime.dgm$521",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
},
{
"name": "org.codehaus.groovy.runtime.dgm$1180",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
]
更新装备文件后,运用与之前相同的指令从头编译native-image。完成后,是运转程序的时分了。
zzw:groovy_and_graalvm zzw$ ./randomnumber
The random number is: 515
The doubled sum of numbers between 0 and 515 is 265740
成功了!您还注意到,与曾经的测验(将Groovy代码作为Java程序运转)比较,反应时间要好得多。让咱们来测量本机映像的履行时间。
zzw:groovy_and_graalvm zzw$ time ./randomnumber
The random number is: 833
The doubled sum of numbers between 0 and 833 is 694722
real 0m0.027s
user 0m0.010s
sys 0m0.010s
我靠,太令人兴奋了,才27毫秒。
自动反射装备
我想大家都赞同,上面这种手动反射装备十分烦人。咱们向装备中添加了一个类,然后从头编译本机映像,以取得另一个缺少的类的反常。关于这样一个简略的程序,咱们必须向反射装备添加三个类。咱们能够幻想,在一个更杂乱的比如中,功率会变的怎么低下。
走运的是,有一个处理这个问题的办法。GraalVM的JDK 包括 “`native-image-agent“, 这是一个Java署理,能用于GraalVM的JDK运转咱们的程序时运用代码到来历。它能为咱们探测到所有的反射(不只如此)。
咱们试试吧。首先,咱们需求将编译好的Groovy代码作为启用了native-image-agent的Java程序运转。
zzw:groovy_and_graalvm zzw$ tree conf
conf
├── jni-config.json
├── proxy-config.json
├── reflect-config.json
└── resource-config.json
假如翻开conf/reflect-config.json文件,您将看到它包括为反射访问装备的很多类。(在我的比如中,这个文件有578行。)
咱们要做的最后一件事是删除-H:ReflectionConfigurationFiles参数并改用-H:ConfigurationFileDirectories参数。它不只加载反射装备文件,还加载署理、JNI和资源的其他三个装备。
zzw:groovy_and_graalvm zzw$ native-image --allow-incomplete-classpath --report-unsupported-elements-at-runtime --initialize-at-build-time --initialize-at-run-time=org.codehaus.groovy.control.XStreamUtils,groovy.grape.GrapeIvy --no-fallback --no-server -cp ".:$GROOVY_HOME/lib/groovy-2.5.9.jar" -H:ConfigurationFileDirectories=conf RandomNumber
[randomnumber:25458] classlist: 13,064.76 ms, 1.48 GB
[randomnumber:25458] (cap): 4,255.09 ms, 1.48 GB
[randomnumber:25458] setup: 10,399.56 ms, 1.48 GB
[randomnumber:25458] (typeflow): 49,725.16 ms, 2.85 GB
[randomnumber:25458] (objects): 50,176.33 ms, 2.85 GB
[randomnumber:25458] (features): 3,838.72 ms, 2.85 GB
[randomnumber:25458] analysis: 105,288.49 ms, 2.85 GB
[randomnumber:25458] (clinit): 1,066.71 ms, 3.83 GB
[randomnumber:25458] universe: 14,101.79 ms, 3.83 GB
[randomnumber:25458] (parse): 11,743.04 ms, 3.83 GB
[randomnumber:25458] (inline): 9,946.67 ms, 3.89 GB
[randomnumber:25458] (compile): 75,234.97 ms, 3.94 GB
[randomnumber:25458] compile: 100,240.38 ms, 3.94 GB
[randomnumber:25458] image: 8,620.27 ms, 3.94 GB
[randomnumber:25458] write: 2,989.65 ms, 3.94 GB
[randomnumber:25458] [total]: 256,066.97 ms, 3.94 GB
相同咱们来看看是否能够履行
zzw:groovy_and_graalvm zzw$ ./randomnumber
The random number is: 182
The doubled sum of numbers between 0 and 182 is 33306
大功告成!!!
github相关源码