Part 9 · 1 chapters · ~8 min
Performance and Profiling
JIT warm-up and benchmarking with JMH, Java Flight Recorder and Mission Control, async-profiler, heap dumps and Eclipse MAT, GC logs and choosing a collector, connection pool and thread sizing, Hibernate performance (N+1, batching, fetch plans), and JSON serialisation costs.
12
Measuring the JVM
code
// JMH: the only trustworthy way to microbenchmark the JVM (handles warm-up, dead-code elimination, forks)
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5) @Fork(2)
public class FeeBench { @Benchmark public long fee() { return Fees.fee(5_000_000L); } }
java -XX:StartFlightRecording=duration=120s,filename=rec.jfr -jar ledger.jar # always-on capable, ~1% overhead
jfr print --events jdk.ExecutionSample rec.jfr | head # or open in JDK Mission Control
asprof -d 30 -e cpu -f flame.html <pid> # async-profiler flame graph
jcmd <pid> GC.heap_dump heap.hprof # then Eclipse MAT: dominator tree| usual bottleneck | fix |
|---|---|
| Hibernate N+1 and lazy loading | JOIN FETCH or entity graphs; hibernate.default_batch_fetch_size; DTO projections for reads |
| JDBC batching off | hibernate.jdbc.batch_size and ordered inserts; sequence-based ids (identity disables batching) |
| pool exhaustion | HikariCP metrics; pool size from the database's capacity, not thread count (virtual threads especially) |
| allocation-heavy code | JFR allocation profiling; avoid boxing in hot loops |
| GC pauses on large heaps | ZGC or G1 pause goals; right-size the heap |