JVM Performance Optimization
Java Virtual Machine performance tuning, garbage collection, and profiling techniques. Use when diagnosing performance issues, tuning JVM parameters, or optimizing Java applications.
Note: This document synthesizes JVM performance concepts from various sources including Oracle documentation, JVM specifications, and community best practices.
Performance Trade-offs
JVM tuning involves inherent trade-offs. Improving one metric often impacts another.
| Metric | Description |
|---|---|
| Throughput | Work units per time period |
| Latency | Time to complete single operation |
| Capacity | Concurrent work units supported |
| Utilization | Resource usage percentage |
| Efficiency | Throughput per resource unit |
| Scalability | Performance under increasing load |
| Degradation | Performance decline over time |
2. Garbage Collection Algorithms
See references/gc-tuning.md for detailed GC algorithms (mark-and-sweep, generational collection, STW pauses), JVM tuning parameters, memory analysis, and JIT compilation.
3. Garbage Collectors
See references/garbage-collectors.md for detailed information on:
- Serial GC, Parallel GC, G1 GC
- ZGC (Z Garbage Collector)
- Shenandoah
4. GC Selection Guide
| Heap Size | Latency Requirement | Recommended GC |
|---|---|---|
| < 100MB | Any | Serial |
| < 4GB | Throughput priority | Parallel |
| 4GB - 32GB | Balanced | G1 |
| > 32GB | Low latency | ZGC/Shenandoah |
| Any | Ultra-low latency (< 1ms) | ZGC/Shenandoah |
5. Performance Analysis Approach
Systematic Process
- Define performance goals with specific metrics
- Measure baseline performance
- Identify bottlenecks through profiling
- Make targeted changes
- Verify improvement with measurements
- Document findings
Measurement Principles
- Statistical significance: Multiple runs required
- Control environment: Same hardware, data, load
- Measure before and after: Quantify change impact
- Non-normal distributions: Use percentiles, not just means
7. Profiling Tools
JDK Flight Recorder (JFR)
Low-overhead production profiling.
# Start recording
jcmd <pid> JFR.start name=profile duration=60s filename=recording.jfr
# Or via JVM args
-XX:StartFlightRecording=duration=60s,filename=recording.jfr
Events:
- CPU usage
- Memory allocation
- GC events
- Thread events
- Method profiling
Java Mission Control (JMC)
GUI for JFR analysis.
Key Views:
- Event browser
- Thread analysis
- Memory analysis
- Code profiling
async-profiler
Low-overhead sampling profiler.
# CPU profiling
./profiler.sh -d 60 -f cpu.html <pid>
# Allocation profiling
./profiler.sh -d 60 -e alloc -f alloc.html <pid>
JMX Monitoring
# Enable remote JMX
-Dcom.sun.management.jmxremote
-Dcom.sun.management.jmxremote.port=9010
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false
8. Common Performance Mistakes
Optimizing Without Measurement
Making changes based on assumptions rather than data.
Warning signs:
- Complex code for assumed performance
- No profiling data
- "It feels faster"
Copy-Paste Tuning
Applying JVM flags without understanding their impact.
Warning signs:
- Copy-paste JVM flags from blogs
- Using outdated tuning advice
- Ignoring workload characteristics
Flawed Microbenchmarks
Microbenchmarks can produce misleading results.
Common issues:
- JIT compilation effects
- Dead code elimination
- Warmup not considered
Use JMH for microbenchmarks:
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(1)
public class MyBenchmark {
@Benchmark
public void testMethod() {
// benchmark code
}
}
Ignoring Tail Latency
Average latency can hide problematic outliers.
Wrong: Average is 50ms Right: P99 is 500ms, indicates tail latency problem
9. Virtual Threads (Java 21+)
Benefits
- Lightweight (millions possible)
- No thread pool management
- Simpler async code
When to Use
Good fit:
- I/O-bound workloads
- Many concurrent tasks
- Blocking APIs
Not good fit:
- CPU-bound tasks
- Synchronized blocks (pins carrier thread)
- Thread-local heavy code
Implementation
// Create virtual thread
Thread.startVirtualThread(() -> {
// Task code
});
// ExecutorService with virtual threads
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
}
Pinning Issues
Pinning causes: Carrier thread blocked, reducing throughput.
Avoid:
synchronizedblocks/methods- Native methods that block
Fix:
// Replace synchronized with ReentrantLock
// Before
synchronized(lock) { ... }
// After
private final ReentrantLock lock = new ReentrantLock();
lock.lock();
try { ... } finally { lock.unlock(); }
10. Cloud-Native Considerations
Container Memory Limits
# JVM respects container limits (Java 10+)
-XX:+UseContainerSupport
# Limit JVM heap to leave room for off-heap
# Rule: Heap = Container Memory * 0.75 - Off-heap estimate
-Xmx6g # In 8GB container with ~1GB off-heap
Startup Optimization
# Class Data Sharing
java -Xshare:dump
-XX:+UseSharedSpaces
# AOT compilation (GraalVM)
native-image -jar app.jar
# CDS with dynamic archive
-XX:ArchiveClassesAtExit=app.jsa
-XX:SharedArchiveFile=app.jsa
Observability Stack
┌─────────────┐
│ Application │
│ (JVM) │
└──────┬──────┘
│
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Micrometer │────▶│ Prometheus │────▶│ Grafana │
│ (metrics) │ │ (storage) │ │ (dashboard) │
└─────────────┘ └─────────────┘ └─────────────┘
│
▼
┌─────────────┐ ┌─────────────┐
│OpenTelemetry│────▶│ Jaeger │
│ (tracing) │ │ (traces) │
└─────────────┘ └─────────────┘
11. Performance Troubleshooting Checklist
High CPU Usage
- Profile with async-profiler
- Check for GC overhead (
jstat -gcutil) - Look for busy loops
- Check for excessive logging
High Memory Usage
- Check heap usage (
jcmd GC.heap_info) - Look for memory leaks (heap dump)
- Analyze GC logs
- Check Metaspace for class leaks
Long GC Pauses
- Check GC logs (
-Xlog:gc*) - Analyze pause times vs goals
- Consider different GC algorithm
- Check for heap sizing issues
Slow Startup
- Profile with JFR
- Check class loading (
-Xlog:class+load) - Consider CDS or AOT
- Reduce classpath scanning
JVM Flags Quick Reference
# Essential logging
-Xlog:gc*:file=gc.log:time,uptime,level,tags
# Memory
-Xms4g -Xmx4g
-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m
# G1 GC
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
# ZGC (Java 15+)
-XX:+UseZGC
-XX:ZCollectionInterval=0
# Flight Recorder
-XX:StartFlightRecording=duration=60s,filename=rec.jfr
# Heap dump on OOM
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/tmp/heap.hprof
Related Skills
- spring-framework: Actuator, Micrometer setup, Spring-specific debugging
- k8s-workflow: Container resource management
- dockerfile: JVM containerization patterns
References
- Oracle JVM Documentation
- Java Performance by Scott Oaks (O'Reilly)
- GC Handbook by Charlie Hunt, Binu John
- Optimizing Java (2nd Edition) by Benjamin Evans, James Gough (for deeper study)