Flame Graphs

Flame graphs visualize sampled call stacks so wide regions indicate code paths consuming substantial execution time. They help Java backend engineers identify where CPU time is concentrated and which call paths deserve optimization.

A flame graph is a visualization of stack samples collected by a profiler. Each rectangle represents a method or stack frame, and frames are arranged from callers at the bottom to callees at the top. The horizontal axis represents the aggregated samples rather than a timeline.

The width of a frame indicates how often that frame appears in the collected samples, so a wide frame represents substantial observed execution time. The vertical position shows call-stack depth. A wide frame is therefore a useful optimization target, while a tall stack is not automatically problematic.

For Java applications, profilers can obtain stack samples from running threads and aggregate them by call path. Common examples include async-profiler, Java Flight Recorder-based tooling, and other JVM profiling tools. Sampling generally has lower overhead than instrumenting every method call, although it provides statistical rather than exact timing information.

To use a flame graph, first identify the relevant workload and profiling period, then look for broad regions associated with expensive operations. Drill down into their callers and callees to understand why the time is being spent there. After making an optimization, profile again under comparable conditions rather than assuming that a code change improved performance.

Key points

Interview questions

Why can a flame graph be useful for performance debugging?

It condenses many stack samples into a visual representation of where execution time is concentrated. This makes hot call paths easier to spot than scanning large amounts of profiler output. It also preserves caller-to-callee relationships, which helps explain how an expensive operation was reached. Because the data is sampled, the graph should be interpreted as statistical evidence rather than exact accounting.

Does a tall stack in a flame graph mean the application is slow?

No. Height primarily indicates call-stack depth. A stack can be very deep while consuming relatively little CPU, producing a tall but narrow region. Conversely, a shallow frame can be very wide and represent a major CPU hotspot. The key signal in a typical CPU flame graph is the amount of horizontal width associated with the relevant path.

How would you use a flame graph to optimize a slow Java service?

First I would reproduce the problematic workload and establish a baseline profile. I would inspect CPU flame graphs for dominant call paths, then determine whether the hotspot comes from application logic, serialization, synchronization, I/O-related CPU work, or framework/runtime activity. I would make a targeted change based on that evidence and re-profile under comparable conditions. I would also check throughput, latency, allocation behavior, and correctness because reducing one hotspot does not necessarily improve the overall service.

What does the width of a frame in a flame graph mean?

The width represents the number of aggregated samples containing that frame. A wider frame means the profiler observed that method on the stack more frequently. In a CPU flame graph, this generally indicates a larger share of observed CPU execution. It does not mean the method's source-code size or individual invocation duration.

How do you read a Java CPU flame graph?

Start at the bottom, where the graph shows the entry points or thread roots, and move upward through callers to callees. Look for wide regions because they account for many samples. Then inspect the complete path to determine which operation is responsible for the observed CPU consumption. After changing the code, repeat the profiling run under a comparable workload.