When you are running your Java application in physical servers, you would have been using ‘-Xmx’ JVM argument to specify the Java heap size. If you are porting your application to Containers, you might be wondering how to configure Java heap size in the container’s world? Are there any best practices? In this article, we will discuss the possible JVM arguments that can be used to specify the Java heap size and the best option to choose.
There are 3 different options to specify the maximum Java heap size in containers. They are:
1. -XX:MaxRAMFraction, -XX:MinRAMFraction
2. -XX:MaxRAMPercentage, -XX:MinRAMPercentage
3. -Xmx
Let us discuss these JVM arguments, their merits, and shortcomings.
1. -XX:MaxRAMFraction, -XX:MinRAMFraction
Supported Version
*‘-XX:MaxRAMFraction’, ‘-XX:MinRAMFraction’ JVM arguments are supported from only Java 8 update 131 to Java 8 update 190. So, if you are using any other version of JDK, you cannot use this option.
How does it work?
Say you have allocated 1 GB of memory to your container, then if you configure -XX:MaxRAMFraction=2, then approximately ~512GB (i.e. 1/2 of 1GB) will be allocated to your Java heap size.
*If you are going to use ‘-XX:MaxRAMFraction’ JVM argument, make sure to pass these two additional JVM arguments as well ‘-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap’. Only if you pass these two JVM arguments then JVM will derive the heap size value from the container’s memory size, otherwise, it will derive the heap size value from the underlying host’s memory size.
# *docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2 -XshowSettings:vm -version VM settings: * * Max. Heap Size (Estimated): 494.94M
Here you can see when the docker container’s memory is set to ‘1GB’ (i.e., -m 1GB) and ‘-XX:MaxRAMFraction=2. Based on this setting, JVM is allocating Max heap size to be 494.9MB (approximately half the size of 1GB).
Note: *Both ‘-XX:MaxRAMFraction’ and ‘-XX:MinRAMFraction’ are used to determine the maximum Java heap size. JDK development team could have given a better name than ‘-XX:MinRAMFraction’. This name makes us think, ‘-XX:MinRAMFraction’ argument is used to configure minimum heap size. But it’s not true. To learn more about their difference read this article
What are its limitations?
Here are the drawbacks to this approach.
*a. Say if you want to configure 40% of the docker’s memory size, then we must set ‘-XX:MaxRAMFraction=2.5’. When you pass 2.5 as the value, JVM will not start. It is because ‘-XX:MaxRAMFraction’ can take only integer values and not decimal values. See the below example where JVM is failing to start.
# docker run -m 1GB openjdk:8u131 java -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=2.5 -XshowSettings:vm -version VM Improperly specified VM option 'MaxRAMFraction=2.5' Error: Could not create the Java Virtual Machine. Error: A fatal exception has occurred. Program will exit.
b. In this option your Java application’s heap size will be derived from the container’s memory size (because it is fraction basis). Say suppose if your application requires 1GB heap size for optimal performance and if container is configured to run with memory size that is less than 1GB then your application will still run but it will suffer from poor performance characteristics.
*c. This argument has been deprecated in modern Java versions. It is only supported from Java 8 update 131 to Java 8 update 190.
2. -XX:MaxRAMPercentage, -XX: MinRAMPercentage
Supported Version
*‘-XX:MaxRAMPercentage’, ‘-XX: MinRAMPercentage’ JVM arguments are supported from Java 8 update 191 and above. So, if you are running on older JDK versions, you can’t use this JVM argument.
How it works?
*Say you have allocated 1 GB of memory to your container, then if you configure -XX:MaxRAMPercentage=50, then approximately 512GB (i.e. 1/2 of 1GB) will be allocated to your Java heap size.
# docker run -m 1GB openjdk:10 java -XX:MaxRAMPercentage=50 -XshowSettings:vm -version VM settings: * * Max. Heap Size (Estimated): 494.94M * * Using VM: OpenJDK 64-Bit Server VM
Here you can see when docker container’s memory is set to ‘-m 1GB’ and ‘-XX:MaxRAMPercentage=50’. Based on this setting, JVM is allocating Max heap size to be 494.9MB (approximately half of 1GB).
Note: Both ‘-XX:MaxRAMPercentage’ and ‘-XX:MinRAMPercentage’ are used to determine the maximum Java heap size. JDK development team could have given a better name than ‘-XX:MinRAMPercentage’. This name makes us think, ‘-XX:MinRAMPercentage’ argument is used to configure minimum heap size. But it’s not true. To learn more about their difference read this article
Note: In several articles in internet, it’s mentioned that you need to pass ‘-XX:+UseContainerSupport’ JVM argument when you are passing ‘-XX:MaxRAMPercentage’, ‘-XX:InitialRAMPercentage’, ‘-XX:MinRAMPercentage’. Actually, that’s not true. ‘-XX:+UseContainerSupport’ is passed by default argument in the JVM. So, you don’t need to explicitly configure it.
What are the limitations?
Here are the limitations to this approach.
*a. This argument is not supported in the older versions of Java. It is only supported from Java 8 update 191.
*b.* In this option your Java application’s heap size will be derived by the container’s memory size (because it is percentage basis). Say suppose if your application requires 1GB heap size for optimal performance and if container is configured to run with memory size that is less than 1GB then your application will still run but it will suffer from poor performance characteristics.
3. -Xmx
Supported Version:
*‘-Xmx’ is supported in all versions of Java
How it works?
Using ‘-Xmx’ JVM argument you specify fine grained specific size such as 512MB, 1024MB.
*Here you can see the -Xmx to supported in non-container (traditional Physical server world):
# java -Xmx512m -XshowSettings:vm -version VM settings: * * Max. Heap Size: 512.00M * * Ergonomics Machine Class: client * * Using VM: OpenJDK 64-Bit Server VM
*Here you can see the -Xmx to supported in java 8 update 131 version in the container world:
# docker run -m 1GB openjdk:8u131 java -Xmx512m -XshowSettings:vm -version VM VM settings: * * Max. Heap Size: 512.00M * * Ergonomics Machine Class: client * * Using VM: OpenJDK 64-Bit Server VM
Here you can see the -Xmx to supported in java 10 version in the container world:
# docker run -m 1GB openjdk:10 java -Xmx512m -XshowSettings:vm -version * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * VM settings: * * Max. Heap Size: 512.00M * * Using VM: OpenJDK 64-Bit Server VM
What are the limitations?
a. If you are going to allocate ‘-Xmx’ more than the container’s memory size then your application will experience ‘java.lang.OutOfMemoryError: *kill process or sacrifice child’
Best Practices
1. Irrespective of what option you use for configuring heap size (i.e., -XX:MaxRAMFraction, -XX:MaxRAMPercentage, -Xmx), always make sure you allocate at least 25% more memory to your container (i.e. ‘-m’) than your heap size value. Say you have configured -Xmx value to be 2GB, then configure container’s memory size at least to be 2.5GB. Do this even if your Java application is the only process going to run on the container. Because lot of engineer thinks Java application will not consume more than -Xmx value. That is not true. Besides heap space your application needs space for Java threads, Garbage collection, metaspace, native memory, socket buffers. All these components require additional memory outside allocated heap size. Besides that, other small processes (like APM agents, splunk scripts, etc.) will also require memory. To learn more about them, watch this quick ‘JVM Memory’ video clip
2. If you are running *only your Java application* within the container, then set initial heap size (i.e., using either one of ‘-XX:InitialRAMFraction’, ‘-XX:InitialRAMPercentage’, -Xms) to the same size as max heap size. Setting initial heap size and max heap has certain advantages. One of them is: you will incur lower Garbage Collection pause times. Because whenever heap size grows from the initial allocated size, it will pause the JVM. It can be circumvented when you set initial, and max heap sizes to be the same. Besides that, if you have under allocated container’s memory size, then JVM will not even start (which is better than experiencing OutOfMemoryError when transactions are in flight).
3. In my personnel opinion, I prefer to use -Xmx option than -XX:MaxRAMFraction, -XX:MaxRAMPercentage options to specify Java Heap size in the container world, for the following reasons:
* I do not want the container’s size to determine my java application’s heap size. Your body’s size should decide whether you are going to wear a ‘small’ or ‘medium’ or ‘large’ size T-shirts, not other way around. You do not want to fit-in a 6-foot man with a ‘small’ size T-shirt. Memory size plays a THE KEY ROLE in deciding your application’s performance. It influences your garbage collection behavior and performance characteristics. You do not want that factor to be decided by your container’s memory setting.
* Using ‘-Xmx’ I can set fine-grained/precision values like 512MB, 256MB. *
* -Xmx is supported on all java versions.
4. You should study whether your application’s garbage collection, performance characteristics are impacted because of the new settings in the containers. To study the garbage collection behaviour, you can use free tools like *GCeasy , IBM GC & Memory Visualizer, HP jmeter