| name | java-performance |
| description | JVM performance tuning guide — heap size configuration, GC selection, Kubernetes CPU throttling, and Prometheus monitoring. Use when optimizing Java applications, configuring the JVM for containers or Kubernetes, choosing the right Garbage Collector (G1, ZGC, ParallelGC), diagnosing memory or latency issues, or understanding Spring Boot executor metrics. |
| metadata | {"scope":"optimization","version":"1.0.0"} |
JVM Performance
Always configure the JVM — default settings were designed for 2000s-era environments.
When to Use This Skill
- Configure JVM for Docker containers or Kubernetes
- Choose and tune the Garbage Collector (GC)
- Diagnose OutOfMemoryError, high pause times, or CPU throttling
- Interpret Prometheus executor metrics in Spring Boot
- Compare scale up vs scale out strategies for JVMs
JVM Ergonomics
Use -XX:+PrintFlagsFinal to view all active JVM settings.
Aggressive flags (use with caution):
-XX:+AggressiveHeap
-XX:+AggressiveOpts
The JVM does not know it is running in a container — configure explicitly to avoid inadequate defaults.
Heap Size
How the JVM calculates automatically
| Available Memory | Default Heap |
|---|
| Up to 256 MB | 50% |
| 256–512 MB | 126 MB fixed |
| Above 512 MB | 25% |
Configuration flags
-Xms 50m
-Xmx 1g
-XX:MaxRAMPercentage=74
Microsoft Recommendations
Servers:
- Set whatever value the application requires.
Containers:
- Respect the 75% limit of the container memory.
- Be careful with off-heap memory: JNI, FFMI, Spark, Pinot, Elasticsearch.
-Xmx: good for well-sized, stable workloads.
-XX:MaxRAMPercentage: better for workloads that scale.
- Consider
MinHeapSize == MaxHeapSize (early research suggests this).
-XX:+AlwaysPreTouch: locks the defined memory at startup (startup cost, but eliminates runtime memory checks).
Garbage Collector
Default by version
| Version | Default GC |
|---|
| Java 8 | SerialGC or ParallelGC |
| Java 9+ | G1GC (real default) |
| Java 11+ | SerialGC (< 2 CPUs and < 1792 MB RAM) |
| Java 11+ | G1GC or ParallelGC (with sufficient resources) |
Processors visible to the JVM
Outside container: uses Runtime.availableProcessors()
Inside container: based on cpu.period, cpu.quota or cpu.share
- 1–1000m → 1 CPU
- 1001–2000m → 2 CPUs
Recommendations by heap size
| Heap Size | Recommended GC |
|---|
| Up to 2 GB | ParallelGC (best throughput) |
| 2–4 GB | ParallelGC or G1GC |
| Above 4 GB | G1GC or ZGC |
ParallelGC triggers Stop-the-World → impacts latency (tail latency).
G1GC
- Pauses range from milliseconds to seconds.
- Divides memory into regions of 1–32 MB.
- Good balance between throughput and latency.
ZGC
- Designed for low latency and high scalability.
- Does most of its work while application threads are still running.
- Maximum pause: ~1ms.
- Supports heaps from hundreds of MB to TB.
- Requires sufficient resources (memory and CPU) to free memory faster than application threads consume it.
Kubernetes
CPU Throttling
CPU requests in Kubernetes define the CPU time available per period.
- 1000m = the application can use one full CPU per 100ms period.
- If multiple threads sum > 1000m in the period → throttled for up to 60ms.
Processors visible to the JVM in Kubernetes:
| CPU Limit | CPUs for JVM |
|---|
| 1–1000m | 1 CPU |
| 1001–2000m | 2 CPUs |
| 2001–3000m | 3 CPUs |
Trick: Deceiving the JVM
-XX:ActiveProcessorCount=4
Useful for IO-bound applications in containers with 1–2 cores: the JVM increases its internal thread pool, improving the capacity to wait for IO in parallel.
Recommendations for JVM on Kubernetes
- Minimum recommended: 1000m CPU limit with 2 CPUs declared for the JVM.
- Ideal: 2000m CPU limit + 2 CPUs for JVM + heap above 2 GB.
- JVM performs better with scale up (not scale out).
- Scale up saves memory (single metaspace/non-heap).
- Scale up uses only one GC for the entire heap.
Recommended startup strategy
- Start with ParallelGC for small heaps (avoid JVM default ergonomics).
- Use JFR (Java Flight Recorder) and GC logs to understand bottlenecks.
- Evolve to G1GC or ZGC based on collected data.
Diagnostic Tools
jps
jmap -histo $PID
jmap -clstats $PID
Prometheus Metrics (Spring Boot)
executor_queued_tasks
Shows how many tasks are waiting for execution in the executor queue.
- Value 0.0: pool handles the demand — no backlog.
- Value increases when all threads are busy and new tasks arrive.
executor_queue_remaining_tasks
Shows how many free slots remain in the queue.
- Value 100.0: no tasks waiting — system not overloaded.
- Decreases when the pool is saturated and tasks are queued.
Spring Boot configuration:
spring:
task:
execution:
pool:
queue-capacity: 100
Customization by Java Version
Java 8
-XX:+UseStringDeduplicationJVM
-XX:+UseSerialGC
-XX:+UseConcMarkSweepGC
-XX:+UseG1GC
Java 9
- G1GC as default.
- Divides memory into regions of 1–32 MB.
Java 11
-XX:+UseEpsilonGC
-XX:+UseZGC
Java 12+
- Microbenchmark suite included.
- CDS Archives enabled by default.
- Ongoing improvements to G1GC.
References