Oracle GraalVM Enterprise is a high performance runtime that provides significant improvements to application execution, efficiency and security running on premise or in the cloud. In this blog post we are going to show how to get started with GraalVM Enterprise in Oracle Cloud Infrastructure (OCI) and run some workloads in the virtual host. An important notice, if you have an ongoing subscription to Oracle Cloud, the support for GraalVM Enterprise is included.
OCI has been one of the first adopters of GraalVM Enterprise.
Oracle Cloud Infrastructure Monitoring service, the health and performance monitoring metrics, first completed the move to using GraalVM in production and reported a 5% reduced CPU consumption and a 10% increase in the number of transactions per second compared to the previous JDK.
This high-performance platform can be used in the following deployment scenarios:
Each of those deserves a separate blog post. Further below we will focus on the first scenario, more precisely, on Virtual Machine compute instance type in OCI. We will start by setting up the GraalVM Enterprise environment and continue by running some demos. Firstly, we will run a sufficiently large Spring-based application. Secondly, we will go over a JMH benchmark to measure the performance using the GraalVM compiler versus C2. Lastly, we will run an example written with the Micronaut framework and compile it ahead-of-time.
There are two ways to proceed with the GraalVM Enterprise installation:
We will follow an automatic installation. For manual file transferring, check this resource. Assuming you have a VM instance in OCI up and running already. For a complete beginner, start by creating and launching the first Linux instance tutorial. For demonstration purposes we created a demo-instance of the VM.Standard2.1 type on the Oracle Linux 7.7 pre-built image.
In OCI a user can connect to an instance using a Secure Shell (SSH) or remote desktop connection. We are going to use SSH to connect from a Unix-style system:
ssh -i .ssh/id_rsa firstname.lastname@example.org
The.ssh/id_rsa is a full path and name of the file containing the private SSH key, opc is the default name for the Oracle Linux image, and 18.104.22.168 is the instance IP address provisioned from the console. For more details, refer to the Connecting to Your Linux Instance Using SSH guide.
Since recently, Oracle GraalVM Enterprise RPMs have been made available in OCI YUM repository for the product installation convenience. That means that OCI customers can use GraalVM Enterprise environment in their cloud instances by simply installing it with yum - a package-management utility for the Linux operating systems.
Having connected to the instance, let's verify what GraalVM RPMs are available for the installation:
[opc@demo-instance ~]$ sudo yum provides graalvm*
The printed list is huge as includes previous versions, but we are looking for the latest "Oracle GraalVM Enterprise Edition JDK8 Bundle". Though it is recommended to refresh the repo data which outdates with newer GraalVM Enterprise releases by running yum check-update.
To install GraalVM Enterprise, it is enough to run sudo yum install <package_name>. In our case it would be sudo yum install graalvm20-ee-8-20.0.0-1.el7.x86_64.
All dependent packages, .e.g, libpolyglot, llvm etc., would be resolved. A total installed size is 1.3 G which is affordable as an instance of the VM.Standard2.1 type is assigned 15 GB Memory and 1 Core OCPU by default. Also, OCI gives you a possibility to alter the default boot volume size of 46.6 GB to maximum 32,768 GB anytime later.
After the installation, GraalVM Enterprise binaries are placed in the root usr/lib64/graalvm directory:
[opc@demo-instance ~]$ java -version java version "1.8.0_241" Java(TM) SE Runtime Environment (build 1.8.0_241-b07) Java HotSpot(TM) 64-Bit Server VM GraalVM EE 20.0.0 (build 25.241-b07-jvmci-20.0-b02, mixed mode)
For better user experience, let's set PATH and JAVA_HOME to point to the GraalVM Enterprise. Open the .bashrc profile in the VM server, nano ~/.bashrc, and copy-paste what is suggested below, save and exit the editor:
export JAVA_HOME=/usr/lib64/graalvm/graalvm20-ee-java8 export PATH=/usr/lib64/graalvm/graalvm20-ee-java8/bin:$PATH
Then run source ~/.bashrc to get these changes in the current SSH session. Now we have a ready-to-go VM instance. OCI allows to create custom images of it, inheriting all parents environment settings: return to the instance console, expand “Actions” drop down menu in the top menu and click “Create Custom Image".
We compressed all applications sources into oci-demos.zip and transferred them in one go via the scp SSH service from our local machine to the VM host using this command:
~/Downloads$ scp -i ~/.ssh/id_rsa oci-demos.zip email@example.com:/home/opc
If you want to repeat the examples we are going to show below, please download each separately (demo 1, demo 2, demo 3).
The home/opc is a default working directory. Re-connect to the host and unzip the archive:
ssh -i .ssh/id_rsa firstname.lastname@example.org [opc@demo-instance ~]$ unzip oci-demos.zip
GraalVM Enterprise would work for your Java applications out of the box either on a local or virtual host. We are going to demonstrate that using a well-known sample Spring application - PetClinic. To start, enter Spring PetClinic application inside the oci-demos directory, build the project with Maven and run it:
[opc@demo-instance ~]$ cd oci-demos/spring-petclinic [opc@demo-instance spring-petclinic]$ ./mvnw package [opc@demo-instance spring-petclinic]$ java -jar target/*.jar
To visit PetClinic in the browser, we had to ensure that the Linux host firewall and OCI security lists are not blocking the port 8080. Let's look into this closer. Each compute instance in OCI is attached to a Virtual Cloud Network (VCN), which in turn resides in a subnet and enables a network connection for that instance. You can think of each compute instance as residing in the subnet to which you can write a custom firewall rule in addition to the associated subnet's security lists. Navigate to VCN address from the instance information window, then to Public Subnet and open Default Security List for the attached VCN. Press "Add Ingress Rule" and fill in the following data:
Make sure the destination port is 8080. To open a running server, substitute "localhost" with the Public IP address assigned to the instance. If you still experience connection issues, open the port 8080 on the Linux firewall:
[opc@demo-instance ~]$ sudo firewall-cmd --permanent --add-port=8080/tcp [opc@demo-instance ~]$ sudo systemctl reload firewalld
In our case, the application is listening on http://22.214.171.124:8080:
The second application we are going to run is a JMH benchmark to measure the performance using the GraalVM Enterprise compiler and without it (using C2, the JVM default one). The benchmark creates a stream from array elements, and maps each number using several mapping functions. GraalVM Enterprise achieves the performance improvement of between 2x and 5x on Stream programs.
The benchmark project has to be built with Maven. Unlike the previous PetClinic Spring application, this demo does not come with a Maven Wrapper (.mvnw), and Oracle Linux 7 does not include this build tool by default, we will need to add it to the system packages. Search for it in YUM repository:
[opc@demo-instance ~]$ yum search maven
Search gives multiple results. Oracle only provides the latest versions to the software collection library and only supported for the latest update of Oracle Linux 7. Having checked this resource, we can see "rh-maven35" is most recent. Install it:
[opc@demo-instance ~]$ sudo yum install rh-maven35*
and include the following line source /opt/rh/rh-maven35/enable to your the .bashrc file. It will make Maven always available. Enter the java-simple-stream-benchmark directory, build the benchmark project and run it.
[opc@demo-instance ~]$ cd oci-demos/java-simple-stream-benchmark [opc@demo-instance java-simple-stream-benchmark]$ mvn package [opc@demo-instance java-simple-stream-benchmark]$ java -jar target/benchmarks.jar
The JavaSimpleStreamBenchmark.testMethod is executed in 3 iterations to leverage the warmup time and pick performance. The benchmark result is in nanoseconds per operation which means lower numbers are better. Now let's run this benchmark on the same JVM (GraalVM Enterprise), but without the GraalVM compiler by adding the -XX:-UseJVMCICompiler option. The GraalVM compiler will not be used as the JVMCI compiler and the JVM will use its default one:
[opc@demp-instance java-simple-stream-benchmark]$ java -XX:-UseJVMCICompiler -jar target/benchmarks.jar
The difference in score results is quite noticeable.
Lastly we are going to run a Micronaut application in a JVM versus as a native executable. Change to the micronaut directory, build and run the example with Gradle Wrapper:
[opc@demo-instance ~]$ cd oci-demos/micronaut [opc@demo-instance micronaut]$ ./gradlew assemble [opc@demo-instance micronaut]$ ./gradlew run
The application is started on port 8080 calling the Controller random() method that returns a Conference. The @Controller annotation is mapped to the path /conferences:
Let's terminate the application and generate a native Linux executable with the Native Image technology of GraalVM Enterprise. Micronaut framework itself does not rely on reflection or dynamic class loading so works well with GraalVM Native Image. Native Image comes as a separate installable so we need to add it to the base installation. Search for a proper package name with YUM and then install it:
[opc@demp-instance ~]$ yum search native-image [opc@demp-instance ~]$ sudo yum install graalvm20-ee-8-native-image.x86_64
If you compare the content of a GRAALVM_HOME/bin directory before and after the Native Image installation ($ ls /usr/lib64/graalvm/graalvm20-ee-java8/bin), you would notice that the native-image launcher has been added.
Native Image has a dependency on libstdc++ system library. To avoid a probable image generation failure because of a missing dependency, let’s install it at first and then build a native image:
[opc@demo-instance micronaut]$ yum search libstdc++ [opc@demo-instance micronaut]$ sudo yum install libstdc++* [opc@demo-instance micronaut]$ native-image --no-fallback --no-server -cp build/libs/micronaut-0.1-all.jar [opc@demo-instance micronaut]$ ./micronaut
During application creation, we used the graal-native-image feature, which added svm and graal dependencies in build.gradle script and wrote a native-image.properties file in resources/META-INF/native-imagedirectory with necessary configuration. Compare the application startup time when running in a JVM and as a native image!
Note, the resulting executable is not a fallback image, you can build it on your local machine with GraalVM Enterprise distribution for Linux platforms, then transfer over secure copy protocol (scp) SSH service to your VM host and launch. There is no JDK required to run it! This scenario is actual in cases when you have resources constrains, e.g. memory, or the processes need to start fast.
Fast startup time, lower memory consumption and higher maximum latency make GraalVM Enterprise very welcomed in cloud deployment scenarios. Many teams in Oracle Cloud Infrastructure have already tested and seen improved performance by running their services on GraalVM.
If you would like to try this out but do not have an account, then sign up for the Oracle Cloud Free Tier program. If you are an Oracle Cloud user, try running a workload with the GraalVM Enterprise environment and evaluate the cost-effectiveness.