Java 27 and 26: runnable demos and captured output for every JEP, plus version lanes

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
This commit is contained in:
2026-09-21 15:08:50 +00:00
committed by Claude
co-authored by Claude Sonnet 5
commit f59c1de96d
152 changed files with 5049 additions and 0 deletions
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Public-API diffs between JDK 25 (LTS), 26 and 27, produced from javap dumps of every exported class.
# ./scripts/api-diff.sh -> docs/output/50-vector-api-surface.txt, 51-api-diff-26-to-27.txt, 52-api-diff-25-to-26.txt
# The dumps are 5 MB each and take a few minutes to build, so they are cached in build/apidiff/.
set -euo pipefail
source "$(dirname "$0")/env.sh"
D="$ROOT/build/apidiff"; mkdir -p "$D"
for v in 25 26 27; do
jdk="JDK$v"
[ -s "$D/api-$v.txt" ] || python3 "$ROOT/api-diff/dump_api.py" "${!jdk}" "$D/api-$v.txt"
done
{ echo "# Public API removed/added/changed between JDK 26 and 27 (javap -public of every exported class; internal packages skipped)"; echo
python3 "$ROOT/api-diff/apidiff.py" "$D/api-26.txt" "$D/api-27.txt"; } > "$OUT/51-api-diff-26-to-27.txt"
{ echo "# Public API removed/added/changed between JDK 25 and 26"; echo
python3 "$ROOT/api-diff/apidiff.py" "$D/api-25.txt" "$D/api-26.txt"; } > "$OUT/52-api-diff-25-to-26.txt"
{ echo "# jdk.incubator.vector: what changed between 26 and 27 (JEP 529 was the 11th incubation in 26, JEP 537 is the 12th in 27)"; echo
python3 "$ROOT/api-diff/apidiff.py" "$D/api-26.txt" "$D/api-27.txt" --vector-only; } > "$OUT/50-vector-api-surface.txt"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# The most useful demonstrations of a preview API are the compile errors. This compiles the same
# JDK 26 sources with the 26 and the 27 compiler and commits what javac actually said.
# ./scripts/broken-on-27.sh -> docs/output/20-broken-*.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
SRC="$ROOT/jep-tour/src"
compile() { # jdk-home release file -> prints javac's own output, then its real exit code
local out rc
out="$( cd "$SRC/broken" && "$1/bin/javac" --enable-preview --release "$2" -Xlint:-preview -d "$ROOT/build/broken-$2" "$3" 2>&1 )"; rc=$?
printf '%s\n' "$out" | grep -v -E '^(Note: |$)' || true
echo "exit=$rc"
}
for f in Pem26Style StructuredScope26 Lazy26; do
out="$OUT/20-broken-$(echo "$f" | tr 'A-Z' 'a-z').txt"
{
echo "\$ javac --enable-preview --release 26 $f.java (JDK 26)"
compile "$JDK26" 26 "$f.java"
echo
echo "\$ javac --enable-preview --release 27 $f.java (JDK 27)"
compile "$JDK27" 27 "$f.java"
} > "$out" 2>&1
echo "wrote $out"
done
{
echo "\$ javac --enable-preview --release 27 Dominated.java (JDK 27)"
compile "$JDK27" 27 Dominated.java
} > "$OUT/20-broken-dominated.txt" 2>&1
echo "wrote $OUT/20-broken-dominated.txt"
Executable
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env bash
# Where the JDKs live. Override on the command line: JDK27=/path/to/jdk-27 ./scripts/run-all.sh
# Every demo that compares releases needs BOTH a JDK 26 and a JDK 27 on disk.
export JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"
export JDK26="${JDK26:-/opt/jdks/jdk26}"
export JDK27="${JDK27:-/opt/jdks/jdk27}"
# Container demos use these images (same vendor for both releases, so the only variable is the JDK version).
export IMG26="${IMG26:-amazoncorretto:26}"
export IMG27="${IMG27:-amazoncorretto:27}"
# The JVM prints "Picked up JAVA_TOOL_OPTIONS" on every launch in some sandboxes; it is noise in a transcript.
unset JAVA_TOOL_OPTIONS
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
export ROOT
OUT="$ROOT/docs/output"; mkdir -p "$OUT"; export OUT
# The 21 -> 25 lane demos (lanes.sh) also need a JDK 21.
export JDK21="${JDK21:-/opt/jdks/jdk21}"
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
# What does the switch cost on a machine that used to get Serial? 1 CPU, 2 GB, three runs each.
# Timing is indicative only: one shared machine, one process per run. Read the SHAPE, not the digits.
# ./scripts/g1-cost.sh -> docs/output/04-g1-cost-1cpu.txt (4000 batches, ~6-8 s per run)
# BATCHES=400 OUTNAME=05-g1-cost-1cpu-short.txt ./scripts/g1-cost.sh (a short-lived process, e.g. a CI job)
set -euo pipefail
source "$(dirname "$0")/env.sh"
BUILD="$ROOT/build/g1"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java
RUNS="${RUNS:-3}"; BATCHES="${BATCHES:-4000}"; OUTNAME="${OUTNAME:-04-g1-cost-1cpu.txt}"
{
echo "# Allocation workload, 1 CPU / 2 GB container, $BATCHES batches x 50,000 objects, $RUNS runs per configuration (indicative, not a benchmark)"
echo "# 'default' means nobody passed a collector flag."
echo
for cfg in "$IMG26|" "$IMG27|" "$IMG27|-XX:+UseSerialGC"; do
img="${cfg%%|*}"; flag="${cfg##*|}"
for i in $(seq 1 "$RUNS"); do
echo "=== $img ${flag:-default} run $i"
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$img" \
java $flag -cp /app Workload "$BATCHES" | grep -v -E '^allocated'
done
echo
done
} > "$OUT/$OUTNAME" 2>&1
cat "$OUT/$OUTNAME"
+49
View File
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
# JEP 523 in a real container: what does the JVM pick when nobody asks for a collector?
# Runs GcReport in Docker with 1 CPU and 2 CPUs, on the JDK 26 and JDK 27 images.
# ./scripts/g1-default.sh -> docs/output/01-g1-default-1cpu-vs-2cpu.txt
# docs/output/02-g1-default-memory.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
BUILD="$ROOT/build/g1"; rm -rf "$BUILD"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java # one class file set runs on both JDKs
run() { # image label docker-args... -- java-args...
local img="$1" label="$2"; shift 2
echo "\$ docker run --rm $* $img java -cp /app GcReport"
docker run --rm "$@" -v "$BUILD:/app:ro" "$img" java -cp /app GcReport
echo
}
{
echo "# JEP 523: which collector does the JVM pick when you do not choose one?"
echo "# Same class files, same vendor (Amazon Corretto), same 2 GB memory limit. Only the JDK version and the CPU limit change."
echo
for cpus in 1 2; do
for img in "$IMG26" "$IMG27"; do
echo "=== $img --cpus=$cpus --memory=2g"
run "$img" x --cpus=$cpus --memory=2g
done
done
} > "$OUT/01-g1-default-1cpu-vs-2cpu.txt" 2>&1
{
echo "# The other half of the old rule: memory. The old heuristic wanted at least 2 CPUs AND about 1792 MB."
echo "# With 2 CPUs the CPU half of the rule is satisfied, so only the memory limit differs between these runs."
echo
for mem in 1g 2g; do
for img in "$IMG26" "$IMG27"; do
echo "=== $img --cpus=2 --memory=$mem"
run "$img" x --cpus=2 --memory=$mem
done
done
} > "$OUT/02-g1-default-memory.txt" 2>&1
{
echo "# The opt-out still works on 27: an explicit choice always wins over the JVM's."
echo
echo "=== $IMG27 --cpus=1 --memory=2g -XX:+UseSerialGC"
echo "\$ docker run --rm --cpus=1 --memory=2g $IMG27 java -XX:+UseSerialGC -cp /app GcReport"
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$IMG27" java -XX:+UseSerialGC -cp /app GcReport
} > "$OUT/03-g1-default-optout.txt" 2>&1
echo "wrote 01-, 02-, 03- transcripts to $OUT"
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# Why does G1 on 1 CPU touch ~490 MB in a 0.5 s process? Two suspects, tested:
# 1. the JDK 27 change of MinHeapFreeRatio/MaxHeapFreeRatio from 40/70 to 0/100 -> restored here; no effect
# 2. G1 simply being allowed to grow toward the 512 MB default maximum heap -> capped here; RSS follows the cap
# ./scripts/g1-tuning.sh -> docs/output/06-g1-tuning-1cpu.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
BUILD="$ROOT/build/g1"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -d "$BUILD" "$ROOT"/g1-container/src/*.java
{
echo "# JDK 27, G1 (the default), 1 CPU / 2 GB container, 400 batches, 2 runs per configuration"
echo
echo "=== flags that differ between 26 and 27 (from -XX:+PrintFlagsFinal)"
for img in "$IMG26" "$IMG27"; do
echo "--- $img"
docker run --rm --cpus=1 --memory=2g "$img" java -XX:+PrintFlagsFinal -version 2>/dev/null \
| grep -E ' (MinHeapFreeRatio|MaxHeapFreeRatio|MaxHeapSize|ConcGCThreads|ParallelGCThreads|UseCompactObjectHeaders|G1IHOP|UseCompressedClassPointers) ' || true
done
echo
for flags in "" "-XX:MinHeapFreeRatio=40 -XX:MaxHeapFreeRatio=70" "-Xmx256m"; do
echo "=== $IMG27 flags: [${flags:-none}]"
for i in 1 2; do
docker run --rm --cpus=1 --memory=2g -v "$BUILD:/app:ro" "$IMG27" java $flags -cp /app Workload 400 \
| grep -E 'wall|gc count|peak'
done
done
} > "$OUT/06-g1-tuning-1cpu.txt" 2>&1
cat "$OUT/06-g1-tuning-1cpu.txt"
+17
View File
@@ -0,0 +1,17 @@
#!/usr/bin/env bash
# Runs the five JDK 27 feature demos on a real JDK 27. Every demo asserts its own claims (see Check.java),
# so a green transcript means the sentence in the post is still true.
# ./scripts/jep-tour.sh -> docs/output/21-structured.txt 22-lazy.txt 23-primitive-patterns.txt 24-pem.txt 25-vector.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
S="$ROOT/jep-tour/src"; B="$ROOT/build/tour"; rm -rf "$B"; mkdir -p "$B"
"$JDK27/bin/javac" --enable-preview --release 27 -Xlint:-preview -d "$B" "$S/Check.java" "$S/StructuredDemo.java" "$S/LazyDemo.java" \
"$S/PrimitivePatterns.java" "$S/PemDemo.java" 2>&1 | grep -v '^Note:' || true
"$JDK27/bin/javac" --add-modules jdk.incubator.vector --release 27 -d "$B" "$S/Check.java" "$S/VectorDemo.java" 2>&1 | grep -v -E '^(Note:|WARNING)' || true
run() { local name="$1" cls="$2"; shift 2
{ echo "\$ java $* $cls (JDK 27)"; "$JDK27/bin/java" -Dstdout.encoding=UTF-8 "$@" -cp "$B" "$cls" 2>&1 | grep -v '^WARNING: Using incubator'; echo "exit=${PIPESTATUS[0]}"; } > "$OUT/$name" 2>&1; }
run 21-structured.txt StructuredDemo --enable-preview
run 22-lazy.txt LazyDemo --enable-preview
run 23-primitive-patterns.txt PrimitivePatterns --enable-preview
run 24-pem.txt PemDemo --enable-preview
run 25-vector.txt VectorDemo --add-modules jdk.incubator.vector
+34
View File
@@ -0,0 +1,34 @@
#!/usr/bin/env bash
# JEP 536: JFR in-process data redaction. Start a JVM that has secrets in its arguments, system
# properties and environment, record it with JFR, and print what ended up in the recording.
# ./scripts/jfr-redaction.sh -> docs/output/40-jfr-redaction.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
B="$ROOT/build/jfr"; rm -rf "$B"; mkdir -p "$B"
"$JDK27/bin/javac" --release 26 -d "$B" "$ROOT/jep-tour/src/Idle.java"
# record <label> <jdk> <FlightRecorderOptions value or empty>
record() {
local label="$1" jdk="$2" opts="${3:-}" jfr="$B/$1.jfr"
local fropt=(); [ -n "$opts" ] && fropt=("-XX:FlightRecorderOptions:$opts")
DB_PASSWORD=hunter2 "$jdk/bin/java" -Dapi.token=abc123 -Dregion=ap-south-1 "${fropt[@]}" \
-XX:StartFlightRecording:filename="$jfr" -cp "$B" Idle --password=hunter2 --user=ankur >/dev/null 2>&1
echo "=== $label"
[ -n "$opts" ] && echo " -XX:FlightRecorderOptions:$opts"
"$jdk/bin/jfr" print --events jdk.JVMInformation "$jfr" | grep -E 'jvmArguments|javaArguments' | sed 's/^ *//' | sed -E 's/(-XX:StartFlightRecording:filename=)[^ "]*/\1<file>/'
"$jdk/bin/jfr" print --events jdk.InitialSystemProperty "$jfr" | grep -A1 -E 'key = "(api.token|region)"' | grep -v -- '^--$' | sed 's/^ *//' | paste - -
"$jdk/bin/jfr" print --events jdk.InitialEnvironmentVariable "$jfr" | grep -A1 -E 'key = "DB_PASSWORD"' | grep -v -- '^--$' | sed 's/^ *//' | paste - -
echo
}
{
echo "# The process under test: java -Dapi.token=abc123 -Dregion=ap-south-1 ... Idle --password=hunter2 --user=ankur"
echo "# with DB_PASSWORD=hunter2 in the environment. Which of those values reach the recording?"
echo
record "jdk26-default" "$JDK26"
record "jdk27-default" "$JDK27"
record "jdk27-own-list-replaces-defaults" "$JDK27" "redact-argument=--user*,redact-key=region"
record "jdk27-plus-extends-defaults" "$JDK27" "redact-argument=+--user*,redact-key=+region"
record "jdk27-none-disables-redaction" "$JDK27" "redact-argument=none,redact-key=none"
} > "$OUT/40-jfr-redaction.txt" 2>&1
cat "$OUT/40-jfr-redaction.txt"
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
# The 21 -> 25 hub lane: every claim in ankurm.com/java-21-to-25-lts-features and /java-21-to-25-upgrade-guide-ai-prompts
# that can be checked on a JDK, checked on JDK 21, 25, 26 and 27.
# ./scripts/lanes.sh -> docs/output/80-*.txt ... 91-*.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
SRC="$ROOT/lanes/src"; B="$ROOT/build/lanes"; rm -rf "$B"; mkdir -p "$B"
jdk() { case "$1" in 21) echo "$JDK21";; 25) echo "$JDK25";; 26) echo "$JDK26";; 27) echo "$JDK27";; esac; }
scrub() { sed -i -E "s#$ROOT#<repo>#g; s#@[0-9a-f]{6,8}#@<hash>#g" "$@"; }
# javac with the JDK's own compiler; prints javac's words and the real exit code
jc() { local v="$1"; shift; local out rc; out="$("$(jdk "$v")/bin/javac" "$@" 2>&1)"; rc=$?
printf '%s\n' "$out" | grep -v -E '^(Note: |$)' | sed 's/^/ /'; echo " exit=$rc"; return $rc; }
# java with the JDK's own launcher
jr() { local v="$1"; shift; "$(jdk "$v")/bin/java" "$@" 2>&1 | sed 's/^/ /'; }
# JDK 23 and 24 are only used to pin down WHEN something changed; they run from the Corretto images
dj() { local v="$1"; shift; docker run --rm -v "$B:/b" "amazoncorretto:$v" "$@" 2>&1 | sed 's/^/ /'; }
djc() { local v="$1"; shift; local out rc; out="$(docker run --rm -v "$B:/b" -v "$SRC:/src:ro" "amazoncorretto:$v" javac "$@" 2>&1)"; rc=$?
printf '%s\n' "$out" | grep -v -E '^(Note: |$)' | sed 's/^/ /'; echo " exit=$rc"; return $rc; }
# ---------- 80: the four Java 21 features, compiled once with --release 21, run on 21, 25, 26, 27 ----------
{
echo "# One class file (javac 21 --release 21), four JVMs"
mkdir -p "$B/hub"; "$(jdk 21)/bin/javac" --release 21 -d "$B/hub" "$SRC/Check.java" "$SRC/HubExamples.java" 2>&1
for v in 21 25 26 27; do echo; echo "\$ java -cp hub HubExamples (JDK $v)"; jr $v -cp "$B/hub" HubExamples; done
} > "$OUT/80-hub-examples.txt"
# ---------- 81: scoped values, final in 25 ----------
{
echo "# ScopedValue (JEP 506). Preview API in 21, final in 25."
echo; echo "\$ javac ScopedValueDemo.java (JDK 21, no flag)"; jc 21 --release 21 -d "$B/sv21" "$SRC/Check.java" "$SRC/ScopedValueDemo.java"
for v in 25 26 27; do
echo; echo "\$ javac ScopedValueDemo.java (JDK $v, no flag)"; mkdir -p "$B/sv$v"
jc $v -d "$B/sv$v" "$SRC/Check.java" "$SRC/ScopedValueDemo.java" && { echo "\$ java ScopedValueDemo (JDK $v)"; jr $v -cp "$B/sv$v" ScopedValueDemo; }
done
} > "$OUT/81-scoped-values.txt"
# ---------- 82: structured concurrency, NOT final in 25 ----------
{
echo "# StructuredTaskScope (JEP 505 in 25). Fifth preview in 25, sixth in 26, seventh in 27."
for v in 25 26 27; do
echo; echo "\$ javac StructuredHub.java (JDK $v, no flag)"; jc $v -d "$B/st$v-nopreview" "$SRC/Check.java" "$SRC/StructuredHub.java"
echo "\$ javac --enable-preview --release $v StructuredHub.java (JDK $v)"; mkdir -p "$B/st$v"
jc $v --enable-preview --release $v -Xlint:-preview -d "$B/st$v" "$SRC/Check.java" "$SRC/StructuredHub.java" \
&& { echo "\$ java --enable-preview StructuredHub (JDK $v)"; jr $v --enable-preview -cp "$B/st$v" StructuredHub; }
done
for v in 26 27; do echo; echo "\$ javac --enable-preview --release $v StructuredHubNew.java (JDK $v)"; mkdir -p "$B/stn$v"
jc $v --enable-preview --release $v -Xlint:-preview -d "$B/stn$v" "$SRC/Check.java" "$SRC/StructuredHubNew.java" \
&& { echo "\$ java --enable-preview StructuredHubNew (JDK $v)"; jr $v --enable-preview -cp "$B/stn$v" StructuredHubNew; }
done
} > "$OUT/82-structured-not-final.txt"
# ---------- 89: the Joiner factory methods on 25, 26, 27 ----------
{
echo "# javap --module java.base -public 'java.util.concurrent.StructuredTaskScope\$Joiner', static factories only"
for v in 25 26 27; do echo; echo "== JDK $v"; "$(jdk $v)/bin/javap" --module java.base -public 'java.util.concurrent.StructuredTaskScope$Joiner' 2>&1 \
| grep -E '^ public static' | sed -E 's/java\.util\.concurrent\.//g; s/java\.util\.stream\.//g; s/java\.util\.function\.//g; s/java\.util\.//g; s/java\.lang\.//g'; done
} > "$OUT/89-joiner-api.txt"
# ---------- 83: finalization ----------
{
echo "# Is finalization removed by default in 25? (JEP 421 deprecated it for removal in 18)"
for v in 21 25 26 27; do mkdir -p "$B/fin$v"; "$(jdk $v)/bin/javac" -Xlint:-removal -d "$B/fin$v" "$SRC/FinalizerDefault.java" 2>&1 | grep -v -E '^(Note: |$)'
echo; echo "\$ java FinalizerDefault (JDK $v)"; jr $v -cp "$B/fin$v" FinalizerDefault
echo "\$ java --finalization=disabled FinalizerDefault (JDK $v)"; jr $v --finalization=disabled -cp "$B/fin$v" FinalizerDefault; done
} > "$OUT/83-finalization.txt"
# ---------- 84: security manager ----------
{
echo "# System.setSecurityManager(new SecurityManager())"
for v in 21 25; do mkdir -p "$B/sm$v"; "$(jdk $v)/bin/javac" -Xlint:-removal -d "$B/sm$v" "$SRC/SecurityManagerGone.java" 2>&1 | grep -v -E '^(Note: |$)'
echo; echo "\$ java SecurityManagerGone (JDK $v)"; jr $v -cp "$B/sm$v" SecurityManagerGone; done
} > "$OUT/84-security-manager.txt"
# ---------- 85: Thread.stop / suspend / resume ----------
{
echo "# Thread.stop(), suspend(), resume(): when did they actually go?"
for v in 21 25; do mkdir -p "$B/ts$v"; "$(jdk $v)/bin/javac" -Xlint:-removal -d "$B/ts$v" "$SRC/ThreadStopRuntime.java" 2>&1 | grep -v -E '^(Note: |$)'
echo; echo "\$ java ThreadStopRuntime (JDK $v)"; jr $v -cp "$B/ts$v" ThreadStopRuntime; done
for v in 21 25 26; do echo; echo "\$ javac ThreadSuspendGone.java (JDK $v)"; jc $v -d "$B/sus$v" "$SRC/ThreadSuspendGone.java"; done
for v in 23 24; do echo; echo "\$ javac ThreadSuspendGone.java (JDK $v, Corretto image)"; mkdir -p "$B/sus$v"; djc $v -d /b/sus$v /src/ThreadSuspendGone.java; done
} > "$OUT/85-thread-stop-suspend.txt"
# ---------- 86: Unsafe and JNI warnings ----------
{
echo "# sun.misc.Unsafe memory access (JEP 498) and JNI (JEP 472): what does the JVM say?"
for v in 21 25; do mkdir -p "$B/us$v"; "$(jdk $v)/bin/javac" -Xlint:-removal -d "$B/us$v" "$SRC/UnsafeWarning.java" >/dev/null 2>&1
echo; echo "\$ java UnsafeWarning (JDK $v)"; jr $v -cp "$B/us$v" UnsafeWarning; done
echo; echo "\$ java UnsafeWarning (JDK 24, Corretto image)"; dj 24 java -cp /b/us21 UnsafeWarning
for v in 21 25; do mkdir -p "$B/jni$v"; "$(jdk $v)/bin/javac" -d "$B/jni$v" "$SRC/JniWarning.java" 2>&1
echo; echo "\$ java JniWarning (JDK $v)"; jr $v -cp "$B/jni$v" JniWarning
echo "\$ java --enable-native-access=ALL-UNNAMED JniWarning (JDK $v)"; jr $v --enable-native-access=ALL-UNNAMED -cp "$B/jni$v" JniWarning; done
echo; echo "\$ java JniWarning (JDK 24, Corretto image)"; dj 24 java -cp /b/jni21 JniWarning
} > "$OUT/86-unsafe-and-jni.txt"
# ---------- 87: pinning ----------
{
echo "# 4 virtual threads on ONE carrier, each sleeping 200 ms inside synchronized (JEP 491, Java 24)"
for v in 21 25 26 27; do mkdir -p "$B/pin$v"; "$(jdk $v)/bin/javac" -d "$B/pin$v" "$SRC/PinningDemo.java" 2>&1
echo; echo "\$ java -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1 PinningDemo (JDK $v)"
jr $v -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1 -cp "$B/pin$v" PinningDemo; done
for v in 23 24; do echo; echo "\$ java -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1 PinningDemo (JDK $v, Corretto image)"
dj $v java -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1 -cp /b/pin21 PinningDemo; done
} > "$OUT/87-pinning.txt"
# ---------- 88: non-generational ZGC ----------
{
echo "# -XX:+UseZGC -XX:-ZGenerational -Xlog:gc+init -version : generational ZGC is the only ZGC from Java 24 (JEP 490)"
keep='Version:|Using|Generational|Ignoring|Unrecognized|obsolete|Error: Could'
for v in 21 25 26 27; do echo; echo "\$ java -XX:+UseZGC -XX:-ZGenerational -Xlog:gc+init -version (JDK $v)"
jr $v -XX:+UseZGC -XX:-ZGenerational -Xlog:gc+init -version | grep -E "$keep"; done
for v in 23 24; do echo; echo "\$ java -XX:+UseZGC -XX:-ZGenerational -Xlog:gc+init -version (JDK $v, Corretto image)"
dj $v java -XX:+UseZGC -XX:-ZGenerational -Xlog:gc+init -version | grep -E "$keep"; done
} > "$OUT/88-zgc-generational.txt"
# ---------- 90: unnamed variables and compact source files ----------
{
echo "# _ (unnamed variables and patterns) and compact source files with instance main()"
echo; echo "\$ javac UnnamedVariables.java (JDK 21, no flag)"; jc 21 -d "$B/un21" "$SRC/UnnamedVariables.java"
for v in 25; do echo; echo "\$ javac UnnamedVariables.java (JDK $v, no flag)"; mkdir -p "$B/un$v"
jc $v -d "$B/un$v" "$SRC/UnnamedVariables.java" && { echo "\$ java UnnamedVariables (JDK $v)"; jr $v -cp "$B/un$v" UnnamedVariables; }; done
echo; echo "\$ java CompactHello.java (JDK 21, no flag)"; jr 21 "$SRC/CompactHello.java"
echo; echo "\$ java CompactHello.java (JDK 25, no flag)"; jr 25 "$SRC/CompactHello.java"
} > "$OUT/90-unnamed-and-compact.txt"
# ---------- 91: JEP 500 on 27 (70-final-field-mutation.txt covers 25 and 26) ----------
{
echo "# Reflection writing to a final field on JDK 27: same behaviour as 26 (compare 70-final-field-mutation.txt)"
mkdir -p "$B/ffm27"; "$JDK27/bin/javac" -d "$B/ffm27" "$ROOT/recap26/src/FinalFieldMutation.java" 2>&1 | grep -v -E '^(Note: |$)'
echo; echo "=== JDK 27, defaults"; jr 27 -cp "$B/ffm27" FinalFieldMutation
echo; echo "=== JDK 27, --illegal-final-field-mutation=deny"; jr 27 --illegal-final-field-mutation=deny -cp "$B/ffm27" FinalFieldMutation
} > "$OUT/91-final-field-mutation-27.txt"
scrub "$OUT"/8[0-9]-*.txt "$OUT"/90-*.txt "$OUT"/91-*.txt
echo "wrote $OUT/80..91"
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
# JEP 534: object header size on JDK 26 (12 bytes) vs JDK 27 (8 bytes), measured with JOL 0.17.
# ./scripts/object-headers.sh -> docs/output/10-headers-jdk26.txt, 11-headers-jdk27.txt,
# 12-headers-jdk27-opt-out.txt, 13-jol-record-failure.txt,
# 14-jol-record-workaround.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
LIB="$ROOT/.lib"; mkdir -p "$LIB"
JOL="$LIB/jol-core-0.17.jar"
if [ ! -f "$JOL" ]; then
curl -sfL -o "$JOL" https://repo1.maven.org/maven2/org/openjdk/jol/jol-core/0.17/jol-core-0.17.jar
fi
echo "4c98e9e61b3f189241057cb21b4331d1093e5b85 $JOL" | sha1sum -c - >/dev/null # Maven Central's own .sha1
BUILD="$ROOT/build/headers"; rm -rf "$BUILD"; mkdir -p "$BUILD"
"$JDK27/bin/javac" --release 26 -cp "$JOL" -d "$BUILD" "$ROOT"/object-headers/src/*.java
# JOL wants to attach to itself (for Instrumentation) and calls sun.misc.Unsafe, which JDK 24+ warns about.
COMMON=(-Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading --sun-misc-unsafe-memory-access=allow -cp "$BUILD:$JOL")
# The Serviceability Agent line JOL 0.17 prints on JDK 27 is JOL failing to read a flag that no longer exists
# (UseCompressedClassPointers is obsolete in 27); it is kept in the transcript because it is real.
"$JDK26/bin/java" "${COMMON[@]}" HeaderDemo > "$OUT/10-headers-jdk26.txt" 2>&1
"$JDK27/bin/java" "${COMMON[@]}" HeaderDemo > "$OUT/11-headers-jdk27.txt" 2>&1
"$JDK27/bin/java" -XX:-UseCompactObjectHeaders "${COMMON[@]}" HeaderDemo > "$OUT/12-headers-jdk27-opt-out.txt" 2>&1
# JOL 0.17 cannot lay out a record without a workaround. Capture the failure and the fix.
"$JDK27/bin/java" "${COMMON[@]}" RecordProbe > "$OUT/13-jol-record-failure.txt" 2>&1 || true
"$JDK27/bin/java" -Djol.magicFieldOffset=true "${COMMON[@]}" RecordProbe > "$OUT/14-jol-record-workaround.txt" 2>&1
# One compact table of instance sizes, extracted from the three transcripts (not retyped).
{
echo "# Instance sizes in bytes, extracted from 10-, 11- and 12- transcripts"
for f in 10-headers-jdk26 11-headers-jdk27 12-headers-jdk27-opt-out; do
echo "--- $f"
awk '/^=== /{t=$0} /^Instance size/{print t " -> " $0} /^1,000,000 /{print}' "$OUT/$f.txt"
done
} > "$OUT/15-headers-summary.txt"
cat "$OUT/15-headers-summary.txt"
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env bash
# The JDK 27 changes that are not JEPs but will still break somebody's Monday: JVM options that no
# longer exist, a launch mechanism that is gone, a locale property that is ignored, new public APIs.
# ./scripts/other-changes.sh -> docs/output/60-removed-options.txt, 61-behaviour-changes.txt, 62-new-api.txt
set -uo pipefail
source "$(dirname "$0")/env.sh"
B="$ROOT/build/other"; rm -rf "$B"; mkdir -p "$B"
SRC="$ROOT/other-changes/src"
"$JDK27/bin/javac" --release 26 -d "$B" "$SRC/Compat.java" "$SRC/SpawnProbe.java"
"$JDK27/bin/javac" --enable-preview --release 27 -d "$B" "$SRC/Check.java" "$SRC/Api27.java" 2>&1 | grep -v '^Note' || true
# first two lines of the JVM's response to a launch option, and the exit code
try() { local jdk="$1"; shift; local out rc; out="$("$jdk/bin/java" "$@" -version 2>&1 | grep -v -E '^(openjdk version|OpenJDK Runtime|OpenJDK 64-Bit Server VM \()' | head -2)"; rc=${PIPESTATUS[0]}
[ -z "$out" ] && out="(accepted silently)"; printf '%s\n' "$out" | sed 's/^/ /'; }
{
echo "# JVM options that JDK 27 removed or renamed. Each runs 'java <option> -version' on 26 and on 27."
for opt in "-noverify" "-Xverify:none" "-noclassgc" "-XX:+UseCompressedClassPointers" \
"-XX:InitiatingHeapOccupancyPercent=40" "-XX:G1IHOP=40"; do
echo; echo "=== $opt"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v:"; try "${!jdk}" $opt; done
done
echo; echo "=== -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT (JVMCI and the Graal JIT hook were removed in 27)"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v:"; try "${!jdk}" -XX:+UnlockExperimentalVMOptions -XX:+UseGraalJIT; done
} > "$OUT/60-removed-options.txt" 2>&1
{
echo "# Behaviour changes that need no new API to observe"
echo; echo "=== a script that pins the VFORK process launch mechanism"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v: java -Djdk.lang.Process.launchMechanism=VFORK SpawnProbe"
"${!jdk}/bin/java" -Djdk.lang.Process.launchMechanism=VFORK -cp "$B" SpawnProbe 2>&1 | head -3 | sed 's/^/ /'; done
echo; echo "=== the legacy ISO 639 language codes property (iw, ji, in)"
for v in 26 27; do jdk="JDK$v"; echo " JDK $v: java -Djava.locale.useOldISOCodes=true Compat"
"${!jdk}/bin/java" -Djava.locale.useOldISOCodes=true -cp "$B" Compat 2>&1 | sed 's/^/ /'; done
} > "$OUT/61-behaviour-changes.txt" 2>&1
{
echo "# New public API in 27, each exercised once (found by diffing javap dumps, see 51-api-diff-26-to-27.txt)"
"$JDK27/bin/java" --enable-preview -Dstdout.encoding=UTF-8 -cp "$B" Api27 2>&1
} > "$OUT/62-new-api.txt" 2>&1
cat "$OUT"/60-removed-options.txt "$OUT"/61-behaviour-changes.txt "$OUT"/62-new-api.txt
+101
View File
@@ -0,0 +1,101 @@
#!/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() { # <jdk> <version> <gc> -> $B/aot/<version>/<gc>.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() { # <jdk> <version> <gc> [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#<repo>#g; s#@[0-9a-f]{6,8}#@<hash>#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"
+22
View File
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Regenerates every file in docs/output/. Needs: JDK 21, 25 (LTS), 26 and 27 on disk, Docker with the
# amazoncorretto:21, :23, :24, :26 and :27 images, and python3. Takes roughly 15-20 minutes, most of it the G1 cost runs.
#
# Non-deterministic outputs (timings, RSS, GC counts, the machine's preferred SIMD species) will differ on your
# machine; the shape should not. Everything else is byte-stable between runs.
set -uo pipefail
cd "$(dirname "$0")"
./g1-default.sh
./g1-cost.sh
BATCHES=400 OUTNAME=05-g1-cost-1cpu-short.txt ./g1-cost.sh
./g1-tuning.sh
./object-headers.sh
./jep-tour.sh
./broken-on-27.sh
./tls-pq.sh
./jfr-redaction.sh
./other-changes.sh
./recap26.sh
./api-diff.sh
./lanes.sh
./upgrade.sh
+54
View File
@@ -0,0 +1,54 @@
#!/usr/bin/env bash
# JEP 527: post-quantum hybrid key exchange in the JDK's default TLS 1.3 stack.
# ./scripts/tls-pq.sh -> docs/output/30-tls-groups-matrix.txt, 31-tls-clienthello-size.txt, 32-tls-opt-out.txt
set -euo pipefail
source "$(dirname "$0")/env.sh"
B="$ROOT/build/tls"; rm -rf "$B"; mkdir -p "$B"
"$JDK27/bin/javac" --release 26 -d "$B" "$ROOT"/jep-tour/src/TlsPeer.java "$ROOT"/jep-tour/src/ClientHelloSize.java
# A throwaway self-signed certificate for the loopback server; the same store is the client's trust store.
"$JDK27/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
STORE=(-Djavax.net.ssl.keyStore="$B/ks.p12" -Djavax.net.ssl.keyStorePassword=changeit
-Djavax.net.ssl.trustStore="$B/ks.p12" -Djavax.net.ssl.trustStorePassword=changeit)
DEBUG=(-Djavax.net.debug=ssl:handshake)
# handshake <server-jdk> <client-jdk> [extra client flag]
handshake() {
local sj="$1" cj="$2" cflag="${3:-}"; local pf="$B/port"; rm -f "$pf"
"$sj/bin/java" "${STORE[@]}" "${DEBUG[@]}" -cp "$B" TlsPeer server "$pf" > "$B/server.log" 2>&1 &
local spid=$!
for _ in $(seq 1 50); do [ -s "$pf" ] && break; sleep 0.1; done
"$cj/bin/java" $cflag "${STORE[@]}" "${DEBUG[@]}" -cp "$B" TlsPeer client "$(cat "$pf")" > "$B/client.log" 2>&1
wait "$spid"
echo "client offered : $(grep -m1 '"named groups"' "$B/client.log" | sed 's/^ *//')"
echo "server selected: $(awk '/"ServerHello"/{f=1} f&&/"named group"/{print $NF; exit}' "$B/client.log")"
grep -m1 'handshake complete' "$B/client.log"
}
{
echo "# Who talks to whom: the key-exchange group each pairing actually negotiates (TLS 1.3, loopback)"
for pair in "26 26" "27 27" "27 26" "26 27"; do
set -- $pair
cj="JDK$1"; sj="JDK$2"
echo; echo "=== client JDK $1 -> server JDK $2"
handshake "${!sj}" "${!cj}"
done
} > "$OUT/30-tls-groups-matrix.txt" 2>&1
{
echo "# Size of the first TLS record (the ClientHello) each JDK sends"
for v in 26 27; do
jdk="JDK$v"; echo; echo "=== JDK $v"
"${!jdk}/bin/java" -cp "$B" ClientHelloSize
done
echo; echo "=== JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 (post-quantum group removed)"
"$JDK27/bin/java" -Djdk.tls.namedGroups=x25519,secp256r1 -cp "$B" ClientHelloSize
} > "$OUT/31-tls-clienthello-size.txt" 2>&1
{
echo "# Opting out on JDK 27: a client that does not offer the hybrid group"
echo; echo "=== client JDK 27 with -Djdk.tls.namedGroups=x25519,secp256r1 -> server JDK 27"
handshake "$JDK27" "$JDK27" "-Djdk.tls.namedGroups=x25519,secp256r1"
} > "$OUT/32-tls-opt-out.txt" 2>&1
cat "$OUT/30-tls-groups-matrix.txt" "$OUT/31-tls-clienthello-size.txt" "$OUT/32-tls-opt-out.txt"
+110
View File
@@ -0,0 +1,110 @@
#!/usr/bin/env bash
# The build-file half of the 21 -> 25 upgrade guide (ankurm.com/java-21-to-25-upgrade-guide-ai-prompts), run for real.
# ./scripts/upgrade.sh -> docs/output/92-maven-release.txt, 93-gradle-toolchain.txt, 94-release-flag.txt,
# 95-lombok-matrix.txt, 96-jdeps-internals.txt, 97-mockito-bytebuddy.txt
# Needs Maven, Gradle 8.x and 9.x (GRADLE8 / GRADLE9), JDKs 21, 25, 26, 27 and network access to Maven Central.
# In a sandbox that reaches Maven Central through a proxy, export JAVA_TOOL_OPTIONS (the proxy trust store)
# before running: it is handed to Maven and Gradle below and unset for everything else by env.sh.
set -uo pipefail
NET_JTO="${JAVA_TOOL_OPTIONS:-}"
source "$(dirname "$0")/env.sh"
GRADLE8="${GRADLE8:-/opt/gradle-8.14.3}"; GRADLE9="${GRADLE9:-/opt/gradle-9.1.0}"
SRC="$ROOT/lanes/src"; UP="$ROOT/lanes/upgrade"; B="$ROOT/build/upgrade"; rm -rf "$B"; mkdir -p "$B"
jdk() { case "$1" in 21) echo "$JDK21";; 25) echo "$JDK25";; 26) echo "$JDK26";; 27) echo "$JDK27";; esac; }
scrub() { sed -i -E "s#$ROOT#<repo>#g; s#/opt/jdks/[^ ]*#<jdk>#g; s#/tmp/[^ ]*#<tmp>#g" "$@"; }
# ---------- 92: Maven, the three properties from Step 1 ----------
mvnrun() { local v="$1"; rm -rf "$B/mvn$v" && cp -r "$UP" "$B/mvn$v" && cd "$B/mvn$v" \
&& JAVA_HOME="$(jdk "$v")" MAVEN_OPTS="$NET_JTO" mvn -B -ntp package 2>&1 | grep -E '^\[(INFO|ERROR)\] (--- |BUILD|Compiling|Failed to execute|.*release version|Using|Building jar)|error: release' | sed -E 's/^/ /'; cd "$ROOT"; }
{
echo "# pom.xml sets maven.compiler.source/target/release to 25 (lanes/upgrade/pom.xml)"
echo; echo "\$ mvn package (build JDK 21)"; mvnrun 21
echo; echo "\$ mvn package (build JDK 25)"; mvnrun 25
echo; echo "\$ mvn package (build JDK 27, still release 25)"; mvnrun 27
echo; echo "\$ javap -v -cp target/classes demo.App | grep 'major version'"; "$(jdk 25)/bin/javap" -v -cp "$B/mvn25/target/classes" demo.App | grep 'major version' | sed 's/^ */ /'
echo; echo "\$ java -cp target/classes demo.App (JDK 25)"; "$JDK25/bin/java" -cp "$B/mvn25/target/classes" demo.App | sed 's/^/ /'
echo; echo "\$ java -cp target/classes demo.App (JDK 21: the class file is newer than the JVM)"; "$JDK21/bin/java" -cp "$B/mvn25/target/classes" demo.App 2>&1 | head -2 | sed 's/^/ /'
} > "$OUT/92-maven-release.txt" 2>&1
scrub "$OUT/92-maven-release.txt"
# ---------- 93: Gradle, the toolchain block from Step 1 ----------
# gradlerun <gradle home> <label> <JDK that runs Gradle itself>
gradlerun() { local g="$1" name="$2" runjdk="$3"; rm -rf "$B/gr$name" && cp -r "$UP" "$B/gr$name" && cd "$B/gr$name" \
&& JAVA_HOME="$runjdk" GRADLE_OPTS="$NET_JTO" "$g/bin/gradle" --no-daemon --console=plain -q \
-Porg.gradle.java.installations.paths="$JDK25" -Porg.gradle.java.installations.auto-download=false \
-Dorg.gradle.jvmargs="$NET_JTO" run 2>&1 | grep -v -E '^(Picked up|Starting a Gradle Daemon|$)' | head -3 | sed 's/^/ /'; cd "$ROOT"; }
{
echo "# build.gradle.kts asks for a Java 25 toolchain (lanes/upgrade/build.gradle.kts). Two questions: which JDK compiles, and which JDK runs Gradle itself?"
echo; echo "\$ gradle run (Gradle 8.14.3 running on JDK 21, toolchain finds JDK 25)"; gradlerun "$GRADLE8" 8-21 "$JDK21"
echo; echo "\$ gradle run (Gradle 8.14.3 running on JDK 25)"; gradlerun "$GRADLE8" 8-25 "$JDK25"
echo; echo "\$ gradle run (Gradle 9.1.0 running on JDK 25)"; gradlerun "$GRADLE9" 9-25 "$JDK25"
} > "$OUT/93-gradle-toolchain.txt" 2>&1
scrub "$OUT/93-gradle-toolchain.txt"
# ---------- 94: --release versus -source/-target ----------
mkdir -p "$B/rf-a" "$B/rf-b"
{
echo "# ReleaseFlag.java calls String.equalsFoldCase, which is new in Java 26. The compiler is JDK 27's javac both times."
echo; echo "\$ javac --release 25 ReleaseFlag.java"
out="$("$JDK27/bin/javac" --release 25 -d "$B/rf-a" "$SRC/ReleaseFlag.java" 2>&1)"; rc=$?; printf '%s\n' "$out" | sed 's/^/ /'; echo " exit=$rc"
echo; echo "\$ javac -source 25 -target 25 ReleaseFlag.java"
out="$("$JDK27/bin/javac" -source 25 -target 25 -d "$B/rf-b" "$SRC/ReleaseFlag.java" 2>&1)"; rc=$?; printf '%s\n' "$out" | sed 's/^/ /'; echo " exit=$rc"
echo; echo "\$ java -cp rf-b ReleaseFlag (JDK 25)"; "$JDK25/bin/java" -cp "$B/rf-b" ReleaseFlag 2>&1 | head -3 | sed 's/^/ /'
echo; echo "\$ java -cp rf-b ReleaseFlag (JDK 27)"; "$JDK27/bin/java" -cp "$B/rf-b" ReleaseFlag 2>&1 | sed 's/^/ /'
} > "$OUT/94-release-flag.txt" 2>&1
scrub "$OUT/94-release-flag.txt"
# ---------- 95: Lombok versions against JDK 25, 26, 27 ----------
LB="$B/lombok"; mkdir -p "$LB"
for v in 1.18.34 1.18.40 1.18.46 1.18.48; do
curl -sS -m 90 -o "$LB/lombok-$v.jar" "https://repo1.maven.org/maven2/org/projectlombok/lombok/$v/lombok-$v.jar"; done
{
echo "# javac LombokProbe.java with -processorpath lombok-<version>.jar, then java LombokProbe. Class: @Getter on one field."
for v in 1.18.34 1.18.40 1.18.46 1.18.48; do for j in 25 26 27; do
rm -rf "$LB/o"; mkdir -p "$LB/o"
out="$("$(jdk $j)/bin/javac" -cp "$LB/lombok-$v.jar" -processorpath "$LB/lombok-$v.jar" -d "$LB/o" "$SRC/LombokProbe.java" 2>&1)"; rc=$?
if [ $rc -eq 0 ]; then res="compiled; $("$(jdk $j)/bin/java" -cp "$LB/o:$LB/lombok-$v.jar" LombokProbe 2>&1 | head -1)"
else res="javac failed: $(printf '%s\n' "$out" | grep -v -E '^(Note|WARNING)' | grep -m1 -E 'error|Exception' | cut -c1-150)"; fi
printf 'Lombok %-8s on JDK %s: %s\n' "$v" "$j" "$res"
done; done
echo; echo "# The same source with the jar on the class path only (no -processorpath), the way a hand-run javac used to find it:"
for j in 21 25; do rm -rf "$LB/o"; mkdir -p "$LB/o"
out="$("$(jdk $j)/bin/javac" -cp "$LB/lombok-1.18.48.jar" -d "$LB/o" "$SRC/LombokProbe.java" 2>&1)"; rc=$?
printf 'JDK %s, Lombok 1.18.48 on the class path only: exit=%s %s\n' "$j" "$rc" "$(printf '%s\n' "$out" | grep -m1 -E 'error|Note: Annotation' | cut -c1-150)"; done
for j in 23 24; do out="$(docker run --rm -v "$LB:/lb" -v "$SRC:/src:ro" "amazoncorretto:$j" javac -cp /lb/lombok-1.18.48.jar -d /tmp/o /src/LombokProbe.java 2>&1)"; rc=$?
printf 'JDK %s, Lombok 1.18.48 on the class path only: exit=%s %s\n' "$j" "$rc" "$(printf '%s\n' "$out" | grep -m1 -E 'error|Note: Annotation' | sed 's#/src/#<repo>/lanes/src/#' | cut -c1-150)"; done
} > "$OUT/95-lombok-matrix.txt" 2>&1
scrub "$OUT/95-lombok-matrix.txt"
# ---------- 96: jdeps --jdk-internals ----------
UW="$B/uw"; mkdir -p "$UW"
"$JDK25/bin/javac" -d "$UW" "$SRC/UnsafeWarning.java" >/dev/null 2>&1
{
echo "# jdeps --jdk-internals on the class from lanes/src/UnsafeWarning.java (JDK 25's jdeps)"
echo; echo "\$ jdeps --jdk-internals --multi-release 25 UnsafeWarning.class"
"$JDK25/bin/jdeps" --jdk-internals --multi-release 25 "$UW/UnsafeWarning.class" 2>&1 | sed 's/^/ /'
} > "$OUT/96-jdeps-internals.txt" 2>&1
scrub "$OUT/96-jdeps-internals.txt"
# ---------- 97: Mockito (and the ByteBuddy it brings) on 21, 25, 27 ----------
MK="$B/mockito"; mkdir -p "$MK"
for v in 5.12.0 5.17.0 5.18.0 5.23.0; do mkdir -p "$MK/$v"
cat > "$MK/$v/pom.xml" <<POM
<project xmlns="http://maven.apache.org/POM/4.0.0"><modelVersion>4.0.0</modelVersion><groupId>x</groupId><artifactId>x</artifactId><version>1</version>
<dependencies><dependency><groupId>org.mockito</groupId><artifactId>mockito-core</artifactId><version>$v</version></dependency></dependencies></project>
POM
(cd "$MK/$v" && JAVA_HOME="$JDK25" MAVEN_OPTS="$NET_JTO" mvn -q -B -ntp dependency:copy-dependencies -DoutputDirectory=lib >/dev/null 2>&1); done
{
echo "# lanes/src/MockitoProbe.java: mock an interface, stub one call. Four Mockito versions, each with the ByteBuddy it declares."
for v in 5.12.0 5.17.0 5.18.0 5.23.0; do bb="$(ls "$MK/$v/lib" | grep -E '^byte-buddy-[0-9]' | sed 's/byte-buddy-//; s/.jar//')"
for j in 21 25 27; do rm -rf "$MK/o"; mkdir -p "$MK/o"
"$(jdk $j)/bin/javac" -cp "$MK/$v/lib/*" -d "$MK/o" "$SRC/MockitoProbe.java" >/dev/null 2>&1
out="$("$(jdk $j)/bin/java" -cp "$MK/o:$MK/$v/lib/*" MockitoProbe 2>&1)"
if grep -q '^mock says hi' <<<"$out"; then res="works: mock says hi"; else res="FAILS: $(grep -m1 -E 'Mockito cannot mock' <<<"$out")"; fi
printf 'Mockito %-6s (ByteBuddy %-7s) on JDK %s: %s\n' "$v" "$bb" "$j" "$res"
done; done
echo; echo "# Notices on stderr from the working Mockito 5.23.0 on JDK 25"
rm -rf "$MK/o"; mkdir -p "$MK/o"; "$JDK25/bin/javac" -cp "$MK/5.23.0/lib/*" -d "$MK/o" "$SRC/MockitoProbe.java" >/dev/null 2>&1
"$JDK25/bin/java" -cp "$MK/o:$MK/5.23.0/lib/*" MockitoProbe 2>&1 | grep -E '^(Mockito is currently self-attaching|WARNING: A Java agent has been loaded dynamically)' | sed 's#(file:[^)]*)#(file:<path>)#' | sed 's/^/ /'
} > "$OUT/97-mockito-bytebuddy.txt" 2>&1
scrub "$OUT/97-mockito-bytebuddy.txt"
echo "done"; wc -l "$OUT"/9[2-6]-*.txt