During the week, there was an interesting video and blog post on the latest GraalVM 22.2 Release. The release has numerous new features and improvements including:

  • smaller native executables
  • the ability to generate heap dumps in native executables
  • experimental native image debugging within IntelliJ IDEA
  • the ability to embed a Software Bill of Materials (SBOM) into the executable for improved security (when using GraalVM Enterprise)
  • native metadata integration.

This blog looks at the last of these. We'll use the running example of the H2 database which the video discusses.

Native Metadata

For anyone who has used GraalVM, they will know that frequently certain information must be given to the native compiler. Certain classes can be initialized at build time, others should be initialized at runtime. If accessing certain kinds of resources, knowledge of those resources must be given to the compiler. Parts of the application which might be invoked through reflection or involve serialization, might not be deemed reachable and won't automatically be included by the compiler.

Each library that is being used within an application will have its own set of classes and resources which will commonly need to dealt with by anyone using that library. The Native Metadata repository keeps a shared copy of this information on a per library basis. Once someone has populated the metadata, other projects using the same library can get that information automatically. We'll look more at metadata integration shortly, but first, let's look at our database application.

Working with SQL in Groovy

The application creates and then populates a customer database with four customers. It then prints them out:

import groovy.sql.Sql
import groovy.transform.CompileStatic

@CompileStatic
class H2Demo {
static void main(args) {
Sql.withInstance('jdbc:h2:./data/test') { sql ->
sql.execute 'DROP TABLE IF EXISTS customers'
sql.execute 'CREATE TABLE customers(id INTEGER AUTO_INCREMENT, name VARCHAR)'
for (cust in ['Lord Archimonde', 'Arthur', 'Gilbert', 'Grug']) {
sql.executeInsert "INSERT INTO customers(name) VALUES $cust"
}
println sql.rows('SELECT * FROM customers').join('\n')
}
}
}

Groovy's Sql class makes this relatively easy. The withInstance method will create a database connection and close it down when finished with. The executeInsert method is using a Groovy interpolated String (GString) which creates a prepared statement under the covers.

Configuring our native build

Here is our build file:

plugins {
id 'application'
id 'groovy'
id 'org.graalvm.buildtools.native'
}

application {
mainClass = 'H2Demo'
}

repositories {
mavenCentral()
}

dependencies {
implementation 'com.h2database:h2:2.1.210'
implementation 'org.apache.groovy:groovy:4.0.4'
implementation 'org.apache.groovy:groovy-sql:4.0.4'
}

graalvmNative {
agent {
defaultMode = 'standard'
}
metadataRepository {
enabled = true
}
binaries {
main {
buildArgs.addAll(
// '-H:IncludeSBOM=cyclonedx',
'--report-unsupported-elements-at-runtime',
'--initialize-at-run-time=groovy.grape.GrapeIvy,org.h2.store.fs.niomem.FileNioMemData',
'--initialize-at-build-time',
'--no-fallback',
)
}
}
}

We make use of the graalvm native build plugin. We define our dependencies of Groovy and H2. We can also supply any needed parameters to the native compiler. Importantly, we enable integration with the metadata repository.

When we run the build, it will automatically create the native app for us:

paulk@pop-os:/extra/projects/groovy-graalvm-h2$ ./gradlew clean nativeRun
...
> Task :nativeCompile
[native-image-plugin] Using executable path: /extra/devtools/graalvm-ce-java17-22.2.0/bin/native-image
========================================================================================================================
GraalVM Native Image: Generating 'H2Demo' (executable)...
========================================================================================================================
...
[1/7] Initializing...                                                                                    (5.3s @ 0.26GB)
 Version info: 'GraalVM 22.2.0 Java 17 CE'
 Java version info: '17.0.4+8-jvmci-22.2-b06'
 C compiler: gcc (linux, x86_64, 11.2.0)
 Garbage collector: Serial GC
 1 user-specific feature(s)
 - com.oracle.svm.polyglot.groovy.GroovyIndyInterfaceFeature
[2/7] Performing analysis...  [************]                                                            (51.7s @ 1.82GB)
  10,597 (90.60%) of 11,697 classes reachable
  17,002 (64.13%) of 26,510 fields reachable
  58,165 (63.45%) of 91,666 methods reachable
     393 classes,   100 fields, and 2,057 methods registered for reflection
      65 classes,    74 fields, and    55 methods registered for JNI access
       4 native libraries: dl, pthread, rt, z
[3/7] Building universe...                                                                               (8.0s @ 4.02GB)
[4/7] Parsing methods...      [**]                                                                       (4.8s @ 3.85GB)
[5/7] Inlining methods...     [***]                                                                      (3.0s @ 1.72GB)
[6/7] Compiling methods...    [******]                                                                  (38.0s @ 3.63GB)
[7/7] Creating image...                                                                                  (5.9s @ 1.70GB)
  26.65MB (46.64%) for code area:    38,890 compilation units
  28.04MB (49.05%) for image heap:  359,812 objects and 66 resources
   2.46MB ( 4.31%) for other data
  57.15MB in total
------------------------------------------------------------------------------------------------------------------------
Top 10 packages in code area:                               Top 10 object types in image heap:
   1.48MB sun.security.ssl                                     5.85MB byte[] for code metadata
   1.06MB java.util                                            2.82MB java.lang.String
 979.43KB java.lang.invoke                                     2.78MB java.lang.Class
 758.29KB org.apache.groovy.parser.antlr4                      2.47MB byte[] for general heap data
 723.92KB com.sun.crypto.provider                              2.04MB byte[] for java.lang.String
 588.57KB org.h2.table                                       910.68KB com.oracle.svm.core.hub.DynamicHubCompanion
 582.06KB org.h2.command                                     764.95KB java.util.HashMap$Node
 494.23KB org.codehaus.groovy.classgen                       761.53KB java.lang.Object[]
 476.03KB c.s.org.apache.xerces.internal.impl.xs.traversers  715.65KB byte[] for embedded resources
 468.69KB java.lang                                          584.75KB java.util.HashMap$Node[]
  18.87MB for 370 more packages                                8.28MB for 2535 more object types
------------------------------------------------------------------------------------------------------------------------
                        3.9s (3.2% of total time) in 30 GCs | Peak RSS: 6.22GB | CPU load: 6.48
------------------------------------------------------------------------------------------------------------------------
Produced artifacts:
 /extra/projects/groovy-graalvm-h2/build/native/nativeCompile/H2Demo (executable)
 /extra/projects/groovy-graalvm-h2/build/native/nativeCompile/H2Demo.build_artifacts.txt (txt)
========================================================================================================================
Finished generating 'H2Demo' in 2m 1s.
    [native-image-plugin] Native Image written to: /extra/projects/groovy-graalvm-h2/build/native/nativeCompile

> Task :nativeRun
[ID:1, NAME:Lord Archimonde]
[ID:2, NAME:Arthur]
[ID:3, NAME:Gilbert]
[ID:4, NAME:Grug]

Checking the native image speed

We can also check the speed once the native image is built:

paulk@pop-os:/extra/projects/groovy-graalvm-h2$ time build/native/nativeCompile/H2Demo
[ID:1, NAME:Lord Archimonde]
[ID:2, NAME:Arthur]
[ID:3, NAME:Gilbert]
[ID:4, NAME:Grug]

real	0m0.027s
user	0m0.010s
sys	0m0.011s

More information

Check out the full source code from the repo: https://github.com/paulk-asert/groovy-graalvm-h2.

Conclusion

We have looked at a simple H2 database application and the steps involved in creating a native application with Groovy and GraalVM.