How to control Java memory in Tomcat running on Docker

Laurent Bel
Pernod Ricard Tech
Published in
5 min readApr 24, 2021

--

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.

--

--

Laurent Bel
Pernod Ricard Tech

Leading the IT Architecture & Innovation team at Pernod Ricard. Interested in IT technology in general.