Add flags: production JVM flag cheat sheet for Spring Boot on JDK 25/27

MaxRAMPercentage vs a hardcoded -Xmx, -XX:-UseContainerSupport, the
ActiveProcessorCount lie, and which diagnostic flags are worth leaving
on in prod, verified in real Docker containers on JDK 25 and JDK 27.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
2026-10-01 04:34:04 +00:00
co-authored by Claude Sonnet 5
parent e83c9949d8
commit 2f88734f5d
22 changed files with 832 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
import java.util.ArrayList;
import java.util.List;
/**
* Allocates a large number of a trivial fixed-shape object and a trivial
* array, then sleeps so `jcmd <pid> GC.class_histogram` can be run against
* it from outside the container. Used to measure the per-instance byte cost
* that -XX:+/-UseCompactObjectHeaders actually makes, rather than quoting
* the JEP's own number. See
* https://ankurm.com/jvm-flags-for-spring-boot-in-containers-jdk-25-27/
*/
public class HeaderSizeDemo {
static final class Pair { int a; int b; Pair(int a, int b) { this.a = a; this.b = b; } }
public static void main(String[] args) throws Exception {
int count = args.length > 0 ? Integer.parseInt(args[0]) : 2_000_000;
List<Pair> pairs = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
pairs.add(new Pair(i, i));
}
System.out.println("allocated " + pairs.size() + " Pair instances, pid=" + ProcessHandle.current().pid());
System.gc();
Thread.sleep(120_000);
System.out.println(pairs.size());
}
}
+37
View File
@@ -0,0 +1,37 @@
import java.util.ArrayList;
import java.util.List;
/**
* Deliberately allocates and retains heap in fixed-size chunks until it is
* killed or finishes, printing progress as it goes. Used to demonstrate what
* actually happens when a container's memory limit, the JVM's heap flags,
* and real allocation pressure disagree with each other. See the post at
* https://ankurm.com/jvm-flags-for-spring-boot-in-containers-jdk-25-27/
* and docs/output/05-oom-*.txt / 06-oom-*.txt for the captured runs.
*/
public class MemoryHog {
public static void main(String[] args) throws Exception {
int chunkMb = args.length > 0 ? Integer.parseInt(args[0]) : 16;
int sleepMs = args.length > 1 ? Integer.parseInt(args[1]) : 150;
List<byte[]> held = new ArrayList<>();
long totalMb = 0;
System.out.println("MemoryHog starting: chunk=" + chunkMb + "MB sleep=" + sleepMs + "ms");
System.out.flush();
while (true) {
byte[] chunk = new byte[chunkMb * 1024 * 1024];
for (int i = 0; i < chunk.length; i += 4096) {
chunk[i] = 1; // touch each page so it is actually resident, not just reserved
}
held.add(chunk);
totalMb += chunkMb;
Runtime rt = Runtime.getRuntime();
System.out.printf("held=%dMB heapUsed=%dMB heapMax=%dMB freeHeap=%dMB%n",
totalMb,
(rt.totalMemory() - rt.freeMemory()) / (1024 * 1024),
rt.maxMemory() / (1024 * 1024),
rt.freeMemory() / (1024 * 1024));
System.out.flush();
Thread.sleep(sleepMs);
}
}
}