Set your Garbage Collector

Page content

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.