#!/usr/bin/env bash # The Java 26 recap lane: what 26 changed that a 25 -> 27 upgrade meets in one go. # ./scripts/recap26.sh -> docs/output/70-final-field-mutation.txt, 71-aot-cache-matrix.txt, 72-aot-startup.txt, # 73-removed-in-26.txt, 74-api26.txt, 75-http3-fallback.txt # Needs JDK 25 (LTS), 26 and 27 on disk. set -uo pipefail source "$(dirname "$0")/env.sh" SRC="$ROOT/recap26/src"; B="$ROOT/build/recap26"; rm -rf "$B"; mkdir -p "$B/26" "$B/25" "$B/aot" "$JDK26/bin/javac" -d "$B/26" "$SRC/Check.java" "$SRC/FinalFieldMutation.java" "$SRC/Api26.java" "$SRC/Http3Fallback.java" "$JDK25/bin/javac" --release 25 -d "$B/25" "$SRC/FinalFieldMutation.java" # ---------- JEP 500: final means final ---------- { echo "# JEP 500: reflection writing to a final field, same class file behaviour on 25 and on 26" echo; echo "=== JDK 25 (class compiled --release 25)"; "$JDK25/bin/java" -cp "$B/25" FinalFieldMutation 2>&1 | sed 's/^/ /' echo; echo "=== JDK 26, defaults"; "$JDK26/bin/java" -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /' echo; echo "=== JDK 26, --enable-final-field-mutation=ALL-UNNAMED (the opt-in the warning asks for)" "$JDK26/bin/java" --enable-final-field-mutation=ALL-UNNAMED -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /' echo; echo "=== JDK 26, --illegal-final-field-mutation=deny (what a future default will look like)" "$JDK26/bin/java" --illegal-final-field-mutation=deny -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /' echo; echo "=== JDK 26, --illegal-final-field-mutation=debug (warning plus the offending stack trace)" "$JDK26/bin/java" --illegal-final-field-mutation=debug -cp "$B/26" FinalFieldMutation 2>&1 | sed 's/^/ /' | head -12 for mode in "" "--enable-final-field-mutation=ALL-UNNAMED" "--illegal-final-field-mutation=deny"; do echo; echo "=== JDK 26, JFR default settings, [${mode:-no flag}]: jfr print --events jdk.FinalFieldMutation" rm -f "$B/ffm.jfr" "$JDK26/bin/java" $mode -XX:StartFlightRecording:filename="$B/ffm.jfr",settings=default -cp "$B/26" FinalFieldMutation >/dev/null 2>&1 "$JDK26/bin/jfr" print --events jdk.FinalFieldMutation "$B/ffm.jfr" 2>&1 \ | grep -E 'jdk.FinalFieldMutation|declaringClass|fieldName|FinalFieldMutation.main' | sed 's/^ */ /' | sed -E 's/\{$//' [ "${PIPESTATUS[0]}" -eq 0 ] || true echo " events recorded: $("$JDK26/bin/jfr" summary "$B/ffm.jfr" | awk '/jdk.FinalFieldMutation /{print $2}')" done } > "$OUT/70-final-field-mutation.txt" 2>&1 # ---------- JEP 516: AOT object caching with any GC ---------- APP="$B/aot/AotApp"; mkdir -p "$APP" cp "$ROOT/recap26/src/AotApp.java" "$APP/" for v in 25 26 27; do jdk="JDK$v"; mkdir -p "$B/aot/$v/cls" "${!jdk}/bin/javac" --release 25 -d "$B/aot/$v/cls" "$APP/AotApp.java"; (cd "$B/aot/$v/cls" && "${!jdk}/bin/jar" cf ../app.jar AotApp.class); done mkcache() { # -> $B/aot//.aot local j="$1" v="$2" gc="$3" d="$B/aot/$2"; rm -f "$d/$gc.aot" "$d/$gc.conf" "$j/bin/java" -XX:+Use${gc}GC -XX:AOTMode=record -XX:AOTConfiguration="$d/$gc.conf" -cp "$d/app.jar" AotApp >/dev/null 2>&1 "$j/bin/java" -XX:+Use${gc}GC -XX:AOTMode=create -XX:AOTConfiguration="$d/$gc.conf" -XX:AOTCache="$d/$gc.aot" -cp "$d/app.jar" >/dev/null 2>&1; } { echo "# JEP 516: can the cache's archived heap objects be used? One row per (JDK, GC the cache was built under, GC at run time)." echo "# 'heap objects' = the log says the archived module graph was used; 'unusable' = the JVM refused the whole cache." for v in 25 26 27; do jdk="JDK$v"; for build in G1 Z; do mkcache "${!jdk}" "$v" "$build" for run in Serial G1 Z; do log="$("${!jdk}/bin/java" -XX:+Use${run}GC -XX:AOTCache="$B/aot/$v/$build.aot" -Xlog:aot=info -cp "$B/aot/$v/app.jar" AotApp 2>&1)" if grep -q 'Unable to use AOT cache' <<<"$log"; then r="unusable: $(grep -o 'saved state of.*CDS will be disabled' <<<"$log" | head -1 | sed 's/, CDS will be disabled//')" elif grep -q 'full module graph: enabled' <<<"$log"; then r="heap objects used (full module graph: enabled)" else r="cache used, heap objects NOT used (full module graph: disabled)"; fi printf 'JDK %s cache built under %-2s run under %-6s -> %s\n' "$v" "$build" "$run" "$r" done; done; done } > "$OUT/71-aot-cache-matrix.txt" 2>&1 median() { sort -n | awk '{a[NR]=$1} END{print a[int((NR+1)/2)]}'; } bench() { # [cache-file] local j="$1" v="$2" gc="$3" cache="${4:-}" i t0 t1; local -a ts=() for i in 1 2 3 4 5 6 7 8 9; do t0=$(date +%s%N) if [ -n "$cache" ]; then "$j/bin/java" -XX:+Use${gc}GC -XX:AOTCache="$cache" -cp "$B/aot/$v/app.jar" AotApp >/dev/null 2>&1 else "$j/bin/java" -XX:+Use${gc}GC -Xshare:auto -XX:-AOTClassLinking -cp "$B/aot/$v/app.jar" AotApp >/dev/null 2>&1; fi t1=$(date +%s%N); ts+=($(( (t1 - t0) / 1000000 ))) done; printf '%s\n' "${ts[@]}" | median; } { echo "# Wall-clock ms for 'java -cp app.jar AotApp' (median of 9), no AOT cache vs cache built under the same GC. INDICATIVE: shared 2-CPU sandbox." for v in 25 26 27; do jdk="JDK$v"; for gc in G1 Z; do mkcache "${!jdk}" "$v" "$gc" printf 'JDK %s %-2s no cache: %4s ms with cache: %4s ms\n' "$v" "$gc" "$(bench "${!jdk}" "$v" "$gc")" "$(bench "${!jdk}" "$v" "$gc" "$B/aot/$v/$gc.aot")" done; done } > "$OUT/72-aot-startup.txt" 2>&1 # committed transcripts must not carry the author's paths or per-run identity hashes scrub() { sed -i -E "s#$ROOT##g; s#@[0-9a-f]{6,8}#@#g" "$@"; } scrub "$OUT/70-final-field-mutation.txt" "$OUT/71-aot-cache-matrix.txt" "$OUT/72-aot-startup.txt" # ---------- JEP 504 and friends: things that were removed ---------- compile() { local jdk="$1" rel="$2" f="$3"; local out rc; out="$("$jdk/bin/javac" --release "$rel" -d "$B/rm" "$f" 2>&1)"; rc=$? printf ' javac (JDK %s, --release %s): exit %s\n' "$4" "$rel" "$rc"; printf '%s\n' "$out" | grep -v '^Note:' | sed 's/^/ /'; } mkdir -p "$B/rm" { echo "# Source that compiled on 25 and does not on 26" for f in AppletGone ThreadStopGone; do echo; echo "=== recap26/src/broken/$f.java" compile "$JDK25" 25 "$SRC/broken/$f.java" 25; compile "$JDK26" 26 "$SRC/broken/$f.java" 26; done } > "$OUT/73-removed-in-26.txt" 2>&1 # ---------- new API in 26 ---------- { "$JDK26/bin/java" -Dstdout.encoding=UTF-8 -cp "$B/26" Api26; echo "exit code: $?"; } > "$OUT/74-api26.txt" 2>&1 # ---------- JEP 517: HTTP/3 client, checkable half ---------- "$JDK26/bin/keytool" -genkeypair -alias demo -keyalg EC -groupname secp256r1 -dname "CN=localhost" -validity 2 -storetype PKCS12 \ -keystore "$B/ks.p12" -storepass changeit -ext san=dns:localhost >/dev/null 2>&1 { echo "# JEP 517: a client that prefers HTTP/3 against a server that only speaks HTTP/1.1 over TLS (loopback, JDK's own HttpsServer)" echo "# There is no live HTTP/3 server in this transcript: the sandbox has no UDP egress. The API surface is javap output below." "$JDK26/bin/java" -cp "$B/26" Http3Fallback "$B/ks.p12" changeit 2>&1 echo; echo "# javap --module java.net.http -public java.net.http.HttpOption 'java.net.http.HttpOption\$Http3DiscoveryMode'" "$JDK26/bin/javap" --module java.net.http -public 'java.net.http.HttpOption' 'java.net.http.HttpOption$Http3DiscoveryMode' 2>&1 echo; echo "# javap ... HttpClient\$Version"; "$JDK26/bin/javap" --module java.net.http -public 'java.net.http.HttpClient$Version' 2>&1 | grep -E 'HTTP_' } > "$OUT/75-http3-fallback.txt" 2>&1 scrub "$OUT/73-removed-in-26.txt" "$OUT/74-api26.txt" "$OUT/75-http3-fallback.txt"