MaxRAMPercentage vs a hardcoded -Xmx, -XX:-UseContainerSupport, the ActiveProcessorCount lie, and which diagnostic flags are worth leaving on in prod, verified in real Docker containers on JDK 25 and JDK 27. Co-Authored-By: Claude Sonnet 5 <[email protected]>
108 lines
5.5 KiB
Bash
Executable File
108 lines
5.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Regenerates every file under docs/output/. Requires Docker (a running daemon)
|
|
# and network access to pull eclipse-temurin:25-jre, eclipse-temurin:25-jdk, and
|
|
# ubuntu:24.04, plus ~280MB to download the Temurin 27 GA tarball (no official
|
|
# eclipse-temurin:27-* image exists on Docker Hub as of 2026-10-01, which is why
|
|
# docker/Dockerfile.jdk27 installs the tarball directly).
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
echo "== building images =="
|
|
mvn -q -B clean package -DskipTests
|
|
cp target/flags-demo.jar docker/flags-demo.jar
|
|
if [ ! -f docker/temurin27.tar.gz ]; then
|
|
curl -sL -o docker/temurin27.tar.gz \
|
|
"https://api.adoptium.net/v3/binary/latest/27/ga/linux/x64/jdk/hotspot/normal/eclipse"
|
|
fi
|
|
docker build -t flags-demo:jdk25 -f docker/Dockerfile.jdk25 docker
|
|
docker build -t flags-demo:jdk25-jdk -f docker/Dockerfile.jdk25-jdk docker
|
|
docker build -t flags-demo:jdk27 -f docker/Dockerfile.jdk27 docker
|
|
|
|
echo "== 01/02: GC selection matrix, JDK 25 vs JDK 27 (JEP 523) =="
|
|
combos=("256m:1" "512m:1" "1800m:1" "512m:2" "1800m:2" "4g:2")
|
|
for jdk in jdk25 jdk27; do
|
|
out="docs/output/0$([ "$jdk" = jdk25 ] && echo 1 || echo 2)-gc-selection-$jdk.txt"
|
|
: > "$out.tmp"
|
|
for c in "${combos[@]}"; do
|
|
mem="${c%%:*}"; cpu="${c##*:}"
|
|
echo "--- mem=$mem cpus=$cpu ---" >> "$out.tmp"
|
|
docker run --rm --memory="$mem" --cpus="$cpu" --entrypoint java "flags-demo:$jdk" \
|
|
-XX:+PrintFlagsFinal -version 2>/dev/null \
|
|
| grep -E "^\s*(bool UseG1GC|bool UseSerialGC|bool UseParallelGC|size_t MaxHeapSize|int ActiveProcessorCount|bool UseCompactObjectHeaders|double MaxRAMPercentage)\s*=" \
|
|
>> "$out.tmp"
|
|
echo "" >> "$out.tmp"
|
|
done
|
|
mv "$out.tmp" "$out"
|
|
done
|
|
|
|
echo "== 03: ActiveProcessorCount lying to the JVM =="
|
|
{
|
|
docker run --rm --cpus=1 --memory=1g --entrypoint java flags-demo:jdk27 \
|
|
-XX:+PrintFlagsFinal -version 2>/dev/null | grep -E "ParallelGCThreads|ConcGCThreads|ActiveProcessorCount"
|
|
echo ""
|
|
docker run --rm --cpus=1 --memory=1g --entrypoint java flags-demo:jdk27 \
|
|
-XX:ActiveProcessorCount=8 -XX:+PrintFlagsFinal -version 2>/dev/null | grep -E "ParallelGCThreads|ConcGCThreads|ActiveProcessorCount"
|
|
} > docs/output/03-activeprocessorcount-lie.txt.body
|
|
|
|
echo "== 04: compact object headers, byte-for-byte (JEP 534) =="
|
|
run_histo() {
|
|
local tag=$1 extra=$2 cname=$3
|
|
docker run -d --memory=1g --name "$cname" -v "$PWD/docker/headersize.jar:/headersize.jar" \
|
|
--entrypoint java "flags-demo:$tag" $extra -cp /headersize.jar HeaderSizeDemo 2000000 >/dev/null
|
|
sleep 6
|
|
docker exec "$cname" jcmd 1 GC.class_histogram 2>&1 | grep -E "Pair|Total"
|
|
docker rm -f "$cname" >/dev/null 2>&1
|
|
}
|
|
{
|
|
echo "--- JDK 25, default (UseCompactObjectHeaders=false) ---"
|
|
run_histo jdk25-jdk "" hdr25default
|
|
echo ""
|
|
echo "--- JDK 25, -XX:+UseCompactObjectHeaders ---"
|
|
run_histo jdk25-jdk "-XX:+UseCompactObjectHeaders" hdr25on
|
|
echo ""
|
|
echo "--- JDK 27, default (UseCompactObjectHeaders=true) ---"
|
|
run_histo jdk27 "" hdr27default
|
|
echo ""
|
|
echo "--- JDK 27, -XX:-UseCompactObjectHeaders ---"
|
|
run_histo jdk27 "-XX:-UseCompactObjectHeaders" hdr27off
|
|
} > docs/output/04-compact-object-headers.txt.body
|
|
|
|
echo "== 05: a correctly set MaxRAMPercentage fails safely =="
|
|
docker run --rm --memory=256m -v "$PWD/docker/memoryhog.jar:/memoryhog.jar" --entrypoint java flags-demo:jdk25 \
|
|
-Xlog:gc -XX:MaxRAMPercentage=75 -cp /memoryhog.jar MemoryHog 24 200 \
|
|
> docs/output/05-maxrampercentage-correct-oom.txt.body 2>&1 || true
|
|
|
|
echo "== 06: a hardcoded -Xmx bigger than the container gets the container killed =="
|
|
docker run --rm --memory=256m -v "$PWD/docker/memoryhog.jar:/memoryhog.jar" --entrypoint java flags-demo:jdk25 \
|
|
-Xlog:gc -Xmx1g -cp /memoryhog.jar MemoryHog 24 200 \
|
|
> docs/output/06-hardcoded-xmx-oomkilled.txt.body 2>&1
|
|
echo "(exit code above should be 137 - SIGKILL from the cgroup OOM killer)"
|
|
|
|
echo "== 07: -XX:-UseContainerSupport makes the JVM blind to the container =="
|
|
{
|
|
docker run --rm --memory=256m --entrypoint java flags-demo:jdk25 \
|
|
-XX:-UseContainerSupport -XX:+PrintFlagsFinal -version 2>/dev/null | grep -E "MaxHeapSize|UseContainerSupport"
|
|
echo ""
|
|
docker run --rm --memory=256m -v "$PWD/docker/memoryhog.jar:/memoryhog.jar" --entrypoint java flags-demo:jdk25 \
|
|
-Xlog:gc -XX:-UseContainerSupport -cp /memoryhog.jar MemoryHog 24 200
|
|
} > docs/output/07-usecontainersupport-false-oomkilled.txt.body 2>&1 || true
|
|
|
|
echo "== 08: -XX:+HeapDumpOnOutOfMemoryError actually leaves a dump =="
|
|
mkdir -p /tmp/flags-demo-dumps
|
|
docker run --rm --memory=256m -v "$PWD/docker/memoryhog.jar:/memoryhog.jar" -v /tmp/flags-demo-dumps:/dumps \
|
|
--entrypoint java flags-demo:jdk25-jdk \
|
|
-XX:MaxRAMPercentage=75 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/dumps/heap.hprof \
|
|
-cp /memoryhog.jar MemoryHog 24 200 > docs/output/08-heapdumponoutofmemoryerror.txt.body 2>&1 || true
|
|
ls -la /tmp/flags-demo-dumps >> docs/output/08-heapdumponoutofmemoryerror.txt.body
|
|
rm -rf /tmp/flags-demo-dumps
|
|
|
|
echo "== 09: -XX:NativeMemoryTracking=summary =="
|
|
docker run -d --memory=512m --name nmtdemo -v "$PWD/docker/flags-demo.jar:/app.jar" --entrypoint java flags-demo:jdk25-jdk \
|
|
-XX:NativeMemoryTracking=summary -XX:MaxRAMPercentage=75 -jar /app.jar >/dev/null
|
|
sleep 6
|
|
docker exec nmtdemo jcmd 1 VM.native_memory summary > docs/output/09-native-memory-tracking.txt.body 2>&1
|
|
docker rm -f nmtdemo >/dev/null 2>&1
|
|
|
|
echo "Done. Each docs/output/NN-*.txt.body is the fresh capture; prepend the command"
|
|
echo "comment block from the committed .txt file by hand if you are regenerating for a diff."
|