Set your Garbage Collector
A less known thing about deploying a JVM in a container is what garbage collector will be set, if you do not specify one. Let’s look at the cases of JVM running in a container and see what GC will be set by default as I experiment with different Java versions and memory limits.
Java 8 - OpenJDK8-alpine
With memory limit 1791Mb
podman run --memory=1791m -ti openjdk:8-alpine java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
bool UseAdaptiveGCBoundary = false {product}
bool UseAdaptiveSizeDecayMajorGCCost = true {product}
bool UseAdaptiveSizePolicyWithSystemGC = false {product}
bool UseAutoGCSelectPolicy = false {product}
bool UseConcMarkSweepGC = false {product}
bool UseDynamicNumberOfGCThreads = false {product}
bool UseG1GC = false {product}
bool UseGCLogFileRotation = false {product}
bool UseGCOverheadLimit = true {product}
bool UseGCTaskAffinity = false {product}
bool UseMaximumCompactionOnSystemGC = true {product}
bool UseParNewGC = false {product}
bool UseParallelGC := true {product}
bool UseParallelOldGC = true {product}
bool UseSerialGC = false {product}
You can see here for memory limit of 1791Mb JVM decided to use UseParallelGC
.
With memory limit 2024Mb
Let’s increase the memory limit:
podman run --memory=2048m -ti openjdk:8-alpine java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
bool UseAdaptiveGCBoundary = false {product}
bool UseAdaptiveSizeDecayMajorGCCost = true {product}
bool UseAdaptiveSizePolicyWithSystemGC = false {product}
bool UseAutoGCSelectPolicy = false {product}
bool UseConcMarkSweepGC = false {product}
bool UseDynamicNumberOfGCThreads = false {product}
bool UseG1GC = false {product}
bool UseGCLogFileRotation = false {product}
bool UseGCOverheadLimit = true {product}
bool UseGCTaskAffinity = false {product}
bool UseMaximumCompactionOnSystemGC = true {product}
bool UseParNewGC = false {product}
bool UseParallelGC := true {product}
bool UseParallelOldGC = true {product}
bool UseSerialGC = false {product}
For 2048Mb it’s still UseParallelGC
Java 11 - OpenJDK:11
With memory limit 1791Mb
podman run --memory=1791m -ti openjdk:11 java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
bool UseAdaptiveGCBoundary = false {product} {default}
bool UseAdaptiveSizeDecayMajorGCCost = true {product} {default}
bool UseAdaptiveSizePolicyWithSystemGC = false {product} {default}
bool UseConcMarkSweepGC = false {product} {default}
bool UseDynamicNumberOfGCThreads = true {product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseGCOverheadLimit = true {product} {default}
bool UseGCTaskAffinity = false {product} {default}
bool UseMaximumCompactionOnSystemGC = true {product} {default}
bool UseParallelGC = false {product} {default}
bool UseParallelOldGC = false {product} {default}
bool UseSerialGC = false {product} {default}
For Java 11 and 1791Mb of memory limit, the default is UseG1GC
.
With memory limit 256Mb
Due to a bug in JVM 11, for some time, you might have had G1GC here:
podman run --memory=256m -ti openjdk:11 java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
bool UseAdaptiveGCBoundary = false {product} {default}
bool UseAdaptiveSizeDecayMajorGCCost = true {product} {default}
bool UseAdaptiveSizePolicyWithSystemGC = false {product} {default}
bool UseConcMarkSweepGC = false {product} {default}
bool UseDynamicNumberOfGCThreads = true {product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseGCOverheadLimit = true {product} {default}
bool UseGCTaskAffinity = false {product} {default}
bool UseMaximumCompactionOnSystemGC = true {product} {default}
bool UseParallelGC = false {product} {default}
bool UseParallelOldGC = false {product} {default}
bool UseSerialGC = false {product} {default}
Newer and patched JVM 11 should report G1GC:
bool UseAdaptiveGCBoundary = false {product} {default}
bool UseAdaptiveSizeDecayMajorGCCost = true {product} {default}
bool UseAdaptiveSizePolicyWithSystemGC = false {product} {default}
bool UseConcMarkSweepGC = false {product} {default}
bool UseDynamicNumberOfGCThreads = true {product} {default}
bool UseG1GC = false {product} {default}
bool UseGCOverheadLimit = true {product} {default}
bool UseGCTaskAffinity = false {product} {default}
bool UseMaximumCompactionOnSystemGC = true {product} {default}
bool UseParallelGC = false {product} {default}
bool UseParallelOldGC = false {product} {default}
bool UseSerialGC = true {product} {ergonomic}
You can reproduce the JVM bug with, by using this specific JVM 11 version:
podman run --memory=256m -ti openjdk:11 java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
podman run --memory=256m -ti docker.io/adoptopenjdk/openjdk11@sha256:2682486727f32756e15b425a0d029702da638a138fe4470e627794512d7f0d17 java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
Java 17 - OpenJDK:11
With memory limit 1791Mb
On OpenJDK 17 with 1791Mb of memory the default GC is UseSerialGC
.
podman run --memory=1791m -ti openjdk:17-alpine java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
bool UseAdaptiveSizeDecayMajorGCCost = true {product} {default}
bool UseAdaptiveSizePolicyWithSystemGC = false {product} {default}
bool UseDynamicNumberOfGCThreads = true {product} {default}
bool UseG1GC = false {product} {default}
bool UseGCOverheadLimit = true {product} {default}
bool UseMaximumCompactionOnSystemGC = true {product} {default}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = true {product} {ergonomic}
bool UseShenandoahGC = false {product} {default}
bool UseZGC = false {product} {default}
With memory limit 1792Mb
podman run --memory=1792m -ti openjdk:17-alpine java -XX:+PrintFlagsFinal -XX:+UseContainerSupport | grep 'Use.*GC'
bool UseAdaptiveSizeDecayMajorGCCost = true {product} {default}
bool UseAdaptiveSizePolicyWithSystemGC = false {product} {default}
bool UseDynamicNumberOfGCThreads = true {product} {default}
bool UseG1GC = true {product} {ergonomic}
bool UseGCOverheadLimit = true {product} {default}
bool UseMaximumCompactionOnSystemGC = true {product} {default}
bool UseParallelGC = false {product} {default}
bool UseSerialGC = false {product} {default}
bool UseShenandoahGC = false {product} {default}
bool UseZGC = false {product} {default}
When memory limit is >=1792Mb on OpenJDK 17, JVM will switch to UseG1GC
.
Results
We noticed at one of our high throughput containers got 5% change in (generally understood) performance, after setting GC to what we wanted, system went back to normal.
Test your own containers. Set your GC.