Add shenandoah-jdk28: generational vs SATB Shenandoah, G1 and ZGC on a Spring Boot service (JEP 535 status probe included)

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01HmvoHH3ZnCEaPNXRpDxDZe
This commit is contained in:
Claude
2026-10-24 10:53:17 +00:00
parent df13a904d7
commit b25bd65d4b
14 changed files with 460 additions and 0 deletions
@@ -0,0 +1,71 @@
package com.ankurm.shenandoah;
import java.lang.management.ManagementFactory;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicReferenceArray;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* A server with the shape a generational collector is built for: a large, slowly changing live set
* (the cache) plus a steady stream of short-lived garbage (each request).
*/
@SpringBootApplication
public class ShenandoahApplication {
public static void main(String[] args) {
SpringApplication.run(ShenandoahApplication.class, args);
}
@RestController
static class Api {
/** 200,000 slots of 1 KiB is a live set of roughly 210 MiB. */
static final int SLOTS = 200_000;
/** Slots replaced per request: the old value becomes old-generation garbage. */
static final int REPLACED_PER_REQUEST = 8;
/** Short-lived 1 KiB arrays allocated per request: about 100 KiB of young garbage. */
static final int TEMPORARIES = 100;
private final AtomicReferenceArray<byte[]> cache = new AtomicReferenceArray<>(SLOTS);
/** Written on every request so the JIT cannot discard the temporaries. */
private volatile int sink;
Api() {
for (int i = 0; i < SLOTS; i++) {
cache.set(i, new byte[1024]);
}
}
@GetMapping("/work")
String work() {
ThreadLocalRandom r = ThreadLocalRandom.current();
for (int i = 0; i < REPLACED_PER_REQUEST; i++) {
cache.set(r.nextInt(SLOTS), new byte[1024]);
}
byte[][] temp = new byte[TEMPORARIES][];
for (int i = 0; i < TEMPORARIES; i++) {
temp[i] = new byte[1024];
temp[i][0] = (byte) i;
}
sink = temp[r.nextInt(TEMPORARIES)][0] + cache.get(r.nextInt(SLOTS))[0];
return "ok";
}
@GetMapping("/info")
Map<String, Object> info() {
Runtime rt = Runtime.getRuntime();
Map<String, Object> m = new LinkedHashMap<>();
m.put("javaVersion", System.getProperty("java.version"));
m.put("committedMiB", rt.totalMemory() >> 20);
m.put("maxMiB", rt.maxMemory() >> 20);
m.put("gc", ManagementFactory.getGarbageCollectorMXBeans().stream().map(b -> b.getName()).toList());
m.put("jvmUptimeMs", ManagementFactory.getRuntimeMXBean().getUptime());
return m;
}
}
}