开场白

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相关源码