Draft JEP: Efficient Stack Walking API

David M. Lloyd david.lloyd at redhat.com
Wed Jul 9 13:42:42 UTC 2014


Just want to say that I am also looking forward to progress on this.

On 07/09/2014 12:25 AM, Jeremy Manson wrote:
> Thanks for the response, Mandy.  I'm looking forward to seeing the final
> version.
>
> For CallerFinder, we use reflective goop to get at
> sun.misc.JavaLangAccess.getStackTraceElement.  It requires us to build a
> Throwable (with associated stacktrace), but not to generate all of those
> StackTraceElement[] strings.  This saves a lot of CPU cycles over something
> like Thread.getStackTrace(), but in this case, cheaper is better, and we'd
> definitely prefer a better solution.  Functions like
> Java_java_lang_Throwable_fillInStackTrace are still big cycle consumers.
>
> Jeremy
>
>
> On Mon, Jul 7, 2014 at 8:06 PM, Mandy Chung <mandy.chung at oracle.com> wrote:
>
>>   Hi Jeremy,
>>
>> Thanks for the feedback and the CallerFinder API you have.
>>
>>
>> On 7/7/2014 9:55 AM, Jeremy Manson wrote:
>>
>> Hey folks,
>>
>>   I don't know if Mandy's draft JEP has gotten any love,
>>
>>
>> The JEP process is in transition to 2.0 version.  Hope this JEP will come
>> out soon.
>>
>>
>>   but this is something that has (in the past) been a major CPU cycle
>> consumer for us, and we've had to invent / reinvent many wheels to fix it
>> internally, so we'd love to see a principled solution.
>>
>>   A couple of notes:
>>
>>   - A large percentage of the time, you just want to find one of:
>>    1) The direct caller of the method,
>>    2) The first caller outside a given package.
>>
>>
>> The current thinking is to allow you to find the direct caller as well as
>> express the predicate for filtering that will cover these cases.
>>
>>
>>   We added a CallerFinder API that basically looks like this:
>>
>>   // Finds the caller of the invoking method in the current stack that
>> isn't in one of the excluded classes
>>   public static StackTraceElement findCaller(Class<?>... excludedClasses);
>>
>>   // Finds the first caller of a given class
>> public static StackTraceElement findCallerOf(Class<?>... classesToFind);
>>
>>   This isn't the ideal API (it is more the one that happened to be
>> convenient when we threw together the class), but it gets the vast majority
>> of use cases.
>>
>>
>> Does it use Thread.getStackTrace() to implement this CallerFinder API?
>> Thread.getStackTrace or Throwable.getStackTrace both eagerly capture the
>> entire stack trace that is expensive.  We want to have the VM to be able to
>> only capture the stack frames that the client needs and the implementation
>> as efficient as possible.
>>
>>
>>   2) Even with a super-efficient stack walker, anyone who uses the
>> java.util.logging framework pervasively is going to see a lot of CPU cycles
>> consumed by determining the caller.
>>
>>
>> The current LogRecord implementation calls new Throwable that has to pay
>> the cost of capturing the entire stack.
>>
>>
>>    We've had a lot of luck minimizing this by using a bytecode rewriter to
>> change callers of log(msg) to log(sourceClass, sourceMethod, msg).  This is
>> almost certainly something that could be done (even in a principled way!)
>> by the VM; improvements to CPU usage in such apps have been dramatic.
>>
>>
>> Thanks.  I'll make sure to measure and compare the performance with
>> java.util.logging using the new stack walk API and also may ask your help
>> to determine if you observe the performance difference comparing the
>> rewritten bytecode vs the java.util.logging using the new API.
>>
>> Mandy
>>
>>
>>   Jeremy
>>
>>
>>
>> On Sun, Mar 30, 2014 at 4:02 PM, Mandy Chung <mandy.chung at oracle.com>
>> wrote:
>>
>>> Below is a draft JEP we are considering submitting for JDK 9.
>>>
>>> Mandy
>>>
>>> ----------------------------
>>> Title: Efficient API for Stack Walking
>>>
>>> Goal
>>> ----
>>>
>>> Define a standard API for stack walking that will be efficient and
>>> performant.
>>>
>>> Non-goal
>>> --------
>>>
>>> It is not a goal for this API be easy to use via Reflection for example
>>> use in code that is compiled for an older JDK.
>>>
>>> Motivation
>>> ----------
>>>
>>> There is no standard API to obtain information about the caller's class
>>> and traverse the execution stack in a performant way.  Existing libraries
>>> and frameworks such as Log4j and Groovy have to resort to using the
>>> JDK internal API `sun.reflect.Reflection.getCallerClass(int depth)`.
>>>
>>> This JEP proposes to define a standard API for stack walking that will
>>> be efficient and performant and also enable the implementation up
>>> level the stack walk machinery from the VM to Java and replaces
>>> the current mechanism of `Throwable.fillInStackTrace.
>>>
>>> Description
>>> -----------
>>>
>>> There is no standard API to traverse certain frames on the execution
>>> stack efficiently and access the Class instance of each frame.
>>>
>>> There are APIs that allow to access the stack trace information:
>>>    - `Throwable.getStackTrace()` and `Thread.getStackTrace()` that returns
>>>       an array of `StackTraceElement` which contains the classname
>>>       and method name of a stack trace.
>>>    - `SecurityManager.getClassContext()` which is a protected method
>>>       such that only `SecurityManager` subclass can access the class
>>>       context.
>>>
>>> These APIs require the VM to eagerly capture a snapshot of the entire
>>> stack trace and returns the information representing the entire stack.
>>> There is no other way to avoid the cost to examine all frames if
>>> the caller is only interested in the top few frames on the stack.
>>> Both `Throwable.getStackTrace()` and `Thread.getStackTrace()` methods
>>> return an array of `StackTraceElement` that contains the classname and
>>> method name of a stack frame but the `Class` instance.
>>>
>>> In fact, for applications interested in the entire stack, the
>>> specification
>>> allows VM implementation to omit some frames in the stack for performance.
>>> In other words, `Thread.getStackTrace()` may return a partial stack trace.
>>>
>>> These APIs do not satisfy the use cases that currently depend on
>>> the `getCallerClass(int depth)` method or its performance overhead
>>> is intolerable.  The use cases include:
>>>
>>>    - JDK caller-sensitive APIs look up its immediate caller's class
>>>      which will be used to determine the behavior of the API.  For example
>>>      `Class.forName(String classname)` and
>>>      `ResourceBundle.getBundle(String rbname)` methods use the immediate
>>>      caller's class loader to load a class and a resource bundle
>>> respectively.
>>>      `Class.getMethod` etc will use the immediate caller's class loader
>>>      to determine the security checks to be performed.
>>>
>>>    - `java.util.logging`, Log4j and Groovy runtime filter the intermediary
>>>      stack frames (typically implementation-specific and reflection frames)
>>>      and find the caller's class to be used by the runtime of such library
>>>      or framework.
>>>
>>>    - Traverse the entire stack trace or the stack trace of a `Throwbale`
>>>      and obtain additional information about classes for enhanced
>>>      diagnosibility in addition to the class and method name.
>>>
>>> This JEP will define a stack walk API that allows laziness, frame
>>> filtering,
>>> supports short reaches to stop at a frame matching some criteria
>>> as well as long reaches to traverse the entire stack trace.  This would
>>> need the JVM to provide a flexible mechanism to traverse and materialize
>>> the specific stack frame information to be used and allow efficient
>>> lazy access to additional stack frames when required.
>>> Native JVM transitions should be minimzed.
>>>
>>> The API will define how it works when running with a security manager
>>> that allows access to a `Class` instance
>>> of any frame ensuring that the security is not compromised.
>>>
>>> An example API to walk the stack can be like:
>>>     Thread.walkStack(Consumer<StackFrameInfo> action, int depthLimit)
>>>
>>> that takes a callback to be invoked for each frame traversed.  A variant
>>> of the walkStack method will take a predicate for stack frame filtering.
>>>
>>>     Thread.getCaller(Function<StackFrameInfo, R> function)
>>>     Thread.findCaller(Predicate<StackFrameInfo> predicate,
>>>                       Function<StackFrameInfo, R> function)
>>>
>>> finds the caller frame with or without filtering.
>>>
>>> Testing
>>> -------
>>>
>>> Unit tests and JCK tests for the new SE API will need to be developed.
>>> In addition, the performance of the new API for different use cases
>>> will be assessed.
>>>
>>>
>>> Impact
>>> ------
>>>
>>>    - Performance/scalability: performance measurement shall be performed
>>>      using micro-benchmarks as well as real world usage of `getCallerClass`
>>>      replaced with the new API.
>>>
>>>    - TCK: New JCK test cases shall be developed.
>>>
>>>
>>
>>

-- 
- DML



More information about the core-libs-dev mailing list