What's the point of the JVM

Kubernetes, Spring Boot, JVM Performance Tuning - Part 1

Part 1: JVM and Kubernetes settings

In this post we want to look at how to tune Spring Boot and the associated JVM. Since the JVM flags change from Java version to version, we are looking at Java 9/10/11 here. In other Java versions, the flags must be set in the same way.

We first need to understand how Java Memory Management operates. The JVM not only needs heap memory, but also memory for the stack for each thread (stack size - XSS), as well as a constant overhead, i.e. we have approximately

JVM memory requirement = heap size + stack size per thread (XSS) * number of threads + constant overhead (neither heap nor stack)

The value for XSS is OS / environment dependent and varies between 256 KB and 1 MB. That means: I need additional memory of approx. 256 KB for each thread.

To find out which values ​​are set by default for the JVM, enter the following in bash

java -XX: + PrintFlagsFinal -XX: + UnlockExperimentalVMOptions -version | \ grep -iE 'HeapSize | PermSize | ThreadStackSize | Metaspace | Maxram'

or under Windows

java -XX: + PrintFlagsFinal -XX: + UnlockExperimentalVMOptions -version | ^ findstr / i "HeapSize PermSize ThreadStackSize Metaspace Maxram"

The JVM now has container support for environments such as Kubernetes, which means that the JVM only sees the memory that is maximally allocated to the container. Nevertheless it makes sense to limit the JVM memory. If you don't do it, you may not be able to open a debug shell on the container without provoking a "Killed by OOM". With the new flags and you can control how much of the maximum RAM the JVM should use for its heap memory. The default value is 4, which is very careful. We therefore use a value of 2, which means that 50% of the maximum memory is used for the heap.

java -XX: MaxRAM = 1g -XX: MaxRAMFraction = 2 -XX: + UnlockExperimentalVMOptions -XX: + PrintFlagsFinal -version | \ grep -iE 'HeapSize | PermSize | ThreadStackSize | Metaspace | Maxram'

In fact, 1 GB of maximum memory for the JVM is quite a lot in the microservice environment. In our load tests we saw that the JVM takes a maximum of 300 MB for the heap, even if we make more memory available to it. That means it makes sense to limit the maximum JVM memory to 600 MB and to allocate 50% for the heap. The container itself then has a limit of 700 - 800 MB, with 100 - 200 MB being the intended overhead for OS and a possibly debug shell.

So overall we have the following configuration for Kubernetes and the

resources: limits: cpu: 2 memory: 800Mi requests: cpu: 2 memory: 800Mi ... environment: - name: JAVA_OPTS value:> - -XX: MaxRAM = 600m -XX: MaxRAMFraction = 2 -XshowSettings: vm -XX: + ExitOnOutOfMemoryError