2

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?

Nfff3
  • 321
  • 8
  • 24
  • 5
    Unless this is test/debug code, a function whose behavior depends on who called it is a bad design. – user13784117 Aug 14 '20 at 12:26
  • @user13784117, indeed, this is for debug purposes. – Nfff3 Aug 14 '20 at 12:27
  • 1
    tl;dr: No, there's no efficient way to implement this, as many optimizations that the JVM does depend on the fact that method boundaries can be ignored in code flow. Explicitly checking for the caller breaks those optimizations and forces the JVM to execute de-optimized code (or code that's explicitly optimized with this in case, if it is clever enough for that). – Joachim Sauer Aug 14 '20 at 12:38
  • See [this answer](https://stackoverflow.com/a/26122232/3448419) – apangin Aug 14 '20 at 12:57
  • @apangin, yeah, I've seen that before asking the question, seems really nice and tested it, in my case it reduces the time by a factor of 5x which is great but I'd still like to reduce it even further(if it's possible) – Nfff3 Aug 14 '20 at 14:34

2 Answers2

8
  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.

apangin
  • 92,924
  • 10
  • 193
  • 247
-1

You can create am Exception and use fillInStacktrace(), then printStacktrace() and paste the result.

It's probably not terribly efficient, but I don't see why it needs to be if it's for debugging only.

Eg (I'm not at my computer so I haven't tried compiling it) :

try (StringWriter wr = new StringWriter(); 
     PrintWriter pw = new PrintWriter(wr)) {
    new Exception().fillInStacktrace().printStacktrace(pw);
    try (Scanner sc = new Scanner(wr.toString())) {
        int atFound = 0;
        while (sc.hasNextLine()) {
            String line = sc.nextLine();
            if (line.contains("at")) {
                atFound++;
            } 
            if (atFound == 2) {
                // this should be the caller, first one is this method
            } 
        } 
   } 
} 
daniu
  • 14,137
  • 4
  • 32
  • 53