简体   繁体   中英

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)

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.

You are talking about AspectJ. So you do not need any reflection but can just use on-board AspectJ means such as thisEnclosingJoinPointStaticPart.getSignature() in combination with a call() pointcut:

Driver application:

package de.scrum_master.app;

public class Application {
    private static final long NUM_LOOPS = 1000 * 1000;

    public static void main(String[] args) {
        Application application = new Application();

        long startTime = System.nanoTime();
        for (long i = 0; i < NUM_LOOPS; i++)
            application.doSomething();
        System.out.printf(
            "%-40s  |  %8.3f ms%n",
            "AspectJ thisEnclosingJoinPointStaticPart",
            (System.nanoTime() - startTime) / 1.0e6
        );

        startTime = System.nanoTime();
        for (long i = 0; i < NUM_LOOPS; i++)
            application.doSomething2();
        System.out.printf(
            "%-40s  |  %8.3f ms%n",
            "Throwable.getStackTrace",
            (System.nanoTime() - startTime) / 1.0e6
        );

        startTime = System.nanoTime();
        for (long i = 0; i < NUM_LOOPS; i++)
            application.doSomething3();
        System.out.printf(
            "%-40s  |  %8.3f ms%n",
            "SharedSecrets.getJavaLangAccess",
            (System.nanoTime() - startTime) / 1.0e6
        );
    }

    public void doSomething() {}
    public void doSomething2() {}
    public void doSomething3() {}
}

Aspect:

package de.scrum_master.aspect;

import de.scrum_master.app.Application;
import sun.misc.SharedSecrets;

public aspect MyAspect {
    before() : call(* Application.doSomething()) {
        Object o = thisEnclosingJoinPointStaticPart.getSignature();
        //System.out.println(o);
    }

    before() : call(* Application.doSomething2()) {
        Object o = new Throwable().getStackTrace()[1];
        //System.out.println(o);
    }

    before() : call(* Application.doSomething3()) {
        Object o = SharedSecrets.getJavaLangAccess().getStackTraceElement(new Throwable(), 1);
        //System.out.println(o);
    }
}

Console log:

AspectJ thisEnclosingJoinPointStaticPart  |     7,246 ms
Throwable.getStackTrace                   |  1852,895 ms
SharedSecrets.getJavaLangAccess           |  1043,050 ms

As you can see, AspectJ is about 140x faster than the next best reflection-based method.

BTW, if you uncomment the print statements in the aspect, you see these three types of output:

void de.scrum_master.app.Application.main(String[])
de.scrum_master.app.Application.main(Application.java:16)
de.scrum_master.app.Application.main(Application.java:21)

Enjoy!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM