On this page
Profiling Basics
Profiling measures where your application spends time and memory during execution. It is essential for identifying performance bottlenecks before optimizing.
Profiling Types
| Type | Answers | Tools |
|---|---|---|
| CPU | Where is time spent? | async-profiler, JFR, VisualVM |
| Memory | What objects consume heap? | Eclipse MAT, JFR, VisualVM |
| Thread | Deadlocks? Thread contention? | jstack, JFR, VisualVM |
| I/O | Slow network/disk calls? | JFR, strace |
JDK Built-in Tools
jcmd and JFR (Java Flight Recorder)
# Start 60-second JFR recording
jcmd <pid> JFR.start duration=60s filename=recording.jfr settings=profile
# Dump recording
jcmd <pid> JFR.dump filename=recording.jfr
# Analyze with JDK Mission Control (jmc)
JFR overhead is typically <1% — safe for production.
jstack (Thread Dump)
jstack <pid> > threads.txt
# Look for:
# - BLOCKED threads (lock contention)
# - Waiting on monitor
# - Deadlock detection section at bottom
jmap (Memory Map)
jmap -histo:live <pid> | head -20 # top objects by instance count
jmap -dump:live,format=b,file=heap.hprof <pid>
VisualVM
Free GUI tool included with JDK (or standalone):
Connect to local/remote JVM → Monitor tab (CPU, heap, threads)
→ Sampler tab (CPU/memory profiling)
→ Profiler tab (instrumentation-based)
CPU Profiling Workflow
1. Reproduce slowness under load (JMeter, k6)
2. Start CPU profiler during slow period
3. Identify hot methods (top of flame graph)
4. Analyze call tree — why is this method hot?
5. Fix the bottleneck
6. Re-profile to verify improvement
Common Bottleneck Patterns
| Pattern | Symptom | Typical cause |
|---|---|---|
| Hot method | 80%+ CPU in one method | Algorithm inefficiency |
| Lock contention | Many BLOCKED threads | Oversynchronized code |
| GC pressure | High GC time | Object allocation rate |
| I/O wait | Threads in WAITING | Slow DB/network calls |
| Memory leak | Growing old gen | Unbounded caches, listeners |
Spring Boot Actuator
management:
endpoints:
web:
exposure:
include: health,metrics,threaddump,heapdump
curl http://localhost:8080/actuator/threaddump
curl http://localhost:8080/actuator/metrics/jvm.gc.pause
curl http://localhost:8080/actuator/heapdump -o heap.hprof
Benchmarking Before and After
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Thread)
public class StringConcatBenchmark {
@Benchmark
public String stringConcat() {
String result = "";
for (int i = 0; i < 100; i++) result += i;
return result;
}
@Benchmark
public String stringBuilder() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) sb.append(i);
return sb.toString();
}
}
Use JMH (Java Microbenchmark Harness) for reliable micro-benchmarks.
Best Practices
- Profile in production-like conditions — not dev machines
- Use sampling profilers (async-profiler, JFR) over instrumentation in production
- Fix the biggest bottleneck first (Amdahl’s law)
- Measure before and after every optimization
- Don’t optimize without profiling data — premature optimization wastes time