Performance & Optimization
Cloud-Native Java
- The JVM is container-aware: defaults derive from cgroup CPU/memory limits — set
-XX:MaxRAMPercentagedeliberately - Total memory = heap + metaspace + code cache + thread stacks + direct buffers; the OOM-killer counts it all
- CPU quota < 2 cores silently degrades GC/JIT parallelism — check
availableProcessors() - Cold-start ladder: AppCDS → CRaC checkpoint/restore → GraalVM native-image
- JIT warm-up meets autoscaling: fresh pods serve slow requests — pre-warm or scale earlier
- Right-size by measuring: a 4 GB limit around a 512 MB working set is money burned
ENTRYPOINT ["java", \
"-XX:MaxRAMPercentage=75", # heap = 75% of container limit; rest for native
"-XX:+ExitOnOutOfMemoryError", # die fast; let the orchestrator restart cleanly
"-Xlog:gc*:file=/logs/gc.log:time:filecount=5,filesize=20m", \
"-XX:NativeMemoryTracking=summary", \
"-jar", "app.jar"]
$ jcmd 1 VM.native_memory summary # where the non-heap memory wentThe recurring incident (OCNJ ch. 8–9): container OOM-killed while heap dashboards showed 60% usage — because the process exceeded the cgroup limit on metaspace, thread stacks (hundreds of threads × 1 MB), and direct buffers. Budget explicitly: for a 1 GiB limit, ~75% heap works only when threads and native usage are modest; Native Memory Tracking plus jcmd shows the real split.
Startup: class loading + verification + interpretation + JIT warm-up can mean minutes to peak performance for a big Spring service — poison for scale-to-zero. The ladder: AppCDS/CDS archives (share pre-parsed class metadata, easy 10–40% off startup); CRaC (checkpoint a warmed JVM, restore in ~100 ms — needs framework cooperation to close/reopen sockets); GraalVM native-image (ms startup, MBs of RSS, but closed-world reflection config and generally lower peak throughput than warmed C2 — Jit Compilation). Project Leyden is folding these ideas into the mainline JDK (Future Directions).