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