Ian Ian - 1 month ago 7
Java Question

Accessing caller information quickly

I'm working on an aspectj aspect which needs to know where it's invoked from. At the moment I'm using

new Throwable().getStackTrace();


to access this information but each aspect is taking several hundred microseconds to run.

I've looked at the SecurityManager but that only seems to be able to get me the class name.

Are there any other alternatives I've missed?

Update

JMH Benchmark results referred to in my comment on @apangin's answer:

Benchmark Mode Cnt Score Error Units
MyBenchmark.javalangaccess13i avgt 100 2025.865 ± 8.133 ns/op
MyBenchmark.javalangaccess2i avgt 100 2648.598 ± 24.369 ns/op
MyBenchmark.throwable1 avgt 100 12706.978 ± 84.651 ns/op


Benchmark code:

@Benchmark
public StackTraceElement[] throwable1() {
return new Throwable().getStackTrace();
}

@SuppressWarnings("restriction")
@Benchmark
public static StackTraceElement javalangaccess2i() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 2);
}

@SuppressWarnings("restriction")
@Benchmark
public static StackTraceElement javalangaccess13i() {
Exception e = new Exception();
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 13);
}


Tests run under Windows 10, JDK 1.8.0_112 on a Dell XPS13 9343 (i5-5200U @ 2.2GHz)

Answer

Unfortunately, Throwable.getStackTrace() seems to be the only viable option to get the caller frame in pure Java 8.

However, there is a JDK-specific trick to access just one selected stack frame.
It uses non-standard sun.misc.SharedSecrets API.

public static StackTraceElement getCaller() {
    Exception e = new Exception();
    return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(e, 2);
}

Here 2 is the index of the required frame.

This works fine until the latest JDK 8, but private API will not be accessible in JDK 9. A good news is that Java 9 will have new standard Stack-Walking API. Here is how to do the same in Java 9.

public static StackWalker.StackFrame getCaller() {
    return StackWalker.getInstance(Collections.emptySet(), 3)
            .walk(s -> s.skip(2).findFirst())
            .orElse(null);
}

The alternative option, that works well for both older and newer versions of Java, is JVMTI GetStackTrace function. It requires linking native code though.

Comments