JEP draft: JEP Draft: Unbiased Stack-Walk JFR event trigger

OwnerRoman Kennke
TypeFeature
ScopeJDK
StatusDraft
Componenthotspot / jvmti
EffortM
Created2026/03/17 12:49
Updated2026/03/30 13:25
Issue8380294

Summary

Provide an API for use by external tools to request and receive unbiased (aka asynchronous) stack-traces.

Goals

Non-Goals

Motivation

One of the great advantages of Java and the JVM is its vast ecosystem of tools that make the lives of developers easier. One such category of tools are profilers. The JVM comes with built-in facilities for profiling (JFR), and there are various external tools that provide a wider and sometimes more useful set of features both open source (e.g., async-profiler [0]) and commercial. One key feature that profiling solutions require from the JVM is the ability to obtain stack-traces. This allows profiling tools to tell the user exactly where potential performance bottlenecks or various other problems (e.g., cache-misses) may be located in their code, and how the execution of the program got there.

There are currently several ways for external profiling tools to obtain stack-traces, and all have drawbacks which make them an insufficient solution:

An example of what is possible using the new API is shown below. This is a flame-graph that represents ~27000 samples of cache-misses obtained using a small profiling agent running with one of the Renaissance benchmark workloads. The profiling agent obtains the samples by setting up a signal handler on Linux perf counter overflow on the hardware cache-misses counter, and requesting stack-traces whenever that signal fires.

Flame-graph showing cache misses in a Renaissance workload

Description

A new API is added as a JVMTI extension. Calling that API requests a stack-trace from the current thread to be reported via JFR by emitting an StackTraceRequest event. The API has the following signature:

jvmtiError RequestStackTrace(jvmtiEnv* env, jthread* thread, void* ucontext, jlong user_data)

Where the method arguments are:

The function returns an error code:

After calling the API, JFR will emit an event that is specified as follows:

The functionality needs to be enabled before use by calling the following function:

jvmtiError EnableRequestStackTrace(jvmtiEnv* env)

This will typically be called from JVMTI's Agent_OnLoad to globally enable the functionality. However, it is also possible to call the function later. Notice that in order to use the functionality, one would also have to start a JFR recording.

The functionality can be disabled by calling the following function:

jvmtiError DisablesRequestStackTrace(jvmtiEnv* env)

This could be called from JVMTI's Agent_OnUnload or at any earlier time to disable the functionality.

Implementation

Much of the functionality that is required for this feature has already been implemented for JEP 509: JFR CPU-Time Profiling (Experimental). The mechanism for asynchronous stack-walking that has been implemented for the CPU-time sampler is generalized and re-used for the Unbiased Stack-Walk API.

In short, the stack-walker works as follows:

  1. The signal-handler (or any other trigger) calls into RequestStackTrace.
  2. RequestStackTrace records the thread's current PC, BCI and SP, and places a stack-walk-request with that information on a queue.
  3. It then arms the thread for safepoint-polling (aka handshaking).
  4. As soon as the thread arrives at the next safepoint-poll, it stops and starts processing all enqueued requests.
  5. For each request, the stack-walker fetches the PC, BCI and SP, and reconstructs the top frame information from that. Notice that we only need to reconstruct the top frame (plus possibly inlined frames), but never any frames below that, because method returns would always run into a safepoint poll.
  6. Once we have the top-frame, the thread walks the stack down by the usual mechanisms.

Alternatives

The following alternatives have been considered:

jvmtiError RequestStackTrace(jvmtiEnv* env, jthread thread, void* ucontext, jvmtiStackTraceCallbacks* cb, void* user_data)

This would actually have been the author's preferred approach, because it avoids the coupling to JFR and gives the JVMTI agent more freedom in how it handles the stack-trace, while also providing signal-safety and no safepoint-bias. However, it has (so far) been rejected on the grounds that no new functionality is currently wanted in JVMTI.

Testing

Risks and Assumptions