How to control Java memory in Tomcat running on Docker
If you are running Tomcat on Docker and want to control the allocated memory (Java Heap Size) then this article is for you.
Some basic knowledge first
Docker support
Starting in Java 10 (this behavior was back ported to Java 8u191), the “UseContainerSupport” option is by default set to true and will help Tomcat (Java) to work well with Docker.
You can check this by running the following command on a official tomcat image:
docker run --rm tomcat:9.0.44-jdk11-openjdk java -XX:+PrintFlagsFinal -version | grep UseContainerSupport
It will return:
bool UseContainerSupport = true {product} {default}
So, to start with, it is good to know that Tomcat (Java) and Docker should be fine together.
Three Useful options you should be aware of
Java offers three super useful options to control memory (RAM). What is really nice is that they offer you the possibility to control the memory by percentage. So you will be able to tell Tomcat (Java) to use up to 70% of memory. So that if your container is allocated 1 GB of RAM, Tomcat will get around 700 MB. If you adjust your container’s memory 2 GB, Tomcat will benefit from it and will get 70% of the 2 GB.
Sounds much nicer than the -Xmx and -Xms options for those who know what I’m referring to.
Here are the three options:
- InitialRAMPercentage
- MinRAMPercentage
- MaxRAMPercentage
Let’s look at the default values for those three options :
docker run --rm --name tomcat tomcat:9.0.44-jdk11-openjdk java -XX:+PrintFlagsFinal -version | grep -E "UseContainerSupport | InitialRAMPercentage | MaxRAMPercentage | MinRAMPercentage">> InitialRAMPercentage = 1.56
>> MaxRAMPercentage = 25
>> MinRAMPercentage = 50
>> UseContainerSupport = true
So MaxRAMPercentage is smaller than MinRAMPercentage ? And the Maximum RAM percentage 25% !This means that if I’m running a 10 GB docker container, my Tomcat will only get 2,5 GB of RAM ? WTF ?
Let me explain that…
InitialRAMPercentage
Will be used to comput Tomcat’s (Java) initial heap size. So if you set it to 10 percent on a 1 GB of RAM container you will start your java application with 100 MB heap size.
Let’s check that on a 1 GB container for which we set a 10% initial RAM:
docker run --rm --name tomcat -m 1GB -e JAVA_OPTS="-XX:InitialRAMPercentage=10" tomcat:9.0.44-jdk11-openjdk
We use jinfo on the running container to get the Initial Heap Size. It will return 10 MB:
docker exec tomcat jinfo 1 | grep InitialHeapSize>> InitialHeapSize=109051904
Note that the default value for InitialRAMPercentage is 1,5% on Java 11. So keeping it below 10% is probably a good idea.
MinRAMPercentage
This name is super confusing ! You may think that it is the minimum RAM percentage allocated to Tomcat, but it is NOT. It is instead used to calculate the Max Java Heap Size if your container is less than 250 MB !
The default value for MinRAMPercentage is 50%. So that if you are running a small container, 50% of your RAM will be allocated to Tomcat.
Let’s start a really small Tomcat with only 100MB of memory:
docker run --rm --name tomcat -m 100MB tomcat:9.0.44-jdk11-openjdk
And check the maximum Heap Size using jinfo again:
docker exec tomcat jinfo 1 | grep MaxHeapSize>> MaxHeapSize=52428800
We are getting around 50 MB of heap size, which corresponds to 50% of the 100 MB allocated to the container.
MaxRAMPercentage
At least this one does what it says and works exactly like MinRAMPercentage, but will apply if you are running a container that is bigger than 250 MB.
The default value for MaxRAMPercentage is only 25%. Which means that 75% of the memory will be left for anything but your tomcat.
Let’s check that on a 10 GB container:
docker run --rm --name tomcat -m 10GB tomcat:9.0.44-jdk11-openjdk
We will get a maximum heap size of around 2,5 GB:
docker exec tomcat jinfo 1 | grep MaxHeapSize>> MaxHeapSize=2684354560
Looks consistent since 25% o) 10 GB is 2,5 GB. (Yeah, I know, I’m good at mathematics).
What did we learn ?
So now, we know that Tomcat is working great with Docker thanks to the container support option that is active by default. And we also know that we can control the heap size using:
- InitialRAMPercentage: To set the initial heap size to a fraction (percentage) of our overall Tomcat container’s memory.
- MinRAMPercentage: To set the maximum heap size to a fraction (percentage) of our overall Tomcat container’s memory, in case of a small container that is less than 250 MB of RAM.
- MaxRAMPercentage: To set the maximum heap size to a fraction (percentage) of our overall Tomcat container’s memory, in case of a container that is more than 250 MB of RAM.
So let’s pimp Tomcat now !
Now that we understand what we are doing, we can pimp a little our Tomcat so that it has the following behavior (adjust to what you want):
- Initial RAM percentage set to 10% to give our Tomcat a bit a space from the beginning
- Max RAM of 50% if we are running a small container (below 250 MB of RAM)
- Max RAM of 80% if we are running a bigger container (above 250 MB of RAM). In my case, I’m running quite a big application, and my container will be definitively allocated more than 250 MB of memory. So this is the important section for me, as I want my tomcat application to take advantage of a maximum of 80% of the overall memoery my container has.
Using JAVA_OPTS environment variable
The easiest option is to pass those instructions to the container using the JAVA_OPTS environment variable.
On a 1GB RAM container it looks like this:
docker run --rm --name tomcat -m 1GB --env JAVA_OPTS="-XX:InitialRAMPercentage=10 -XX:MinRAMPercentage=50 -XX:MaxRAMPercentage=80" tomcat:9.0.44-jdk11-openjdk
I can then check that I’m getting a heap size of around 800 MB (80% of 1 GB):
docker exec tomcat jinfo 1 | grep MaxHeapSize>> MaxHeapSize=859832320
Using a setenv.sh file
If you are a Tomcat guy, you are most likely used to configure the JAVA_OPTS through a file located in Tomcat’s bin folder: $CATALINA_HOME/bin/setenv.sh
Simply edit (or create) the setenv.sh file and add the following line at the bottom:
export JAVA_OPTS="$JAVA_OPTS -XX:InitialRAMPercentage=10 -XX:MinRAMPercentage=50 -XX:MaxRAMPercentage=80"
Since this article is about Docker, you are most likely to inject the file in your Dockerfile. If you do so, don’t forget to give it execution permissions, otherwise it won’t work properly as catalina.sh will not be able to run your setenv.sh script:
FROM tomcat:9.0.44-jdk11-openjdk
COPY setenv.sh $CATALINA_HOME/bin/setenv.sh
RUN chmod +x $CATALINA_HOME/bin/setenv.sh
Conclusion
So hopefully now you understand how to adjust your Tomcat memory settings and give it more space than the default 25% you’ll get by default.
Credits
I wrote this article because I did not find easily the documentation I was looking for. Nevertheless it was inspired (thank you guys) from the following: