Graalvm memory usage

This weekend I did some graalvm testing on our Openshift environment to see if we could run our microservices with native graalvm images with low memory usages.

It seemed that using a graalvm native image used even more memory when running under load then running the same application using java.

Starting a pod that runs a native image did start with very little memory (13MB), but when putting some http load on the application, the memory quickly went to 270MB and never went back.

Spring boot reference application

I first created a Spring Boot reference application.

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }
}

@RestController
@RequestMapping("/")
public class MyRestController {
  @GetMapping(value = "/", produces = MediaType.APPLICATION_JSON_VALUE)
  public String getDemo() {
    return "hello";
  }
}

When running this on Openshift, the memory of the pod started with 285 MB. After putting some load on the service, the memory went to 295MB but didn’t raise that much.

I used the following script to put load on the application (which I runned from my laptop):

for i in {1..100000};  do curl https://graalvm-demo-spring-tst.web.liander.nl; done

Javalin reference application

Next I created a javalin application since you cannot (?) create native graalvm images from Spring application because of all the reflection and dynamic loading.

public class Application {
  public static void main(String[] args) {
    Javalin app = Javalin.create().start(8080);
    app.get("/", ctx -> ctx.result("hello"));
  }
}

When running this on Openshift, the memory started with 60MB, but went op to 185MB when load was added.

Graalvm application

Creating a native graalvm image which runs on Openshift (which are linux binaries) took some steps.

Step 1: create a docker image with graalvm to create the native image

Dockerfile:

FROM ubuntu
RUN apt-get update && \
    apt-get -y install gcc libc6-dev zlib1g-dev curl bash && \
    rm -rf /var/lib/apt/lists/*
# Latest version of GraalVM (at the time of writing)
ENV GRAAL_VERSION 1.0.0-rc6
ENV GRAAL_FILENAME graalvm-ce-${GRAAL_VERSION}-linux-amd64.tar.gz
# Download GraalVM
RUN curl -4 -L https://github.com/oracle/graal/releases/download/vm-${GRAAL_VERSION}/${GRAAL_FILENAME} -o /tmp/${GRAAL_FILENAME}
# Untar and move the files we need:
RUN tar -zxvf /tmp/${GRAAL_FILENAME} -C /tmp \
    && mv /tmp/graalvm-ce-${GRAAL_VERSION} /usr/lib/graalvm
RUN rm -rf /tmp/*
# Create a volume to which we can mount to build:
VOLUME /project
WORKDIR /project
# And finally, run native-image
ENTRYPOINT ["/usr/lib/graalvm/bin/native-image"]

To create the docker image:

docker build -t graal-native-image:latest .

Step 2: Create the native image

First I needed a reflection.json

[
  {
    "name": "[Lorg.eclipse.jetty.servlet.ServletMapping;",
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  },
  {
    "name": "org.slf4j.impl.StaticLoggerBinder",
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  },
  {
    "name": "com.fasterxml.jackson.databind.ObjectMapper",
    "allDeclaredFields": true,
    "allPublicFields": true,
    "allDeclaredMethods": true,
    "allPublicMethods": true
  }
]

Then I could create the native image with:

docker run -it \
-v /Users/robbert/git/reboot/reboot-graalvm-demoapp/docker/graalvm:/project –rm \
graal-native-image:latest \
–static -H:ReflectionConfigurationFiles=reflection.json -H:+JNI -jar demo-service-all-1.0.0.jar

Step 3: Create a new git repo with my demo application and a Dockerfile so Openshift can build an image

I just copied the demo-service-all-1.0.0 binary (that was created in the previous step) into the git repo, together with the following Dockerfile:

FROM centos
COPY demo-service-all-1.0.0 /opt/demo-service-all-1.0.0
CMD ["/opt/demo-service-all-1.0.0"]

Step 4: Build the image on openshift

I created an image stream with the following yaml code:

apiVersion: v1
kind: ImageStream
metadata:
  name: graalvm-demo
  labels:                                
    role: graalvm-demo

 

Then I created a buildconfig with the following yaml code:

apiVersion: build.openshift.io/v1
kind: BuildConfig
metadata:
  labels:
    app: graalvm-demo
    build: graalvm-demo
  name: graalvm-demo
  namespace: reboot-build
spec:
  failedBuildsHistoryLimit: 5
  nodeSelector: null
  output:
    to:
      kind: ImageStreamTag
      name: 'graalvm-demo:latest'
  postCommit: {}
  resources: {}
  runPolicy: Serial
  source:
    git:
      ref: master
      uri: 'https://github.com/robbertvdzon/openshift-graalvmdemo-docker'
    sourceSecret:
      name: reboot-source-build
    type: Git
  strategy:
    dockerStrategy:
      from:
        kind: DockerImage
        name: 'registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift'
    type: Docker
  successfulBuildsHistoryLimit: 5

On openshift the image will be build automatically, and the image will be stored in the repository.

Step 5: Run the application on openshift

When logged in to openshift, I manually did: add to project -> Deploy image -> (choose image)

When the pod is started, I exposed the image with:

oc expose svc graalvm-demo

Step 6: Run the tests

Ok, finally the graalvm image is running on Openshift!

When just started, the pod only used 13MB, but quickly went up to 275MB when adding some load.

Conclusion

Graalvm might not be that useful to be used in an Openshift environment. It boots very fast (a few msec), but that is not that important in Openshift. Memory consumption is more important, but graalvm does not use less memory then java (at least not in my tests).

Using graamvm in e.g. AWS Lambda might however be very interesting because of the fast boot time.