GraalVM native image can process your application compiling it ahead of time into a standalone executable.
Some of the benefits you get from it are:
- Small standalone distribution, not requiring a JDK
- Instant Startup
- Lower memory footprint
Let's take a look...
We'll create a Micronaut application to compute sequences of prime numbers & then we will see how we can easily create a fast starting Native Image form this Java application.
First, let's make sure that we have (and they are the latest versions) the tools we need:
- Micronaut (Install Micronaut using SDKMan), version 2.3.0
- GraalVM EE, 21.0.0
# Check Micronaut version
mn --version
Micronaut Version: 2.3.0
# Check Java version
java --version
java version "11.0.10" 2021-01-19 LTS
Java(TM) SE Runtime Environment GraalVM EE 21.0.0 (build 11.0.10+8-LTS-jvmci-21.0-b06)
Java HotSpot(TM) 64-Bit Server VM GraalVM EE 21.0.0 (build 11.0.10+8-LTS-jvmci-21.0-b06, mixed mode, sharing)
# Check that we have Native Image installed as well
native-image --version
GraalVM Version 21.0.0 EE (Java Version 11.0.10+8-LTS-jvmci-21.0-b06)
Now, create the application using Micronaut!
mn create-cli-app primes; cd primes
Create and edit the src/main/java/primes/PrimesComputer.java
file:
package primes;
import javax.inject.Singleton;
import java.util.stream.*;
import java.util.*;
@Singleton
public class PrimesComputer {
private Random r = new Random(41);
public List<Long> random(int upperbound) {
int to = 2 + r.nextInt(upperbound - 2);
int from = 1 + r.nextInt(to - 1);
return primeSequence(from, to);
}
public static List<Long> primeSequence(long min, long max) {
return LongStream.range(min, max)
.filter(PrimesComputer::isPrime)
.boxed()
.collect(Collectors.toList());
}
/**
* If n is not a prime, then n = a * b (some a & b)
* Both a and b can't be larger than sqrt(n), or the product of them would be greater than n
* One of the factors has to be smaller than sqrt(n).
* So, if we don't find any factors by the time we get to sqrt(n), then n must be prime
*/
public static boolean isPrime(long n) {
return LongStream.rangeClosed(2, (long) Math.sqrt(n))
.allMatch(i -> n % i != 0);
}
}
Edit the src/main/java/primes/PrimesCommand.java
file:
package primes;
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.context.ApplicationContext;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import javax.inject.*;
import java.util.*;
@Command(name = "primes", description = "...",
mixinStandardHelpOptions = true)
public class PrimesCommand implements Runnable {
@Option(names = {"-n", "--n-iterations"}, description = "How many iterations to run")
int n;
@Option(names = {"-l", "--limit"}, description = "Upper limit for the sequence")
int l;
@Inject
PrimesComputer primesComputer;
public static void main(String[] args) throws Exception {
PicocliRunner.run(PrimesCommand.class, args);
}
public void run() {
for(int i =0; i < n; i++) {
List<Long> result = primesComputer.random(l);
System.out.println(result);
}
}
}
Remove the tests because we changed the functionality of the main command:
rm src/test/java/primes/PrimesCommandTest.java
Now we can build this Micronaut project to get the jar file with our functionality:
./gradlew build
Test the application that it prints the prime numbers:
java -jar build/libs/primes-0.1-all.jar -n 1 -l 100
[53, 59, 61, 67, 71, 73]
Now you can build the native image too:
./gradlew nativeImage
Micronaut includes a Gradle plugin to invoke the native-image
utility and configure its execution.
You can find the resulting executable in build/native-image/application
.
Inspect it with the ldd
utility and check that it's linked to the OS libraries.
# On linux
ldd build/native-image/application
If you are on a mac, then you will need to use the otool
, as below:
# On OsX
otool -L build/native-image/application
Take a look at its file type:
file build/native-image/application
If you have GNU time utility (brew install gnu-time
on Macos), you can time the execution and the memory usage of the process. Compare the following:
/usr/bin/time -v java -jar build/libs/primes-0.1-all.jar -n 1 -l 100
vs.
/usr/bin/time -v build/native-image/application -n 1 -l 100
Next, we'll try to explore some more options how to configure the build process for native images.