UBI 9 OpenJDK images have MaxPercentage is set at 50/50

Solution Verified - Updated

Environment

  • Red Hat Enterprise Linux (RHEL)
    • 9.x
    • ubi 9 image
  • OpenJDK 8, OpenJDK 11, OpenJDK 17
  • Red hat OpenShift Container Platform (OCP)
    • 4.x

Issue

  • Ubi 9 OpenJDK images have MaxPercentage is set at 50/50
  • The following test shows the ubi9 image comes with 50/50:
$ docker run -it registry.access.redhat.com/ubi9/openjdk-17:latest java -XX:+PrintFlagsFinal  | grep -i maxram
uint64_t MaxRAM                                   = 137438953472                           {pd product} {default}
    uintx MaxRAMFraction                           = 4                                         {product} {default}
   double MaxRAMPercentage                         = 25.000000                                 {product} {default}

Resolution

The image must be run with the run-java.sh entrypoint to get the updated MaxRAMPercentage value. That's where it's set. What user sees above is the OpenJDK build, which isn't patched to increase it as that would have an effect on bare metal customers.

Start the application using the run-java.sh as below:

FROM registry.access.redhat.com/ubi9/openjdk-17-runtime:1.20-2.1726695178
COPY version.txt $JBOSS_HOME/version.txt
COPY lib/spring-boot-example.jar $JBOSS_HOME/deployments/spring-boot-example.jar
ENTRYPOINT ["/opt/jboss/container/java/run/run-java.sh", "-jar", "spring-boot-example.jar"]

And this is the expected outcome - 80% heap size + ParallelGC+MaxHeap at 20%, AdaptiveSizePolicyWeight and ExitOnOutOfMemoryError:

Command Line: -XX:MaxRAMPercentage=80.0 -XX:+UseParallelGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:+ExitOnOutOfMemoryError /deployments/spring-boot-example.jar -jar spring-boot-example.jar

Root Cause

If user runs the container without providing a command at all, run-java.sh will be executed, it will invoke java, java will complain about there being no application JAR, however user will see the default memory parameters:

$ docker run --rm -i ubi9/openjdk-17  2>&1 | head
...
Starting the Java application using /opt/jboss/container/java/run/run-java.sh ...
 [0;31mERROR Neither $JAVA_MAIN_CLASS nor $JAVA_APP_JAR is set and 0 JARs found in /deployments (1 expected) [0m
INFO exec -a "java" java -XX:MaxRAMPercentage=80.0 -XX:+UseParallelGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:+ExitOnOutOfMemoryError -cp "." -jar 
Error: -jar requires jar file specification
Usage: java [options] <mainclass> [args...]
           (to execute a class)
   or  java [options] -jar <jarfile> [args...]
           (to execute a jar file)
   or  java [options] -m <module>[/<mainclass>] [args...]
       java [options] --module <module>[/<mainclass>] [args...]

Adding more on this matter - doing a one by one comparison: /opt/jboss/container/java/run/run-java.sh > /opt/jboss/container/java/jvm/java-default-options, which sets the 80% default:

### UBI 9 OpenJDK 17 image
$ podman run --rm -it --entrypoint=/bin/bash registry.access.redhat.com/ubi9/openjdk-17
[default@765cbb44f5e3 ~]$ cat /opt/jboss/container/java/jvm/java-default-options | grep maxmem
      maxmem="80.0" <----------------------------------------------------------------------------------------------------
      maxmem="$(printf "%.0f.0" "$JAVA_MAX_MEM_RATIO")"
  echo "-XX:MaxRAMPercentage=$maxmem"
...

The dockerfile's entrypoint basically specifies the default start pattern and then calls run:

/usr/local/s2i/run ->  /opt/jboss/container/java/run/run-java.sh -> /opt/jboss/container/java/jvm/java-default-options

cat /root/buildinfo/Dockerfile-ubi9-openjdk-17-1.15-1.1686736679 <------------- that's the dockerfile (aka container file):

    # Define run cmd
    CMD ["/usr/local/s2i/run"]

cat /usr/local/s2i/run

[default@3cd588ec5ed1 ~]$ cat /usr/local/s2i/run
if [ -f "${S2I_TARGET_DEPLOYMENTS_DIR}/bin/run.sh" ]; then
    echo "Starting the application using the bundled ${S2I_TARGET_DEPLOYMENTS_DIR}/bin/run.sh ..."
    exec ${DEPLOYMENTS_DIR}/bin/run.sh $args ${JAVA_ARGS} <-------------------------------------------------- this path is not executed.
else
    echo "Starting the Java application using ${JBOSS_CONTAINER_JAVA_RUN_MODULE}/run-java.sh $args..."
    exec "${JBOSS_CONTAINER_JAVA_RUN_MODULE}/run-java.sh" $args ${JAVA_ARGS} <-------------------------------- this path is executed.
fi

cat run-java.sh

[default@ffe5da804d09 ~]$ cat /opt/jboss/container/java/run/run-java.sh | grep default
# Try hard to find a sane default jar-file
  # Load default default config
  # Check also $JAVA_APP_DIR. Overrides other defaults
  # It's valid to set the app dir in the default script
    # XXX: is this correct?  This is defaulted above to /deployments.  Should we
    # define a default to the old /opt/java-run?
  # JAVA_LIB_DIR defaults to JAVA_APP_DIR
  if [ -f "${JBOSS_CONTAINER_JAVA_JVM_MODULE}/java-default-options" ]; then
    jvm_opts=$(${JBOSS_CONTAINER_JAVA_JVM_MODULE}/java-default-options) <--------

UBI 8:

### UBI 8 OpenJDK 17 image
$ $ sudo podman run --rm -it --entrypoint=/bin/bash registry.access.redhat.com/ubi8/openjdk-17
[jboss@ab2238756fac ~]$ cat /opt/jboss/container/java/jvm/java-default-options | grep maxmem
      maxmem="50.0" <----------------------------------------------------------------------------------------------------
      maxmem="$(printf "%.0f.0" "$JAVA_MAX_MEM_RATIO")"
  echo "-XX:MaxRAMPercentage=$maxmem"

The UBI 8 still bring 50/50 ratio and should keep this value until the release of new images on Q3/Q4 2024.
In this matter the development of the images happens on the ubi9 and then is backported to ubi8 and this backport didn't happen yet (at the time of this KCS was written, but later was done).

Finally, the S2I will run the Content from github.com is not included.run.sh script (together with assembly.sh), which will then add:

Starting the Java application ...

Note are two relevant bugs that can break Java non-container awares indirectly:

JiraPurpose
Content from bugs.openjdk.org is not included.JDK-8347129cpuset cgroups controller is required for no good reason
Content from bugs.openjdk.org is not included.JDK-8347811The OpenJDK container detection code still uses the deprecated feature '/proc/cgroups' and should migrate to '/sys/fs/cgroup/cgroup.subtree_control' instead for cg v2 in order to detect enabled controllers.

Diagnostic Steps

  1. Verify if the application is being started with run-java.sh on the inspect and VM.info
  2. Use VM.info to specifically look into Java settings.
  3. When the container is started with run-java.sh (e.g. ENTRYPOINT ["/opt/jboss/container/java/run/run-java.sh", "-jar", "spring-boot.jar"]), the ParallelGC comes up, and this comes from opt/jboss/container/java/jvm/java-default-option where it sets:
  local gcOptions="${GC_CONTAINER_OPTIONS:--XX:+UseParallelGC}"
  1. Finally, no maxmetaspace is set:
 # for compat reasons we don't set a default value for metaspaceSize
  local metaspaceSize
  # We also don't set a default value for maxMetaspaceSize
  local maxMetaspaceSize=${GC_MAX_METASPACE_SIZE}
Components

This solution is part of Red Hat’s fast-track publication program, providing a huge library of solutions that Red Hat engineers have created while supporting our customers. To give you the knowledge you need the instant it becomes available, these articles may be presented in a raw and unedited form.