JVM-Performance-Deep-Dive-Lab
Ein zusammenhängender Diagnosepfad von korrelierten Messwerten über Evidenz und Hypothese bis zur Kontrollmessung. Die Codekarten werden direkt aus den kompilierbaren Java-Dateien erzeugt.
26 Java-Dateien · Java 21 · ohne externe Laufzeitabhängigkeiten
Erzeugt kontrollierte kurzlebige Allokationen für Übungen.
package com.example.enterprise.run8a;
import java.util.ArrayList;
import java.util.List;
public final class AllocationPressureSimulator {
public List<byte[]> allocateBursts(int objects, int bytesPerObject) {
List<byte[]> data = new ArrayList<>();
for (int index = 0; index < objects; index++) {
byte[] value = new byte[bytesPerObject];
value[0] = (byte) index;
data.add(value);
}
return data;
}
public long checksum(List<byte[]> data) {
long checksum = 0;
for (byte[] value : data) {
checksum += value[0];
}
return checksum;
}
}
Berechnet Median und p95, ohne sich als JMH-Ersatz auszugeben.
package com.example.enterprise.run8a;
import java.util.Arrays;
public record BenchmarkReport(
String operation,
int warmupIterations,
long[] measuredNanos) {
public BenchmarkReport {
measuredNanos = measuredNanos.clone();
if (measuredNanos.length == 0) {
throw new IllegalArgumentException("At least one measured iteration is required");
}
}
@Override
public long[] measuredNanos() {
return measuredNanos.clone();
}
public long medianNanos() {
return percentile(0.50);
}
public long p95Nanos() {
return percentile(0.95);
}
public long percentile(double quantile) {
if (quantile < 0.0 || quantile > 1.0) {
throw new IllegalArgumentException("quantile must be between 0 and 1");
}
long[] sorted = measuredNanos.clone();
Arrays.sort(sorted);
int index = (int) Math.ceil(quantile * sorted.length) - 1;
return sorted[Math.max(0, index)];
}
}
Erfasst aktuelle, insgesamt geladene und entladene Klassen.
package com.example.enterprise.run8a;
import java.lang.management.ManagementFactory;
public final class ClassLoadingProbe {
public ClassLoadingSnapshot capture() {
var bean = ManagementFactory.getClassLoadingMXBean();
return new ClassLoadingSnapshot(
bean.getLoadedClassCount(),
bean.getTotalLoadedClassCount(),
bean.getUnloadedClassCount(),
ClassLoader.getSystemClassLoader());
}
}
Hält Classloading-Daten und den zugehörigen Application ClassLoader.
package com.example.enterprise.run8a;
public record ClassLoadingSnapshot(
int loadedNow,
long totalLoaded,
long unloaded,
ClassLoader appLoader) {}
Trennt Heap-Auslastung von RSS- und Native-Memory-Druck.
package com.example.enterprise.run8a;
public record ContainerMemorySnapshot(
long limitBytes,
long processResidentBytes,
long directBufferBytes,
long estimatedThreadStackBytes) {
public long nativeAndUntrackedBytes(MemorySnapshot memory) {
long explained = memory.heapUsed() + memory.nonHeapUsed() + directBufferBytes
+ estimatedThreadStackBytes;
return Math.max(0, processResidentBytes - explained);
}
public double processUtilization() {
return limitBytes > 0 ? (double) processResidentBytes / limitBytes : 0.0;
}
public boolean closeToLimit() {
return limitBytes > 0 && processUtilization() >= 0.90;
}
}
Liefert reproduzierbare Vorher-/Nachher-Snapshots für das Allocation-Szenario.
package com.example.enterprise.run8a;
import java.util.List;
import java.util.Map;
public final class DiagnosticScenarioFactory {
private static final long MIB = 1024L * 1024L;
public JvmDiagnosticSnapshot allocationIncident() {
return snapshot(
new MemorySnapshot(900 * MIB, 960 * MIB, 1024 * MIB, 140 * MIB, 1),
Map.of(Thread.State.RUNNABLE, 8, Thread.State.WAITING, 2),
0.68,
240.0,
340.0,
new ContainerMemorySnapshot(2_048 * MIB, 1_300 * MIB, 80 * MIB, 40 * MIB));
}
public JvmDiagnosticSnapshot afterCacheBounded() {
return snapshot(
new MemorySnapshot(520 * MIB, 768 * MIB, 1024 * MIB, 135 * MIB, 2),
Map.of(Thread.State.RUNNABLE, 8, Thread.State.WAITING, 2),
0.42,
55.0,
170.0,
new ContainerMemorySnapshot(2_048 * MIB, 850 * MIB, 55 * MIB, 40 * MIB));
}
private JvmDiagnosticSnapshot snapshot(
MemorySnapshot memory,
Map<Thread.State, Integer> threadStates,
double cpu,
double allocationRate,
double p95,
ContainerMemorySnapshot container) {
return new JvmDiagnosticSnapshot(
memory,
new ThreadStateSnapshot(threadStates),
List.of(new GarbageCollectorSnapshot("G1 Young", 14, 380)),
new ClassLoadingSnapshot(12_000, 13_400, 1_400,
ClassLoader.getSystemClassLoader()),
container,
cpu,
allocationRate,
p95);
}
}
Erfasst Collector-Zähler und kumulierte Laufzeiten.
package com.example.enterprise.run8a;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.List;
public final class GarbageCollectorProbe {
public List<GarbageCollectorSnapshot> capture() {
List<GarbageCollectorSnapshot> snapshots = new ArrayList<>();
for (GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
snapshots.add(new GarbageCollectorSnapshot(
bean.getName(),
bean.getCollectionCount(),
bean.getCollectionTime()));
}
return List.copyOf(snapshots);
}
}
Hält die stabilen GC-Messwerte eines Collectors.
package com.example.enterprise.run8a;
public record GarbageCollectorSnapshot(
String name,
long collectionCount,
long collectionTimeMillis) {}
Berechnet zurückgewonnenen Speicher und erkennt geringe Rückgewinnung.
package com.example.enterprise.run8a;
public record GcLogLine(
String collector,
long beforeMb,
long afterMb,
long committedMb,
long pauseMillis) {
public long reclaimedMb() {
return beforeMb - afterMb;
}
public boolean suspiciousNoReclaim() {
return beforeMb > 0 && reclaimedMb() < beforeMb * 0.05;
}
}
Liest Lehrformat und einen realistischen Ausschnitt des Unified GC Loggings.
package com.example.enterprise.run8a;
import java.util.Optional;
import java.util.regex.Pattern;
public final class GcLogParser {
private static final Pattern TEACHING_FORMAT = Pattern.compile(
"GC\\(([^)]+)\\) before=(\\d+)M after=(\\d+)M committed=(\\d+)M pause=([0-9.]+)ms");
private static final Pattern UNIFIED_LOG = Pattern.compile(
"GC\\(\\d+\\)\\s+(.+?)\\s+(\\d+)M->(\\d+)M\\((\\d+)M\\)\\s+([0-9.]+)ms");
public Optional<GcLogLine> parse(String line) {
var teaching = TEACHING_FORMAT.matcher(line);
if (teaching.find()) {
return Optional.of(toLine(teaching.group(1), teaching.group(2), teaching.group(3),
teaching.group(4), teaching.group(5)));
}
var unified = UNIFIED_LOG.matcher(line);
if (unified.find()) {
return Optional.of(toLine(unified.group(1).trim(), unified.group(2), unified.group(3),
unified.group(4), unified.group(5)));
}
return Optional.empty();
}
private GcLogLine toLine(
String collectorOrCause,
String before,
String after,
String committed,
String pauseMillis) {
return new GcLogLine(
collectorOrCause,
Long.parseLong(before),
Long.parseLong(after),
Long.parseLong(committed),
Math.round(Double.parseDouble(pauseMillis)));
}
}
Sammelt Messungen und erzeugt einen deskriptiven Benchmark-Bericht.
package com.example.enterprise.run8a;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public final class HotPathAnalyzer {
private final List<ProfilingSample> samples = new ArrayList<>();
public void record(String name, long nanos) {
samples.add(new ProfilingSample(name, nanos));
}
public List<ProfilingSample> samples() {
return List.copyOf(samples);
}
public Optional<ProfilingSample> slowest() {
return samples.stream()
.max(Comparator.comparingLong(ProfilingSample::durationNanos));
}
public BenchmarkReport report(String operation, int warmupIterations) {
long[] values = samples.stream()
.filter(sample -> sample.name().equals(operation))
.mapToLong(ProfilingSample::durationNanos)
.toArray();
return new BenchmarkReport(operation, warmupIterations, values);
}
}
Transportiert Diagnose, beobachtete Evidenz und empfohlene Vertiefung.
package com.example.enterprise.run8a;
import java.util.List;
public record IncidentAssessment(
IncidentQuestion primaryDiagnosis,
List<String> evidence,
List<String> nextActions) {
public IncidentAssessment {
evidence = List.copyOf(evidence);
nextActions = List.copyOf(nextActions);
}
}
Benennt die sechs Diagnosepfade plus unbekannte Ursache.
package com.example.enterprise.run8a;
public enum IncidentQuestion {
CPU_BOUND,
ALLOCATION_BOUND,
LOCK_BOUND,
IO_BOUND,
CLASSLOADING_BOUND,
CONTAINER_LIMIT_BOUND,
UNKNOWN
}
Korreliert Heap, Threads, GC, Classloading, CPU, Container und Latenz.
package com.example.enterprise.run8a;
import java.util.List;
import java.util.Objects;
public record JvmDiagnosticSnapshot(
MemorySnapshot memory,
ThreadStateSnapshot threads,
List<GarbageCollectorSnapshot> garbageCollectors,
ClassLoadingSnapshot classLoading,
ContainerMemorySnapshot container,
double processCpuLoad,
double allocationRateMbPerSecond,
double requestP95Millis) {
public JvmDiagnosticSnapshot {
Objects.requireNonNull(memory, "memory");
Objects.requireNonNull(threads, "threads");
garbageCollectors = List.copyOf(garbageCollectors);
Objects.requireNonNull(classLoading, "classLoading");
Objects.requireNonNull(container, "container");
}
public long totalCollections() {
return garbageCollectors.stream()
.mapToLong(GarbageCollectorSnapshot::collectionCount)
.filter(value -> value > 0)
.sum();
}
public long totalCollectionTimeMillis() {
return garbageCollectors.stream()
.mapToLong(GarbageCollectorSnapshot::collectionTimeMillis)
.filter(value -> value > 0)
.sum();
}
}
Ordnet Messwerte einer prüfbaren Hypothese mit Evidenz und nächsten Schritten zu.
package com.example.enterprise.run8a;
import java.util.ArrayList;
import java.util.List;
public final class JvmIncidentClassifier {
public IncidentAssessment assess(JvmDiagnosticSnapshot snapshot) {
List<String> evidence = new ArrayList<>();
List<String> actions = new ArrayList<>();
if (snapshot.container().closeToLimit()
&& !snapshot.memory().heapLooksTight()) {
evidence.add("Process uses at least 90% of the container limit while heap is below 85%.");
evidence.add("Untracked/native estimate: "
+ snapshot.container().nativeAndUntrackedBytes(snapshot.memory()) + " bytes.");
actions.add("Enable Native Memory Tracking and inspect direct buffers, stacks and metaspace.");
actions.add("Compare -Xmx plus native headroom with the Kubernetes memory limit.");
return new IncidentAssessment(IncidentQuestion.CONTAINER_LIMIT_BOUND, evidence, actions);
}
if (snapshot.threads().blockedRatio() >= 0.25
|| snapshot.threads().count(Thread.State.BLOCKED) >= 4) {
evidence.add("Blocked threads: " + snapshot.threads().count(Thread.State.BLOCKED)
+ " of " + snapshot.threads().totalThreads() + '.');
actions.add("Capture two thread dumps and compare lock owners and waiters.");
actions.add("Inspect synchronized regions before increasing thread counts.");
return new IncidentAssessment(IncidentQuestion.LOCK_BOUND, evidence, actions);
}
if (snapshot.memory().heapLooksTight()
&& snapshot.allocationRateMbPerSecond() >= 100.0) {
evidence.add("Heap utilization: " + percent(snapshot.memory().heapUtilization()) + '.');
evidence.add("Allocation rate: " + snapshot.allocationRateMbPerSecond() + " MiB/s.");
actions.add("Record JFR allocation events and inspect top allocating stack traces.");
actions.add("Compare live-set size after GC before changing heap or collector settings.");
return new IncidentAssessment(IncidentQuestion.ALLOCATION_BOUND, evidence, actions);
}
if (snapshot.processCpuLoad() >= 0.85
&& snapshot.threads().blockedRatio() < 0.10) {
evidence.add("Process CPU load: " + percent(snapshot.processCpuLoad()) + '.');
evidence.add("Few threads are blocked, so lock contention is not the first hypothesis.");
actions.add("Capture a JFR execution profile or async-profiler flame graph.");
actions.add("Validate the hot path with representative production-like input.");
return new IncidentAssessment(IncidentQuestion.CPU_BOUND, evidence, actions);
}
if (snapshot.requestP95Millis() >= 500.0
&& snapshot.processCpuLoad() < 0.50
&& snapshot.threads().count(Thread.State.WAITING)
+ snapshot.threads().count(Thread.State.TIMED_WAITING) >= 4) {
evidence.add("Request p95 is high while process CPU is below 50%.");
evidence.add("Many threads are waiting rather than executing.");
actions.add("Correlate traces with database, HTTP client and queue wait times.");
actions.add("Check timeouts and connection-pool saturation before adding threads.");
return new IncidentAssessment(IncidentQuestion.IO_BOUND, evidence, actions);
}
if (snapshot.classLoading().loadedNow() >= 50_000
|| snapshot.classLoading().totalLoaded() - snapshot.classLoading().unloaded() >= 75_000) {
evidence.add("Unusually high number of currently or historically loaded classes.");
actions.add("Inspect class-loader statistics and repeated deployment/plugin loaders.");
actions.add("Correlate class count with metaspace growth over time.");
return new IncidentAssessment(IncidentQuestion.CLASSLOADING_BOUND, evidence, actions);
}
evidence.add("No threshold in the teaching decision table is crossed.");
actions.add("Collect a longer time series and align JVM data with request metrics and deploys.");
return new IncidentAssessment(IncidentQuestion.UNKNOWN, evidence, actions);
}
public IncidentQuestion classify(
MemorySnapshot memory,
ThreadStateSnapshot threads,
List<GarbageCollectorSnapshot> garbageCollectors) {
var snapshot = new JvmDiagnosticSnapshot(
memory,
threads,
garbageCollectors,
new ClassLoadingSnapshot(0, 0, 0, ClassLoader.getSystemClassLoader()),
new ContainerMemorySnapshot(0, 0, 0, 0),
0.0,
memory.heapLooksTight() ? 150.0 : 0.0,
0.0);
return assess(snapshot).primaryDiagnosis();
}
private String percent(double ratio) {
return "%.1f%%".formatted(ratio * 100.0);
}
}
Zeigt starke statische Retention als bewusstes Anti-Pattern.
package com.example.enterprise.run8a;
import java.util.ArrayList;
import java.util.List;
public final class LeakProneStaticRegistry {
private static final List<byte[]> RETAINED = new ArrayList<>();
private LeakProneStaticRegistry() {}
public static void retain(byte[] data) {
RETAINED.add(data);
}
public static int retainedCount() {
return RETAINED.size();
}
public static void clear() {
RETAINED.clear();
}
}
Liest Heap und Non-Heap über die standardisierten MXBeans.
package com.example.enterprise.run8a;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
public final class MemoryProbe {
private final MemoryMXBean bean = ManagementFactory.getMemoryMXBean();
public MemorySnapshot capture() {
var heap = bean.getHeapMemoryUsage();
var nonHeap = bean.getNonHeapMemoryUsage();
return new MemorySnapshot(
heap.getUsed(),
heap.getCommitted(),
heap.getMax(),
nonHeap.getUsed(),
System.nanoTime());
}
}
Berechnet freie Commit-Kapazität und Heap-Auslastung.
package com.example.enterprise.run8a;
public record MemorySnapshot(
long heapUsed,
long heapCommitted,
long heapMax,
long nonHeapUsed,
long timestampNanos) {
public long heapFreeInsideCommit() {
return Math.max(0, heapCommitted - heapUsed);
}
public double heapUtilization() {
return heapMax > 0 ? (double) heapUsed / heapMax : 0.0;
}
public boolean heapLooksTight() {
return heapMax > 0 && heapUtilization() >= 0.85;
}
}
Repräsentiert eine einzelne lokale Zeitmessung.
package com.example.enterprise.run8a;
public record ProfilingSample(String name, long durationNanos) {
public long millis() {
return durationNanos / 1_000_000;
}
}
Instrumentiert einen lokalen Codebereich mit monotonic time.
package com.example.enterprise.run8a;
public final class ProfilingTimer implements AutoCloseable {
private final String name;
private final HotPathAnalyzer analyzer;
private final long startNanos;
public ProfilingTimer(String name, HotPathAnalyzer analyzer) {
this.name = name;
this.analyzer = analyzer;
this.startNanos = System.nanoTime();
}
@Override
public void close() {
analyzer.record(name, System.nanoTime() - startNanos);
}
}
Demonstriert einen begrenzten, zugriffsbasierten LRU-Cache.
package com.example.enterprise.run8a;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
public final class RetainedCache<K, V> {
private final int maxEntries;
private final LinkedHashMap<K, V> map;
public RetainedCache(int maxEntries) {
if (maxEntries < 1) {
throw new IllegalArgumentException("maxEntries must be positive");
}
this.maxEntries = maxEntries;
this.map = new LinkedHashMap<>(16, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > RetainedCache.this.maxEntries;
}
};
}
public synchronized void put(K key, V value) {
map.put(key, value);
}
public synchronized V get(K key) {
return map.get(key);
}
public synchronized int size() {
return map.size();
}
public synchronized Set<K> keys() {
return new LinkedHashSet<>(map.keySet());
}
}
Führt als Einstieg durch Incident, Evidenz, Maßnahme und Kontrollmessung.
package com.example.enterprise.run8a;
public final class Run8ADemo {
public static void main(String[] args) {
var scenarios = new DiagnosticScenarioFactory();
var classifier = new JvmIncidentClassifier();
IncidentAssessment before = classifier.assess(scenarios.allocationIncident());
IncidentAssessment after = classifier.assess(scenarios.afterCacheBounded());
var gcEvent = new GcLogParser().parse(
"[2.345s][info][gc] GC(12) Pause Young (Normal) 512M->128M(1024M) 12.345ms")
.orElseThrow();
System.out.println("Incident before: " + before.primaryDiagnosis());
before.evidence().forEach(item -> System.out.println(" evidence: " + item));
before.nextActions().forEach(item -> System.out.println(" next: " + item));
System.out.println("After bounded cache: " + after.primaryDiagnosis());
System.out.println("Parsed GC event: reclaimed=" + gcEvent.reclaimedMb()
+ " MiB, pause=" + gcEvent.pauseMillis() + " ms");
System.out.println("RUN8A_DEMO_OK");
}
}
Prüft Parser, Cache, Perzentile und jeden Zweig der Decision Table.
package com.example.enterprise.run8a;
import java.util.List;
import java.util.Map;
public final class Run8ATestRunner {
private static final long MIB = 1024L * 1024L;
public static void main(String[] args) {
boundedCacheEvictsLeastRecentlyUsedEntry();
staticRegistryDemonstratesRetention();
unifiedGcLogIsParsed();
malformedGcLogIsRejected();
benchmarkReportCalculatesPercentiles();
allIncidentBranchesAreReachable();
scenarioShowsImprovementAfterBoundingCache();
System.out.println("RUN8A_TESTS_OK");
}
static void boundedCacheEvictsLeastRecentlyUsedEntry() {
var cache = new RetainedCache<String, String>(2);
cache.put("a", "1");
cache.put("b", "2");
cache.get("a");
cache.put("c", "3");
equals(2, cache.size());
check(cache.keys().contains("a"));
check(!cache.keys().contains("b"));
}
static void staticRegistryDemonstratesRetention() {
LeakProneStaticRegistry.clear();
LeakProneStaticRegistry.retain(new byte[8]);
equals(1, LeakProneStaticRegistry.retainedCount());
LeakProneStaticRegistry.clear();
equals(0, LeakProneStaticRegistry.retainedCount());
}
static void unifiedGcLogIsParsed() {
var parsed = new GcLogParser().parse(
"[2.345s][info][gc] GC(12) Pause Young (Normal) 512M->128M(1024M) 12.345ms")
.orElseThrow();
equals(384L, parsed.reclaimedMb());
equals(12L, parsed.pauseMillis());
}
static void malformedGcLogIsRejected() {
check(new GcLogParser().parse("not a GC event").isEmpty());
}
static void benchmarkReportCalculatesPercentiles() {
var report = new BenchmarkReport("parse", 5, new long[]{10, 20, 30, 40, 100});
equals(30L, report.medianNanos());
equals(100L, report.p95Nanos());
long[] defensiveCopy = report.measuredNanos();
defensiveCopy[0] = 999;
equals(10L, report.measuredNanos()[0]);
}
static void allIncidentBranchesAreReachable() {
equals(IncidentQuestion.ALLOCATION_BOUND,
classify(snapshot(0.90, 0.60, 200, 100, states(Thread.State.RUNNABLE, 8), 10_000, false)));
equals(IncidentQuestion.LOCK_BOUND,
classify(snapshot(0.50, 0.30, 10, 300, states(Thread.State.BLOCKED, 4), 10_000, false)));
equals(IncidentQuestion.CPU_BOUND,
classify(snapshot(0.50, 0.92, 10, 200, states(Thread.State.RUNNABLE, 8), 10_000, false)));
equals(IncidentQuestion.IO_BOUND,
classify(snapshot(0.50, 0.20, 10, 900, states(Thread.State.WAITING, 6), 10_000, false)));
equals(IncidentQuestion.CLASSLOADING_BOUND,
classify(snapshot(0.50, 0.20, 10, 100, states(Thread.State.RUNNABLE, 4), 60_000, false)));
equals(IncidentQuestion.CONTAINER_LIMIT_BOUND,
classify(snapshot(0.50, 0.20, 10, 100, states(Thread.State.RUNNABLE, 4), 10_000, true)));
equals(IncidentQuestion.UNKNOWN,
classify(snapshot(0.50, 0.20, 10, 100, states(Thread.State.RUNNABLE, 4), 10_000, false)));
}
static void scenarioShowsImprovementAfterBoundingCache() {
var factory = new DiagnosticScenarioFactory();
equals(IncidentQuestion.ALLOCATION_BOUND, classify(factory.allocationIncident()));
equals(IncidentQuestion.UNKNOWN, classify(factory.afterCacheBounded()));
}
private static JvmDiagnosticSnapshot snapshot(
double heapRatio,
double cpu,
double allocationRate,
double p95,
Map<Thread.State, Integer> states,
int loadedClasses,
boolean containerTight) {
long heapMax = 1_000 * MIB;
var memory = new MemorySnapshot((long) (heapMax * heapRatio), heapMax, heapMax, 100 * MIB, 1);
var container = containerTight
? new ContainerMemorySnapshot(1_000 * MIB, 950 * MIB, 50 * MIB, 20 * MIB)
: new ContainerMemorySnapshot(2_000 * MIB, 800 * MIB, 50 * MIB, 20 * MIB);
return new JvmDiagnosticSnapshot(
memory,
new ThreadStateSnapshot(states),
List.of(),
new ClassLoadingSnapshot(loadedClasses, loadedClasses, 0,
ClassLoader.getSystemClassLoader()),
container,
cpu,
allocationRate,
p95);
}
private static Map<Thread.State, Integer> states(Thread.State state, int count) {
return Map.of(state, count);
}
private static IncidentQuestion classify(JvmDiagnosticSnapshot snapshot) {
return new JvmIncidentClassifier().assess(snapshot).primaryDiagnosis();
}
private static void equals(Object expected, Object actual) {
if (!java.util.Objects.equals(expected, actual)) {
throw new AssertionError(expected + " != " + actual);
}
}
private static void check(boolean condition) {
if (!condition) {
throw new AssertionError("condition is false");
}
}
}
Erfasst Threadzustände und fragt die JVM nach Deadlocks.
package com.example.enterprise.run8a;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.EnumMap;
import java.util.Map;
public final class ThreadStateSampler {
private final ThreadMXBean bean = ManagementFactory.getThreadMXBean();
public ThreadStateSnapshot capture() {
Map<Thread.State, Integer> counts = new EnumMap<>(Thread.State.class);
for (long id : bean.getAllThreadIds()) {
ThreadInfo info = bean.getThreadInfo(id);
if (info != null) {
counts.merge(info.getThreadState(), 1, Integer::sum);
}
}
return new ThreadStateSnapshot(counts);
}
public long[] findDeadlockedThreadIds() {
long[] ids = bean.findDeadlockedThreads();
return ids == null ? new long[0] : ids.clone();
}
}
Verdichtet Threadzustände und berechnet den Blocked-Anteil.
package com.example.enterprise.run8a;
import java.util.EnumMap;
import java.util.Map;
public record ThreadStateSnapshot(Map<Thread.State, Integer> counts) {
public ThreadStateSnapshot {
EnumMap<Thread.State, Integer> copy = new EnumMap<>(Thread.State.class);
copy.putAll(counts);
counts = Map.copyOf(copy);
}
public int count(Thread.State state) {
return counts.getOrDefault(state, 0);
}
public int totalThreads() {
return counts.values().stream().mapToInt(Integer::intValue).sum();
}
public double blockedRatio() {
return totalThreads() == 0 ? 0.0 : (double) count(Thread.State.BLOCKED) / totalThreads();
}
}
Zeigt schwache Referenzen für Daten ohne fachliche Ownership.
package com.example.enterprise.run8a;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
public final class WeakSessionRegistry {
private final Map<String, WeakReference<byte[]>> sessions = new HashMap<>();
public void remember(String id, byte[] data) {
sessions.put(id, new WeakReference<>(data));
}
public int knownIds() {
return sessions.size();
}
public int liveValues() {
int liveValues = 0;
for (var reference : sessions.values()) {
if (reference.get() != null) {
liveValues++;
}
}
return liveValues;
}
}