#!/usr/bin/env bash # What happens when the cache does not match the run. Each case starts the service with a cache that was made # under different conditions, and prints (1) what the JVM logged about the cache, (2) how long start-up took. # -XX:AOTMode=auto is the default: use the cache if it is valid, otherwise carry on without it. # -XX:AOTMode=on : refuse to start if the cache cannot be used. source "$(dirname "$0")/lib.sh" J25=$JDK25/bin/java; J27=$JDK27/bin/java run_case() { # title, workdir, cmd... (runs once with -Xlog:aot=warning so the cache's complaints show up) local title=$1 wd=$2; shift 2 local t0 log="$WORK/mismatch.log" echo "== $title" t0=$(now_ms) start_app "$wd" "$log" "$@" --server.port=$PORT if wait_ready; then echo " result: started, ready after $(( $(now_ms) - t0 )) ms ($(grep -o 'Started [A-Za-z]* in [0-9.]* seconds' "$log"))" stop_app else wait "$APP_PID" 2>/dev/null echo " result: did not start (exit code $?)" fi grep -E '\[(warning|error)[ ]*\]\[(aot|cds)|saved state of|created with' "$log" | grep -v 'Skipping' | head -5 | sed -E 's/^\[[0-9.]+s\]/ log: /' | cut -c1-170 [ -z "$(grep -E '\[(warning|error)[ ]*\]\[(aot|cds)' "$log" | grep -v Skipping)" ] && echo " log: (nothing at warning or error level)" echo } echo "# cache: $WORK/refresh.aot, trained on JDK 25.0.4.1, extracted layout, default GC (G1), 2 CPUs" echo run_case "control: the same JVM, the same jar, the same path" "$WORK/ext" "$J25" -XX:AOTCache="$WORK/refresh.aot" -jar app.jar # 1. the jar changed after training # (cp -a everywhere below: plain cp gives every file a new modification time, which the cache checks - see the timestamp case) rm -rf "$WORK/ext-changed"; cp -a "$WORK/ext" "$WORK/ext-changed" "$JDK25/bin/java" -Djarmode=tools -jar "$WORK/app-changed.jar" extract --destination "$WORK/changed-tmp" >/dev/null cp "$WORK/changed-tmp/app-changed.jar" "$WORK/ext-changed/app.jar"; rm -rf "$WORK/changed-tmp" # only the application jar differs run_case "the application jar was rebuilt after a one-line code change, default AOTMode=auto" "$WORK/ext-changed" "$J25" -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "the same, with -XX:AOTMode=on" "$WORK/ext-changed" "$J25" -XX:AOTMode=on -XX:AOTCache="$WORK/refresh.aot" -jar app.jar # 2. a dependency jar changed rm -rf "$WORK/ext-dep"; cp -a "$WORK/ext" "$WORK/ext-dep" ( cd "$WORK/ext-dep/lib" && f=$(ls spring-core-*.jar) && touch -d '2001-01-01' "$f" ) run_case "a dependency jar has a different modification time (same bytes)" "$WORK/ext-dep" "$J25" -XX:AOTCache="$WORK/refresh.aot" -jar app.jar # 2b. only a copy, with fresh timestamps (what `cp -r` or a re-extract does) rm -rf "$WORK/ext-copy"; cp -r "$WORK/ext" "$WORK/ext-copy" run_case "the same files copied with plain cp (bytes and path layout identical, timestamps new)" "$WORK/ext-copy" "$J25" -XX:AOTCache="$WORK/refresh.aot" -jar app.jar # 3. moved to another directory rm -rf "$WORK/moved"; mkdir -p "$WORK/moved"; cp -a "$WORK/ext" "$WORK/moved/app-dir" run_case "the application directory was moved after training (same bytes, different path)" "$WORK/moved/app-dir" "$J25" -XX:AOTCache="$WORK/refresh.aot" -jar app.jar # 4. fat jar cache used with the extracted layout run_case "cache trained on the fat jar, run from the extracted layout" "$WORK/ext" "$J25" -XX:AOTCache="$WORK/fat.aot" -jar app.jar # 5. different JVM run_case "JDK 25's cache used by JDK 27" "$WORK/ext" "$J27" -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "the same, with -XX:AOTMode=on" "$WORK/ext" "$J27" -XX:AOTMode=on -XX:AOTCache="$WORK/refresh.aot" -jar app.jar # 6. flags that change what the cache holds run_case "-XX:+UseCompactObjectHeaders added at run time only (JDK 25)" "$WORK/ext" "$J25" -XX:+UseCompactObjectHeaders -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "a different collector at run time: -XX:+UseSerialGC" "$WORK/ext" "$J25" -XX:+UseSerialGC -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "a different collector at run time: -XX:+UseParallelGC" "$WORK/ext" "$J25" -XX:+UseParallelGC -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "a different collector at run time: -XX:+UseZGC" "$WORK/ext" "$J25" -XX:+UseZGC -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "a different heap size at run time: -Xmx256m" "$WORK/ext" "$J25" -Xmx256m -XX:AOTCache="$WORK/refresh.aot" -jar app.jar run_case "a cache file that does not exist" "$WORK/ext" "$J25" -XX:AOTCache="$WORK/nonexistent.aot" -jar app.jar run_case "a cache file that does not exist, with -XX:AOTMode=on" "$WORK/ext" "$J25" -XX:AOTMode=on -XX:AOTCache="$WORK/nonexistent.aot" -jar app.jar echo "# and on JDK 27, whose cache was trained under the default collector (G1):" echo run_case "JDK 27, cache from JDK 27 (control)" "$WORK/ext" "$J27" -XX:AOTCache="$WORK/refresh27.aot" -jar app.jar run_case "JDK 27, -XX:+UseZGC at run time" "$WORK/ext" "$J27" -XX:+UseZGC -XX:AOTCache="$WORK/refresh27.aot" -jar app.jar run_case "JDK 27, -XX:+UseSerialGC at run time" "$WORK/ext" "$J27" -XX:+UseSerialGC -XX:AOTCache="$WORK/refresh27.aot" -jar app.jar run_case "JDK 27, -XX:-UseCompactObjectHeaders at run time" "$WORK/ext" "$J27" -XX:-UseCompactObjectHeaders -XX:AOTCache="$WORK/refresh27.aot" -jar app.jar echo "# why ZGC is different: ask the JVM (-Xlog:aot=info) why it refused the G1-trained cache" echo run_case "JDK 27, -XX:+UseZGC with the G1-trained cache, -Xlog:aot=info" "$WORK/ext" "$J27" -XX:+UseZGC -Xlog:aot=info -XX:AOTCache="$WORK/refresh27.aot" -jar app.jar run_case "JDK 27, cache trained under ZGC, run under ZGC" "$WORK/ext" "$J27" -XX:+UseZGC -XX:AOTCache="$WORK/zgc27.aot" -jar app.jar run_case "JDK 27, cache trained under ZGC, run under G1 (compressed oops on again)" "$WORK/ext" "$J27" -XX:AOTCache="$WORK/zgc27.aot" -jar app.jar run_case "JDK 27, cache trained under ZGC, run under G1 with -XX:-UseCompressedOops" "$WORK/ext" "$J27" -XX:-UseCompressedOops -XX:AOTCache="$WORK/zgc27.aot" -jar app.jar