import java.util.concurrent.CountDownLatch; import java.util.concurrent.StructuredTaskScope; /** * Parks N subtasks holding 5 bound values (mode "sv" = ScopedValue, mode "tl" = * InheritableThreadLocal) and prints READY, then sleeps for 60s so an external `jcmd * GC.class_histogram` can be run against it while every subtask is alive and holding its * bindings. scripts/scoped-values.sh drives this and diffs the two histograms -- see * docs/output/99-histogram-scopedvalue.txt and 99-histogram-threadlocal.txt. * * ScopedValueVsThreadLocalMemory.java measures the SAME comparison a coarser way (total heap * delta before/after) and is honest about that measurement being noisy relative to the fixed cost * of a VirtualThread + its StackChunk. This file exists because the coarse measurement alone * would undersell a real, structural difference that a precise object count makes obvious. * Chapter: docs/17-scoped-values-migration.md */ public class MemoryFootprintProbe { static final ScopedValue SV1 = ScopedValue.newInstance(); static final ScopedValue SV2 = ScopedValue.newInstance(); static final ScopedValue SV3 = ScopedValue.newInstance(); static final ScopedValue SV4 = ScopedValue.newInstance(); static final ScopedValue SV5 = ScopedValue.newInstance(); static final ThreadLocal TL1 = new InheritableThreadLocal<>(); static final ThreadLocal TL2 = new InheritableThreadLocal<>(); static final ThreadLocal TL3 = new InheritableThreadLocal<>(); static final ThreadLocal TL4 = new InheritableThreadLocal<>(); static final ThreadLocal TL5 = new InheritableThreadLocal<>(); public static void main(String[] args) throws Exception { String mode = args[0]; int n = Integer.parseInt(args[1]); CountDownLatch ready = new CountDownLatch(n); CountDownLatch release = new CountDownLatch(1); System.out.println("PID=" + ProcessHandle.current().pid() + " mode=" + mode + " n=" + n); if (mode.equals("sv")) { ScopedValue.where(SV1, "a").where(SV2, "b").where(SV3, "c").where(SV4, "d").where(SV5, "e").run(() -> { try (var scope = StructuredTaskScope.open()) { for (int i = 0; i < n; i++) { scope.fork(() -> { SV1.get(); ready.countDown(); release.await(); return null; }); } ready.await(); System.out.println("READY sv"); Thread.sleep(60_000); release.countDown(); scope.join(); } catch (Exception e) { throw new RuntimeException(e); } }); } else { TL1.set("a"); TL2.set("b"); TL3.set("c"); TL4.set("d"); TL5.set("e"); try (var scope = StructuredTaskScope.open()) { for (int i = 0; i < n; i++) { scope.fork(() -> { TL1.get(); ready.countDown(); release.await(); return null; }); } ready.await(); System.out.println("READY tl"); Thread.sleep(60_000); release.countDown(); scope.join(); } } } }