Search code examples
javaperformanceexceptionstack-framethrowable

Efficient way to get caller method in Java 8?


This is what I'd like to achieve: if there is a method a() which calls method b(), I'd like to know who called method b().

public void a(){
  b();//but it is not necessarily in the same class
}

public void b(){
  String method = getCallerMethod();//returns 'a'
}

Now, this can be achieved efficiently in Java 9+ using the StackWalker API. In Java 8, I can use Thread.currentThread().getStackTrace() or new Exception().getStackTrace(), but both these methods are really slow. I do not need the whole stacktrace, I just need the previous frame in the stacktrace, and only I need the method's name in that frame (and possibly the class name).

Is there a way to achieve this efficiently in Java 8?


Solution

    1. In JDK 8 there is internal undocumented API that provides access to individual stack trace elements without decoding the full stack trace:

      SharedSecrets.getJavaLangAccess().getStackTraceDepth(e)
      SharedSecrets.getJavaLangAccess().getStackTraceElement(e, index)
      

      It helps to avoid large costs of decoding stack trace, but still requires collecting the whole trace. See this answer for details.

    2. Even faster way is JVM TI GetStackTrace function. Its start_depth and max_frame_count arguments allow to get only selected part of the stack trace.

      The drawback of this approach is that it requires a native library.

      I have an example of using GetStackTrace which does almost what you need: StackFrame.getLocation(depth) method returns just one stack frame at the given depth.

    3. In cases when only the caller class is required (without the exact method), the fast, standard and portable solution is

      MethodHandles.lookup().lookupClass()
      
    4. Finally, if you need only the caller method, an alternative solution would be to use Bytecode Instrumentation to find all invoke* bytecodes that call method a, and rewrite them to invoke method a_with_caller(String callerMethod) where callerMethod argument is an instrumentation time constant, derived from the method being instrumented.