Choosing the right asynchronous profiler arguments is essential if you want your JFR actually to contain information relevant to your performance problem. Wrong arguments may lead to useless data or additional overhead.
In our previous guide, Java Flight Recorder (JFR): Complete Guide, we discussed in detail how JFR conducts its built-in method sampling, but we want to note that it is biased toward safe points, meaning that the profiler will capture a thread only if it is at a safe point, which might lead to a certain inaccuracy in hotspot detection. Async profilers can overcome this bias, as AsyncGetCallTrace can capture a thread’s stack trace at any moment, asynchronous to execution, allowing it to bypass the need for checkpointing. And this is the main reason why asynchronous profiling is primarily used on top of JFR rather than as a stand-alone profiler, which is why in this guide we will mainly concentrate on the arguments that can be used to specify exactly what should be recorded, from CPU hotspots to memory allocations and lock contention, and how it can be turned into the JFR output.
async-profiler is a compact profiler for the JVM. The basic command format is as follows:
asprof [options] <pid>
Below are the most commonly used arguments, what they do, a brief example, and an example of how they work in a real JVM.
1. async-profiler -e <event>: What to Profile
Selects the profiling dimension. The main events:
- cpu: Samples threads running on the CPU. Answers the question “where is my CPU time going”?
- wall: Samples all threads, including those that are blocked, waiting, or parked. Best for troubleshooting latency and I/O related performance problems.
- alloc: Samples allocations in Java heaps. Answers the question “what is causing GC overhead”?
- lock: Samples contended locks (synchronized and java.util.concurrent ones).
- nativemem: Samples native memory allocations (malloc/free), use with –nofree to track down memory leaks.
- itimer/ctimer: Alternatives to perf_events if it is limited, like in the case of using Docker containers.
Running async-profiler without understanding what the event types are is like blind shooting; choosing the wrong one may lead to either getting irrelevant information or even performance overhead in your production software.
There are five types of events you can track by using the -e switch:
| Event | Primary Target | Ideal Scenario | Overhead Impact |
| cpu | On-CPU thread execution | High CPU utilization & hotspot detection | Extremely Low (~1-2%) |
| wall | On-CPU + Off-CPU (Blocked/Waiting) | Latency spikes, I/O bottlenecks, lock waits | Low-Medium (depends on thread count) |
| alloc | Heap allocation pressure | Frequent GC pauses, memory churn | Low (threshold dependent) |
| lock | Contended monitors/locks | Thread contention, blocked request threads | Very Low |
| nativemem | C-heap allocations (malloc/free) | Off-heap leaks, DirectByteBuffer issues | Medium |
Pro Tip: Never run wall mode without filtering specific thread pools (–filter) on high-thread JVMs. Sampling thousands of idle threads simultaneously will flood your output file and distort your latency diagnostics.
Events can be used together in one capture:
asprof -e cpu,alloc,lock -d 60 -f profile.jfr 12345
2. async-profiler -d <seconds>: Profiling Duration
How long to profile before stopping and writing the file.
asprof -e cpu -d 300 -f profile.jfr 12345
3. async-profiler -f <filename>: JFR Output File
Where to write the result. The .jfr extension is used to specify that a Java Flight Recorder file should be created, which can be opened in JDK Mission Control. %t will be replaced with the current time, and %p with the PID of the current process.
asprof -e cpu -d 60 -f /tmp/profile-%t.jfr 12345
4. async-profiler -i <interval>: Sampling Interval
How often CPU or wall sampling happens. The default is roughly 10 ms. Smaller values mean more detail but higher overhead.
asprof -e cpu -i 5ms -d 60 -f profile.jfr 12345
5. –alloc <bytes>: Allocation Sampling Threshold
Takes roughly one allocation sample per this many bytes allocated. Larger values reduce overhead.
asprof -e alloc --alloc 512k -d 60 -f profile.jfr 12345
6. –lock <duration>: Lock Threshold
Records lock contention events that exceed the specified duration (e.g., –lock 10ms).
asprof -e lock --lock 10ms -d 60 -f profile.jfr 12345
7. -t: Per-Thread Profiling
Labels every sample with its thread, so threads can be analyzed separately in JMC.
asprof -e cpu -t -d 60 -f profile.jfr 12345
This is particularly useful when investigating thread behavior and pool-related bottlenecks, such as those discussed in our guide to troubleshooting thread leaks using JFR.
8. –jfrsync <config>: Merging JVM Events
Records a JFR profile along with async-profiler and merges JVM internal events, such as GC, CPU load, thread dumps, compilations, and safepoints, into a single file, profile, or a path to a custom .jfc file. By default, the -jfrsync option is off. You can not see any meaningful data in JMC UI (e.g., Thread Dumps, Garbage Collections) without it. Requires JDK11+
asprof -e cpu,alloc,lock --jfrsync profile -d 300 -f profile.jfr 12345
Putting It All Together with async-profiler and JFR
asprof -e cpu,alloc,lock \ -i 10ms \ --alloc 512k \ --lock 10ms \ -t \ --jfrsync profile \ -d 300 \ -f /tmp/profile-%t.jfr \ <pid>
Just one command, just one JFR file, and you will have CPU hot spots, allocation pressure, lock contention, and
JVM instrumentation, all-in-one chart using JDK Mission Control, with the usual overhead of 1-2%.
Rather than memorizing every CLI argument, use these battle-tested argument combinations based on the exact issue hitting your JVM:
Scenario A: Diagnosing High CPU Spikes and Hotspots with async-profiler
Capture execution samples across CPU cores while attaching JVM runtime context (GC, thread dumps) via JFR synchronization:
asprof -e cpu -i 10ms -t --jfrsync profile -d 60 -f /tmp/cpu-spike-%p-%t.jfr <PID>
- -i 10ms: Sets a 10-millisecond sampling interval, striking a balance between granular stack traces and minimal runtime overhead.
- –jfrsync profile: Forces async-profiler to merge JVM internal events (GC pauses, compiler events, safepoints) directly into the generated JFR file.
This approach is useful for identifying CPU-intensive methods and isolating hot code paths. For a deeper walkthrough, see our guide on analyzing and troubleshooting CPU spikes using JFR.
Scenario B: Tracking Down Garbage Collection Churn with async-profiler
Sample memory allocations in TLABs and outside TLABs without triggering stop-the-world pauses:
asprof -e alloc --alloc 512k -d 120 -f /tmp/alloc-churn-%t.jfr <PID>
For a broader look at memory leak causes and detection, see our guide to Java memory leak causes, detection, and fixes.
- –alloc 512k: Captures approximately one allocation sample every 512 KB allocated. Decreasing this value (e.g., 128k) yields finer detail but increases CPU overhead.
Practical Example: Profiling a Running JVM with async-profiler
But to see how this works in practice, let’s try to reproduce this scenario and see how async-profiler would behave. For this example, we’ve launched a simple Spring Boot app with an endpoint that does some intensive calculations when invoked. So after launching the app, we’ve used jcmd to locate its PID, which in this case was 979.
1. Running the Profiler
With the process identified, we used async-profiler 4.5 to profile the application for 60 seconds and generate a Java Flight Recorder file:
./asprof -e cpu -d 60 -f cpu-profile-final.jfr 979
This command uses three of the arguments described above:
- -e cpu selects CPU profiling.
- -d 60 limits the profiling session to 60 seconds.
- -f cpu-profile-final.jfr names the output JFR file.

Fig: async-profiler is running on the terminal of process ID 979, producing 60 seconds of CPU records.
2. Inspecting the Recording
After the session ended, I checked the contents of the records using the jfr command-line tool provided with the JDK:
jfr summary cpu-profile-final.jfr

Fig: jfr summary output showing 60 seconds of records starting at 16:28:41 UTC and containing 4,496 jdk.ExecutionSample events.
The summary shows that during the recorded 60 seconds there were 4496 jdk.ExecutionSample events captured. Those are the cpu stack traces sampled by async-profiler. This is a good amount of data to analyze and find hotspots in your application. Also note that async-profiler can record the data in standard JFR format. This allows you to open the records in any JFR compatible viewer including JDK Mission Control and yCrash.
3. Analyzing the Recording with yCrash
The same cpu-profile-final.jfr file was then uploaded to yCrash for analysis. The recording can be analyzed in yCrash JFRPlayer using AI-powered JFR analysis, helping transform raw profiling data into more accurate, actionable JVM behavior insights.
CPU Sampling and Hotspot Scope

Fig: yCrash method profiling report: CPU sampling rate, hotspot area and thread at a glance
This report paints a detailed picture:
- A total of 4,463 CPU samples were collected.
- Almost the same as the 4,496 raw jdk.ExecutionSample events listed in the jfr summary report, but slightly less.
This inconsistency is expected because yCrash removes a small number of samples whose symbols cannot be resolved during the stack frame resolution and merging process during import.
What’s interesting is the “average sample rate” display:
- The declared value is 0 Hz, but it differs greatly from reality.
- Actually more than 4,400 samples in 60 seconds → about 74 samples per second
- This appears to be a display rounding error. Instead of an exact measurement value, it should be considered a strange humorous “zero display”.
More practical information:
- The maximum utilization of CPU by Hotspot’s top-level method (displayed as “PerformanceTest…” in the UI) was 99.82%
- Within the thread panel, the sampling was taken from 11 different threads
- Among which the greatest CPU utilization belongs to the http-nio-8… group (the Tomcat’s embedded HTTP connector thread pool)
This panel allows us to see clearly that:
- The request processing thread was loaded almost constantly for the entire duration of the recording
- The CPU-intensive method of processing an endpoint was executed almost continuously as well
Host-Level CPU Utilization

Fig: The yCrash resource report: the CPU utilization on the host gradually increased from almost 0% to 12-13% during the time-frame under consideration.
The resource report differs significantly from the previous two panels. Although all 11 threads in the thread pool were used almost constantly during the period of recording, the CPU load on the host remained at only 12-13%. The fact is not surprising, since a single thread can reach almost 99.8% of CPU utilization constantly, although others are used rarely. In combination with the last report, the current one demonstrates the same situation from two different perspectives. Thus, despite the high intensity of one thread, most of the CPU cores were idle.
When comparing JVM threads to host resources, you may find that the JVM is utilizing 100% of a given core, while the host only reports utilization of around 12%.
- Host CPU (8 cores): [##………………] 12.5% Total
- Core 1 (Tomcat-1): [####################] 99.8% Saturated
- Cores 2-8: [………………..] Idle
Here is how to diagnose this Pattern in yCrash or JDK Mission Control :
- Calculate Thread Affinity: With 8 cores available on the host, a single thread doing unoptimized looping or synchronization would only be using one core (1/8 = 12.5%)
- Locate the Hotspot within the Method: Filter out the ExecutionSample events by the http-nio-8080-exec thread group; the root of the problem is in the PerformanceTestService.calculate() method.
- Identify the Root Cause: Given that the other 7 CPU cores are not used at all, it is not a matter of server capacity but rather incorrect parallelization or even an O(n2) algorithm in the calculate() method. It needs to be refactored to perform parallel stream processing or use async worker threads.
Additional async-profiler Arguments and Options
The arguments above cover the everyday workflow. Next, we briefly introduce the other features of the async profiler.
async-profiler Actions for Controlling a Recording
In addition to the fixed duration, you can also control the recording process manually:
asprof start [options] <pid> # Begin profilingasprof stop <pid> # Stop and write the output fileasprof dump <pid> # Write output without stoppingasprof resume <pid> # Continue a stopped session, keeping dataasprof status <pid> # Check if profiling is running and for how longasprof check -e <event> <pid> # Verify whether an event is supportedasprof list <pid> # List all events available on the systemasprof meminfo <pid> # Show the profiler's memory usage
async-profiler Output and Recording Control
- -o <format>: Forces the output format regardless of file extension: flat, traces, collapsed, flamegraph, tree, or jfr. These can be combined, e.g. -o flat, traces.
- –loop <time>: Continuous profiling: writes a new output file every period, using %t in the filename. Example: –loop 1h -f app-%t.jfr.
- –timeout <time>: Automatically stops a started recording after a duration or at a wall-clock time.
- –chunksize <N>/–chunktime <T>: Splits long JFR recordings into chunks by size or time.
async-profiler Sampling Options
- –wall <interval>: Adds wall-clock sampling alongside the CPU event, at its own coarser interval, so one recording shows both on-CPU and off-CPU time.
- –total: Weighs flame-graph frames by total value (bytes for alloc, nanoseconds for lock) instead of sample count.
- –live: For alloc profiling, keeps only objects still live at the end, highlighting heap growth rather than churn.
- –nofree: For nativemem, records only allocations that are never freed, a native leak detector.
async-profiler Stack-Trace Options
- -j <depth>: Maximum Java stack depth to record (default 2048).
- -s: Uses simple class names instead of fully qualified ones.
- -g: Prints method signatures, distinguishing overloads.
- -a: Annotates Java method names so Java frames are distinguishable from native ones.
- -l: Prepends library names to native symbols, e.g. libc.so: malloc.
- -n: Normalizes lambda and hidden-class names so equivalent frames aggregate properly.
- –cstack <mode>: Native stack unwinding: fp, dwarf, lbr, vm, or no. Try dwarf if native frames look truncated.
- –signal <num>: Uses a different signal for sampling if the application already uses SIGPROF.
- –clock <source>: JFR timestamp source: tsc (default) or monotonic.
async-profiler Filtering Options
- -I <pattern>/-X <pattern>: Includes or excludes stack traces by frame pattern, with * wildcards. Example: -I ‘com/mycompany/*’ -X ‘*Logger*’.
- –filter <threads>: Restricts wall-clock sampling to threads matching a name pattern, e.g. –filter ‘http-nio-*’.
- –begin <func>/–end <func>: Profiles only between entry to one native function and return from another.
- –ttsp (Time-to-safepoint profiling): records only what threads do between a safepoint request and the moment everyone stops. It’s the tool for diagnosing long safepoint pauses.
async-profiler Environment and Miscellaneous Options
- –fdtransfer: A helper for profiling in containers where the target process lacks perf permissions.
- –jfropts <opts>: Extra JFR options, e.g. mem to buffer in memory.
- -L <level>: Log level (debug, info, warn, error) for troubleshooting attach failures.
- -v/–version: Prints the profiler version.
- –title, –minwidth, –reverse, –inverted: Flame-graph appearance options: title text, hiding narrow frames, reversing aggregation, and icicle orientation.
Running asprof –help gives the authoritative list for the installed version.
Conclusion: Using async-profiler Arguments for JFR Profiling
This article is focused on async-profiler’s key concepts: event types, duration, output file name, sampling interval, allocation and lock thresholds, labels for individual threads, and merging with JFR recordings, all demonstrated in practice. Profiling was performed on my test application, which is a typical Spring Boot web service. The recording obtained was first analyzed using the jfr tool that comes with the JDK and then opened in yCrash to demonstrate how the same data can be interpreted from different angles. In particular, one thread used almost 100% CPU to serve requests, while the whole host utilization was within the 12-13% range. Considering both perspectives is critical to understanding what happens in applications under load.

Share your Thoughts!