2016-10-25 4 views
2

Я работаю над аспектом аспекта, который должен знать, откуда он вызывается. В настоящий момент я используюБыстрый доступ к информации о вызывающем абоненте

new Throwable().getStackTrace(); 

для доступа к этой информации, но каждый аспект занимает несколько сотен микросекунд.

Я посмотрел на SecurityManager, но, похоже, мне удалось получить имя класса.

Есть ли другие альтернативы, которые я пропустил?

Update

результаты JMH Benchmark, упомянутые в моем комментарии на @ apangin отвечают:

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 код:

@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); 
} 

тесты работают под управлением Windows 10, JDK 1.8. 0_112 на Dell XPS13 9343 (i5-5200U при частоте 2,2 ГГц)

+1

Очевидный вопрос: зачем вам это нужно. Получение трассировки стека занимает довольно много времени по разным причинам, поэтому лучше всего найти альтернативное решение вашей исходной проблемы, которое не связано с чтением по трассе стека. – biziclop

+2

Получение информации о вызывающем абоненте происходит медленно. Например, каждая система ведения журнала имеет эту проблему. И все они советуют вам не включать такую ​​информацию, если вам нужна производительность. – zapl

+0

Как говорится в других комментариях: то, что вы пропустите, состоит в том, что у вас есть противоречащие требованиям, которые вам нужно как-то разрешить; так как вы не найдете разумного и надежного способа выполнить их все. – GhostCat

ответ

5

К сожалению, Throwable.getStackTrace(), по-видимому, является единственным жизнеспособным вариантом для получения кадра вызывающего абонента в чистой Java 8.

Однако существует конкретный трюк JDK для доступа только к одному выбранному фрейму стека.
Используется нестандартный API sun.misc.SharedSecrets.

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

Здесь 2 - индекс требуемого кадра.

Это прекрасно работает до последнего JDK 8, но частный API не будет доступен в JDK 9. Хорошей новостью является то, что Java 9 будет иметь новый стандарт Stack-Walking API. Вот как сделать то же самое в Java 9.

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

Альтернативный вариант, который хорошо работает для старых и новых версий Java, является JVMTI GetStackTrace функция. Однако для этого требуется привязка собственного кода.

+0

Большое спасибо за этот очень полный ответ. Я провел несколько тестов с вашими предложениями через JMH, и кажется, что Throwable.getStackTrace() занимает около 13us, тогда как ваш предлагаемый SharedSecrets api занимает 2 ~ 3us, в зависимости от доступного элемента. Я собирался тестировать Java 9 api, а также для полноты, но для его работы требуются изменения инструментальных средств для Eclipse, и я нервничаю из-за этого. Но эталон https://github.com/pingtimeout/stack-walker-benchmark предлагает его медленнее. – Ian

+0

Похоже, что SharedSecrets api может быть более изменчивым по продолжительности, чем указано в моем первоначальном тестировании. Я вижу значения до 20us (и один outlier over 600us) для суммы двух вызовов, как только я помещаю их в свое приложение. – Ian

+0

Думаю, я нашел гораздо лучшее решение на основе AspectJ. – kriegaex

1

Вы говорите об AspectJ. Так что вам не нужно никакого отражения, но может использовать только на борту AspectJ средства, такие как thisEnclosingJoinPointStaticPart.getSignature() в сочетании с call() срезом точек:

приложение Driver:

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() {} 
} 

Формат:

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); 
    } 
} 

Консоль:

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

Как вы можете видеть, AspectJ примерно в 140 раз быстрее, чем следующий лучший метод на основе отражения.

Кстати, если вы раскомментировать операторы печати, в аспекте, вы видите эти три вида продукции:

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) 

Наслаждайтесь!

+0

+1 Хороший вопрос о встроенной функции AspectJ! К сожалению, это имеет серьезные ограничения. Предоставляется только статическое местоположение непосредственного вызывающего абонента. Иногда вам нужно смотреть на несколько кадров глубже (например, когда наблюдаемый метод вызывается из метода утилиты). Он также не учитывает рефлексивные вызовы и вызовы посредством ссылок на методы. – apangin

+0

Ну, вы не упомянули об этих ограничениях. В нормальных случаях мое решение просто невероятно быстро. Во всяком случае, это было всего лишь упражнение пальца для меня. Интересно, зачем вам вообще нужна информация о вызывающем абоненте. Где стоимость бизнеса? – kriegaex

+0

Я не спрашиваю оригинального вопроса :) Может быть, OP будет абсолютно в порядке с вашим решением. Раньше у меня была аналогичная проблема, и в моем случае интересный вызывающий абонент был около 4 кадров ниже. JVMTI оказался самым быстрым способом получить его. – apangin

Смежные вопросы