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 bottleneckfix
Hibernate N+1 and lazy loadingJOIN FETCH or entity graphs; hibernate.default_batch_fetch_size; DTO projections for reads
JDBC batching offhibernate.jdbc.batch_size and ordered inserts; sequence-based ids (identity disables batching)
pool exhaustionHikariCP metrics; pool size from the database's capacity, not thread count (virtual threads especially)
allocation-heavy codeJFR allocation profiling; avoid boxing in hot loops
GC pauses on large heapsZGC or G1 pause goals; right-size the heap