Add flags: production JVM flag cheat sheet for Spring Boot on JDK 25/27
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]>
This commit is contained in:
Executable
+107
@@ -0,0 +1,107 @@
|
||||
#!/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."
|
||||
Reference in New Issue
Block a user