Docker

Java 8/11 and Docker (Part 2)

Written by: Marcel Koert B.S.E.E. | Posted on: | Category:

The memory

The problem

So let's start with a simple stack on the image cnj / openjdk-jvm-info . src / main / openjdk / stack-memory-default.yml

1.  version: '3.7'
2.
3.  services:
4.  jvm-info:
5.  image: cnj / openjdk-jvm-info
6.  ports:
7.  - "8080: 8080"
8.  environment:
9.  JAVA_OPTS:>
10. -XX: + PrintFlagsFinal
11. -XX: + PrintGCDetails
12. deploy:
13. resources:
14. limits:
15. memory: 1280

Leaving the container like this,

docker stack deploy -c src / docker / openjdk / stack-memory-default.yml openjdk

the JVM prints its configuration (thanks to -XX: + PrintFlagsFinal and -XX: + PrintGCDetails ) and takes its default values, so the memory should be 1/4 of the total (governed by the parameter -XX: MaxRAMFraction = 4 by default on 64-bit JVM Servers with more than 1Gb of RAM ), or 320Mb. Let's take a look at the application logs and see the calculated -Xmx (which corresponds to the MaxHeapSize parameter :

docker service -f logs openjdk_jvm-info | grep MaxHeapSize

We find that MaxHeapSize: = 524288000 bytes, or 500Mb , which happens to be 1/4 of 4Gb , or the host machine , not the limits that I have imposed on the container! For convenience, the endpoint http: // localhost: 8080 / jvm is available, which also reports the memory information: it is less precise, but the order of magnitude is always that.

But didn't we say that since update 131 we could be calm? Yes, as long as you pass particular parameters to the JVM : -XX: + UnlockExperimentalVMOptions and -XX: + UseCGroupMemoryLimitForHeap stack-memory-unlock-experimentals.yml

1.  version: '3.7'
2.  
3.  services:
4.  jvm-info:
5.  image: cnj / openjdk-jvm-info
6.  ports:
7.  - "8080: 8080"
8.  environment:
9.  JAVA_OPTS:>
10. -XX: + UnlockExperimentalVMOptions
11. -XX: + UseCGroupMemoryLimitForHeap
12. -XX: + PrintFlagsFinal
13. -XX: + PrintGCDetails
14. deploy:
15. resources:
16. limits:
17. memory: 1280M

Updating the stack with the new configuration

docker stack deploy -c src / docker / openjdk / stack-memory-default.yml openjdk docker service -f logs openjdk_jvm-info | grep MaxHeapSize

After a few seconds we will see that a new log line will appear , relating to the new container that has been created: this time it reports MaxHeapSize: = 335544320 bytes, or exactly 320Mb , that is 1/4 of 1280Mb.

The solution

Then enabling the -XX: + UnlockExperimentalVMOptions and -XX: + UseCGroupMemoryLimitForHeap parameters , the JVM begins to behave properly . Normally, however, there is a tendency to manually set the value of the JVM memory we want, knowing as a best practice that the values ​​of -Xms and -Xmx is good that they are the same for a matter of performance.

As a main rule, recommended by Red Hat, it is important to emphasize that the limit given to the Container must be almost double the heap . This is trivially because the memory model of the JVM is not formed only by the heap: so if this is equal to the limit of the container, there is no more space for the rest, Docker detects that the dedicated resources are being exceeded and kills the container .

Having established the relationship "Docker resource limit" and "JVM heap", we can proceed in two ways: • we explicitly define the parameters of the JVM -Xms and -Xmx as half of those that we give to the limits of Docker, so as to have tolerance:

1.  version: '3.7'
2.  
3.  services:
4.  jvm-info:
5.  image: cnj / openjdk-jvm-info
6.  ports:
7.  - "8080: 8080"
8.  environment:
9.  JAVA_OPTS:>
10. -XX: + UnlockExperimentalVMOptions
11. -XX: + UseCGroupMemoryLimitForHeap
12. -Xms640m
13. -Xmx640m
14. deploy:
15. resources:
16. limits:
17. memory: 1280M

Theoretically, in this case, unlock experimental would not be needed, but it is good that the JVM understands where it is running.

We let the JVM calculate the limits , however specifying that the minimum and the maximum heap size are half the imposed limit , thanks to the -XX: InitialRAMFraction = 2 and -XX: MaxRAMFraction = 2 :

1.  version: '3.7'
2.  
3.  services:
4.  jvm-info:
5.  image: cnj / openjdk-jvm-info
6.  ports:
7.  - "8080: 8080"
8.  environment:
9.  JAVA_OPTS:>
10. -XX: + UnlockExperimentalVMOptions
11. -XX: + UseCGroupMemoryLimitForHeap
12. -XX: InitialRAMFraction = 2
13. -XX: MaxRAMFraction = 2
14. deploy:
15. resources:
16. limits:
17. memory: 1280M

we see that InitialHeapSize (which corresponds to -Xms ) and MaxHeapSize (which corresponds to -Xmx ) have the same value

docker service logs -f jvm_jvm-info | grep -Ei "InitialHeapSize | MaxHeapSize"

equal to 512Mb. Solved the memory, let's see what we can do for the CPU.

© 2019 Marcel Koert for MeloMar IT BV