Tried to flamegraph a Java service in prod. perf record produced a flamegraph that was 95% [unknown]. Hours of my life later, I learned: JIT-compiled methods aren’t in the binary. perf has no way to resolve them without help.

Enter perf-map-agent (or its successor, async-profiler). The agent attaches to the JVM, dumps a symbol map of JIT methods to /tmp/perf-<pid>.map, and perf finds them automatically.

# attach perf-map-agent
java -cp perf-map-agent.jar net.virtualvoid.perf.AttachOnce $PID

# now record
sudo perf record -F 99 -p $PID --call-graph fp -o /tmp/perf.data -- sleep 30
sudo perf script -i /tmp/perf.data | ./FlameGraph/stackcollapse-perf.pl > out.folded
./FlameGraph/flamegraph.pl out.folded > flame.svg

The flamegraph is now mostly resolved Java methods instead of hex addresses.

These days I reach for async-profiler first because it integrates the whole pipeline in one tool. perf-map-agent is the older workflow but still works if you want the perf data for other reasons.

For Python and Ruby, there are similar tricks — py-spy and rbspy are the easy answers, as they understand the respective runtimes without needing symbol maps.

Lesson: when flamegraphs are mostly [unknown], you’re missing symbol info. Don’t despair, find the agent for your runtime.