gatherers/: windowFixed, windowSliding, fold, scan, mapConcurrent, a custom Gatherer (dedupeConsecutive, takeUntil), and a Collector-vs-Gatherer comparison. Verified: windowSliding emits one short window on a too-short stream rather than shrinking to empty like windowFixed; mapConcurrent waits for in-flight siblings to finish (~901ms) rather than cancelling them when one mapper throws. scoped-values/: ScopedValue API and rebinding, why plain threads (virtual or platform) do not inherit a binding while a StructuredTaskScope subtask does, and the memory cost vs InheritableThreadLocal measured two ways -- a noisy heap-delta first pass, then a precise jcmd GC.class_histogram object census (5 shared Carrier objects vs ~700,000 copied ThreadLocalMap/Entry objects for 100,000 threads). Companion code for the ankurm.com posts "Java Stream Gatherers (JEP 485)" and "Scoped Values vs ThreadLocal in Java 25: Migration Guide with Virtual Threads". Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01FtpJvZfg4nvLvtzgJTDWpB
53 lines
2.6 KiB
Bash
Executable File
53 lines
2.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Scoped Values (JEP 506, final since JDK 25) vs ThreadLocal. StructuredTaskScope (JEP 505) is
|
|
# still PREVIEW on JDK 25 -- every file here that touches it needs --enable-preview --release 25.
|
|
# ./scripts/scoped-values.sh -> docs/output/99-basics.txt 99-inheritance.txt
|
|
# 99-memory-heap-delta.txt 99-histogram-scopedvalue.txt
|
|
# 99-histogram-threadlocal.txt
|
|
set -uo pipefail
|
|
source "$(dirname "$0")/env.sh"
|
|
S="$ROOT/scoped-values/src"; B="$ROOT/build/scoped-values"; rm -rf "$B"; mkdir -p "$B"
|
|
|
|
# ScopedValuesBasics.java needs no preview flag (JEP 506 is final); the other three touch
|
|
# StructuredTaskScope and need --enable-preview.
|
|
"$JDK25/bin/javac" -d "$B" "$S/Check.java" "$S/ScopedValuesBasics.java" 2>&1 | grep -v -E '^(Note:|$)' || true
|
|
"$JDK25/bin/javac" --enable-preview --release 25 -Xlint:-preview -d "$B" "$S"/*.java 2>&1 | grep -v -E '^(Note:|$)' || true
|
|
|
|
{ echo "\$ java ScopedValuesBasics (JDK 25, no preview flag -- JEP 506 is final)"
|
|
"$JDK25/bin/java" -cp "$B" ScopedValuesBasics 2>&1; echo "exit=${PIPESTATUS[0]}"
|
|
} > "$OUT/99-basics.txt" 2>&1
|
|
|
|
{ echo "\$ java --enable-preview InheritanceRequiresStructuredTaskScope (JDK 25)"
|
|
"$JDK25/bin/java" --enable-preview -cp "$B" InheritanceRequiresStructuredTaskScope 2>&1; echo "exit=${PIPESTATUS[0]}"
|
|
} > "$OUT/99-inheritance.txt" 2>&1
|
|
|
|
{ echo "\$ java --enable-preview -Xmx6g ScopedValueVsThreadLocalMemory 500000 (JDK 25)"
|
|
"$JDK25/bin/java" --enable-preview -Xmx6g -cp "$B" ScopedValueVsThreadLocalMemory 500000 2>&1; echo "exit=${PIPESTATUS[0]}"
|
|
} > "$OUT/99-memory-heap-delta.txt" 2>&1
|
|
|
|
# Precise version: park N=100000 subtasks holding 5 bound values each, then ask the JVM itself
|
|
# (jcmd GC.class_histogram) how many objects of each class actually exist while they're all alive.
|
|
histogram() {
|
|
local mode="$1" outfile="$2"
|
|
rm -f "$B/probe.log"
|
|
"$JDK25/bin/java" --enable-preview -Xmx3g -cp "$B" MemoryFootprintProbe "$mode" 100000 > "$B/probe.log" 2>&1 &
|
|
local pid=$!
|
|
for _ in $(seq 1 60); do grep -q "READY $mode" "$B/probe.log" 2>/dev/null && break; sleep 1; done
|
|
{
|
|
echo "\$ jcmd <pid> GC.class_histogram (100000 subtasks parked, mode=$mode, JDK 25)"
|
|
"$JDK25/bin/jcmd" "$pid" GC.class_histogram 2>&1 \
|
|
| grep -E 'ThreadLocal|VirtualThread |SubtaskImpl|Carrier|ScopedValue|StackChunk|^Total' \
|
|
| grep -v CarrierThread
|
|
} > "$OUT/$outfile" 2>&1
|
|
kill -9 "$pid" 2>/dev/null
|
|
wait "$pid" 2>/dev/null
|
|
}
|
|
|
|
histogram sv 99-histogram-scopedvalue.txt
|
|
histogram tl 99-histogram-threadlocal.txt
|
|
|
|
scrub() { sed -i -E "s#$ROOT#<repo>#g" "$@"; }
|
|
scrub "$OUT"/99-*.txt
|
|
|
|
echo "wrote $OUT/99-*.txt"
|