GraalVM and Groovy - how to start?
GraalVM became one of the most popular topics in the JVM ecosystem. It promises the highest possible speed of running JVM-based programs (when compiled to native images), hand in hand with the smaller memory footprint. It sounds interesting enough to give it a try. And today we are going to play around a little bit with running simple Groovy program after compiling to a standalone native image.
Introduction
This blog post documents pretty simple use case of running Groovy code compiled to a GraalVM native image. It will give you a good understanding how to start and how to solve the problems you will most probably face when you start playing around with your own examples.
Prerequisites
We will be using following tools:
GraalVM
19.2.1
(updated: 2019-11-07T19:37:33+0100)Groovy
2.5.8
The easiest way to install GraalVM and Groovy is to use SDKMAN! command line tool. |
The
It is most probably related to the changes introduced in 19.3.0 to the The issue was reported here - oracle/graal/issues/1875 |
Let’s make sure we are using correct GraalVM Java distribution.
$ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-20191008104205.buildslave.jdk8u-src-tar--b07)
OpenJDK 64-Bit GraalVM CE 19.2.1 (build 25.232-b07-jvmci-19.2-b03, mixed mode)
Starting from 19.0.0 version, GraalVM does not contain native-image
tool as a default part of the distribution, so we need to install it using GraalVM Component Updater.
native-image
using gu
(a tool shipped with GraalVM distribution)$ gu install native-image
Downloading: Component catalog from www.graalvm.org
Processing component archive: Native Image
Downloading: Component native-image: Native Image from github.com
Installing new component: Native Image (org.graalvm.native-image, version 19.2.1)
Let’s check if we use a proper version of Groovy.
$ groovy -version
Groovy Version: 2.5.8 JVM: 1.8.0_232 Vendor: Oracle Corporation OS: Linux
Let’s also check if GROOVY_HOME
variable is set correctly. (If you installed Groovy using SDKMAN!, the environment variable should be set correctly.)
GROOVY_HOME
environment variable is set$ env | grep GROOVY_HOME
GROOVY_HOME=/home/wololock/.sdkman/candidates/groovy/current
We are all set and ready to code!
An example
For the purpose of this experiment we are going to use a simple Groovy program that sums and multiplies numbers:
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 prefers static compilation[2], thus we need to switch Groovy from dynamic to a static compilation. We can simply put @groovy.transform.CompileStatic
annotation over the class definition in RandomNumber.groovy
file, or we can create a compiler configuration file that adds this annotation to all classes.
compiler.groovy
withConfig(configuration) {
ast(groovy.transform.CompileStatic)
}
We are ready to compile our code with groovyc
compiler:
groovyc --configscript compiler.groovy RandomNumber.groovy
The code compiled. We can now test if our Groovy script runs as a Java program.
$ java -cp ".:$GROOVY_HOME/lib/groovy-2.5.8.jar" RandomNumber
The random number is: 313
The doubled sum of numbers between 0 and 313 is 98282
It run smoothly! You have probably noticed a small lag - the result was not printed instantly to the console. We can run the program again, but this time with the time
command added to measure execution time.
$ time java -cp ".:$GROOVY_HOME/lib/groovy-2.5.8.jar" RandomNumber
The random number is: 859
The doubled sum of numbers between 0 and 859 is 738740
real 0m0,306s
user 0m0,692s
sys 0m0,073s
It took 306 ms to complete. Is it slow or fast? It depends. If we compare it to the same program, but executed inside Groovy interpreter command line tool, the Java program is approximately 2.5 times faster.
$ time groovy RandomNumber.groovy
The random number is: 711
The doubled sum of numbers between 0 and 711 is 506232
real 0m0,885s
user 0m2,060s
sys 0m0,183s
Let’s see if GraalVM’s native image can do better than that.
Creating native image
One of the most interesting features of GraalVM is its ability to create standalone native binary file from given Java bytecode (either Java .class
or .jar
files).
Running our example inside the JVM was nice, but GraalVM offers much more. We can create standalone native image that will consume much less memory and will execute in a blink of an eye. Let’s give it a try:
$ native-image --allow-incomplete-classpath \ (1)
--report-unsupported-elements-at-runtime \ (2)
--initialize-at-build-time \ (3)
--initialize-at-run-time=org.codehaus.groovy.control.XStreamUtils,groovy.grape.GrapeIvy \ (4)
--no-fallback \ (5)
--no-server \ (6)
-cp ".:$GROOVY_HOME/lib/groovy-2.5.8.jar" \ (7)
RandomNumber (8)
As you can see there are many parameters passed to the native-image
command. We use to allow image building with an incomplete classpath. If we didn’t allow that, native image compilation would fail with the error like the one below.
--allow-incomplete-classpath
parameter is missingError: com.oracle.graal.pointsto.constraints.UnresolvedElementException: Discovered unresolved method during parsing: org.codehaus.groovy.control.XStreamUtils.serialize(java.lang.String, java.lang.Object). To diagnose the issue you can use the --allow-incomplete-classpath option. The missing method is then reported at run time when it is accessed the first time.
The second parameter makes usages of unsupported methods and fields to be reported at a runtime (when they are accessed the first time) instead of the build time. It is also critical to our case. Without this parameter set, compilation fails with the following error.
--report-unsupported-elements-at-runtime
parameter is missingError: Unsupported features in 5 methods
Detailed message:
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.ClassLoader.defineClass(String, byte[], int, int) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
...
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.ClassLoader.defineClass(String, byte[], int, int, ProtectionDomain) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
...
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.ClassLoader.findLoadedClass(String) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
...
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.ClassLoader.findLoadedClass(String) is reachable: The declaring class of this element has been substituted, but this element is not present in the substitution class
...
Error: com.oracle.svm.hosted.substitute.DeletedElementException: Unsupported method java.lang.ClassLoader.loadClass(String, boolean) 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 --report-unsupported-elements-at-runtime. The unsupported element is then reported at run time when it is accessed the first time.
Options and specify that all packages and classes are initialized during the native image generation, except for the two: org.codehaus.groovy.control.XStreamUtils
and groovy.grape.GrapeIvy
.
With --no-fallback
option we want to force the native image compiler that we expect the native image is either generated correctly, or the compilation fails. Without this option set, the compiler falls back to the regular JDK execution in case of an error faced during the compilation. When it happens, we see the following message in the console log.
Warning: Image 'randomnumber' is a fallback image that requires a JDK for execution (use --no-fallback to suppress fallback image generation).
The --no-server
option informs the compiler that we don’t want to use image-build server. We also set the same classpath we set when running Groovy as a Java program. And the last line contains the name of the RandomNumber.class
file.
The compilation takes approximately 60 seconds and this is the output we should expect.
$ 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.8.jar" \
RandomNumber
[randomnumber:30836] classlist: 2,543.84 ms
[randomnumber:30836] (cap): 842.60 ms
[randomnumber:30836] setup: 2,037.49 ms
[randomnumber:30836] (typeflow): 10,398.18 ms
[randomnumber:30836] (objects): 12,716.21 ms
[randomnumber:30836] (features): 502.37 ms
[randomnumber:30836] analysis: 24,049.68 ms
[randomnumber:30836] (clinit): 309.26 ms
[randomnumber:30836] universe: 952.52 ms
[randomnumber:30836] (parse): 2,359.79 ms
[randomnumber:30836] (inline): 3,216.99 ms
[randomnumber:30836] (compile): 17,702.26 ms
[randomnumber:30836] compile: 24,547.04 ms
[randomnumber:30836] image: 2,308.60 ms
[randomnumber:30836] write: 352.50 ms
[randomnumber:30836] [total]: 56,941.42 ms
Running standalone native image
The compilation succeeds and we can see randomnumber
executable file in the current folder.
randomnumber
executable file$ ls -lah randomnumber
-rwxrwxr-x 1 wololock wololock 21M 05-11 13:13 randomnumber
Let’s run it and see the result.
$ ./randomnumber
The random number is: 397
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)
Something is broken. The first line The random number is: 397
gets printed correctly, but it fails when trying to invoke RandomNumber$_main_closure1.doCall(int)
. How is that?
This method represents the closure we pass to the (0..random).sum()
method. The problem is that the process of the doCall(int)
method lookup uses reflection. And even though the native image supports runtime reflection[3], in some cases it is not able do determine it correctly, thus it requires additional configuration provided by the user.
Reflection configuration
The manual reflection configuration for GraalVM native image is fairly simple[4]. All we have to do is to create a JSON configuration file and add -H:ReflectionConfigurationFiles=…
to the command line. We can either configure class that will be used reflectively using helper options like allDeclaredMethods
, or we can manually provide a list of methods (and their parameters) we expect to get invoked using reflection. To keep this example simple, we will use the first approach.
reflections.json
file[
{
"name": "RandomNumber$_main_closure1",
"allDeclaredConstructors": true,
"allPublicConstructors": true,
"allDeclaredMethods": true,
"allPublicMethods": true
}
]
Let’s recompile the native image using reflection configuration.
$ 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.7.jar" \
-H:ReflectionConfigurationFiles=reflections.json \
RandomNumber
[randomnumber:14904] classlist: 2,465.48 ms
[randomnumber:14904] (cap): 847.33 ms
[randomnumber:14904] setup: 1,956.50 ms
[randomnumber:14904] (typeflow): 10,908.61 ms
[randomnumber:14904] (objects): 14,070.69 ms
[randomnumber:14904] (features): 389.80 ms
[randomnumber:14904] analysis: 26,006.96 ms
[randomnumber:14904] (clinit): 368.34 ms
[randomnumber:14904] universe: 1,018.86 ms
[randomnumber:14904] (parse): 2,536.26 ms
[randomnumber:14904] (inline): 3,122.56 ms
[randomnumber:14904] (compile): 18,851.47 ms
[randomnumber:14904] compile: 25,996.75 ms
[randomnumber:14904] image: 2,547.31 ms
[randomnumber:14904] write: 375.97 ms
[randomnumber:14904] [total]: 60,535.87 ms
We can run the program again to see if it works.
$ ./randomnumber
The random number is: 869
java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$521
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51)
at java.lang.ClassLoader.loadClass(Target_java_lang_ClassLoader.java:131)
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:51)
at java.lang.ClassLoader.loadClass(Target_java_lang_ClassLoader.java:131)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:101)
... 12 more
Failed again. This time it couldn’t find a class org.codehaus.groovy.runtime.dgm$521
. This is one of the classes that represent Groovy dynamic methods - methods that extend e.g. JDK classes with the new methods. This class is also accessed through reflection, let’s add to our reflection.json
configuration file.
reflections.json
file[
{
"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
}
]
Let’s recompile the native image using the same command as before. When the compilation is done, let’s see if it works.
$ ./randomnumber
The random number is: 853
java.lang.ClassNotFoundException: org.codehaus.groovy.runtime.dgm$1180
at com.oracle.svm.core.hub.ClassForNameSupport.forName(ClassForNameSupport.java:51)
at java.lang.ClassLoader.loadClass(Target_java_lang_ClassLoader.java:131)
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:51)
at java.lang.ClassLoader.loadClass(Target_java_lang_ClassLoader.java:131)
at org.codehaus.groovy.reflection.GeneratedMetaMethod$Proxy.createProxy(GeneratedMetaMethod.java:101)
... 12 more
Failed again. This time the class org.codehaus.groovy.runtime.dgm$1180
cannot be found. Let’s add it to the reflections.json
configuration file.
reflections.json
file[
{
"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
}
]
After updating the configuration file, let’s recompile the image using the same command as before. When it’s done, it’s time to run the program.
$ ./randomnumber
The random number is: 859
The doubled sum of numbers between 0 and 859 is 738740
It worked! You also noticed that the reaction time is much better compared to the previous attempts (running Groovy code as a Java program). Let’s measure native image execution time.
time ./randomnumber
The random number is: 580
The doubled sum of numbers between 0 and 580 is 336980
real 0m0,008s
user 0m0,005s
sys 0m0,003s
This is really nice - 8 ms. And here is how does it look like compared to the previous results.
As you can see, GraalVM’s native image outperforms the two previous attempts.
Automated reflection configuration
I guess we both agree, that this manual reflection configuration was pretty annoying. We added a class to a configuration, then we recompiled the native image just to get another exception with a different missing class. In case of a such simple program we had to add three classes to the reflection configuration. We can imagine how ineffective would it be in case of a much more complex example.
Luckily, there is a solution to this problem. GraalVM’s JDK is distributed with native-image-agent
- a Java agent that can be used to run our program with GraalVM’s JDK that introspects the code usage. It can detect all reflection for us (and not only that).
Let’s give it a try. Firstly, we need to run our compiled Groovy code as a Java program with the native-image-agent
enabled.
$ java -agentlib:native-image-agent=config-output-dir=conf/ \(1)
-cp ".:$GROOVY_HOME/lib/groovy-2.5.8.jar" RandomNumber
The program executes like it did before, but know it created 4 configuration files in the folder we specified with parameter (in this case I used conf/
folder). Here are the files that got created.
$ tree conf
conf
├── jni-config.json
├── proxy-config.json
├── reflect-config.json
└── resource-config.json
0 directories, 4 files
If you open conf/reflect-config.json
file you will see that it contains tons of classes configured for the reflective access. (In my case this file is 579 lines long.)
The last thing we have to do is to remove -H:ReflectionConfigurationFiles
parameters and use the -H:ConfigurationFileDirectories
parameter instead. It loads not only reflection configuration files, but also remaining three configurations for proxies, JNI, and resources.
$ 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.7.jar" \
-H:ConfigurationFileDirectories=conf/ \
RandomNumber
It compiles in 70 seconds.
[randomnumber:6854] classlist: 2,495.24 ms
[randomnumber:6854] (cap): 924.98 ms
[randomnumber:6854] setup: 2,170.19 ms
[randomnumber:6854] (typeflow): 12,968.96 ms
[randomnumber:6854] (objects): 17,112.64 ms
[randomnumber:6854] (features): 568.95 ms
[randomnumber:6854] analysis: 31,241.39 ms
[randomnumber:6854] (clinit): 447.44 ms
[randomnumber:6854] universe: 1,339.22 ms
[randomnumber:6854] (parse): 2,724.16 ms
[randomnumber:6854] (inline): 4,752.75 ms
[randomnumber:6854] (compile): 20,960.41 ms
[randomnumber:6854] compile: 29,926.05 ms
[randomnumber:6854] image: 2,818.74 ms
[randomnumber:6854] write: 373.36 ms
[randomnumber:6854] [total]: 70,552.29 ms
And see if it works with those automatically generated configuration files.
$ ./randomnumber
The random number is: 347
The doubled sum of numbers between 0 and 347 is 120756
Cowabunga! No problems this time!
Limitations
Even though we compiled the native image successfully, we need to be aware of a few significant limitations. Groovy is not a first class citizen for GraalVM’s ahead-of-time compilation by design, and that is why you can’t expect that your Groovy program will compile to the native image successfully. Below is the list of the major limitations that cannot be avoided.
GraalVM’s SubstrateVM does not support dynamic class loading, dynamic class generation, and bytecode InvokeDynamic. This limitation makes dynamic Groovy scripts and classes almost 99% incompatible with building native images. That is why we had to turn on static compilation in the example described above.
Here you can read more about SubstrateVM limitations. |
Metaprogramming features don’t work in the native image.
Coercing closures to other specific types (e.g. functional interfaces used with Java 8 Stream API) does not work.
Conclusion
I hope you have learned something interesting from this blog post. If you are interested in learning more about Groovy and GraalVM, checkout my other blog posts you can find in the section below.
You can also check my wololock/graalvm-groovy-examples GitHub repository, where I collect some of the demos and examples I create during my experiments. Feel free to test it, experiment on your side and contribute to the project. GraalVM is fascinating and quite challenging piece of technology. The more we experiment with it and learn how to use it most effectively, the more we can help other people adopting it.
Useful resources
Here you can find a list of blog posts I found useful when I was working on this article.
Updates
This blog gets updated whenever new version of GraalVM or Groovy gets released. Below you can find a list of all updates.
2019-11-22: Added information about problems with GraalVM
19.3.0
native image compiler.2019-11-07: Updated blog post to GraalVM
19.2.1
and Groovy2.5.8
.2019-05-11: Updated blog post to GraalVM
19.0.0
.
0 Comments