openjdk.ruOpenJDK на русском

JEP 259: Stack-Walking API

API для обхода стека

ОтветственныйMandy Chung
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск9
Компонентcore-libs
Обсуждениеcore dash libs dash dev at openjdk dot java dot net
ТрудоёмкостьM
ДлительностьM
РецензентыBrian Goetz, Mark Reinhold
ОдобренBrian Goetz
Создан2014/05/22 22:05
Обновлён2017/07/18 15:06
Задача8043814

Аннотация

Определить эффективный стандартный API для обхода стека, который позволяет легко фильтровать информацию в трассировках стека и получать к ней ленивый доступ.

Что не является целью

  • Перевод всего существующего кода обхода стека в JDK на этот новый API не является целью.

Мотивация

Нет стандартного API, который позволял бы эффективно обходить выбранные кадры стека выполнения и получать экземпляр Class для каждого кадра.

Существуют API, которые дают доступ к стеку потока:

  • Throwable::getStackTrace и Thread::getStackTrace возвращают массив объектов StackTraceElement, которые содержат имя класса и имя метода для каждого элемента трассировки стека.

  • SecurityManager::getClassContext — защищённый метод, через который подкласс SecurityManager может получить контекст классов.

Эти API требуют, чтобы VM сразу делала снимок всего стека, и возвращают информацию обо всём стеке. Если вызывающему коду нужны только несколько верхних кадров стека, избежать затрат на просмотр всех кадров нельзя. Оба метода, Throwable::getStackTrace и Thread::getStackTrace, возвращают массив объектов StackTraceElement, которые содержат имена классов и методов, но не сами экземпляры Class. Для приложений, которым нужен весь стек, спецификация позволяет реализации VM ради производительности пропускать некоторые кадры стека. Иначе говоря, Thread::getStackTrace может вернуть неполную трассировку стека.

Эти API не подходят для сценариев, которые сейчас зависят от внутреннего метода JDK sun.reflect.Reflection::getCallerClass, или же их накладные расходы на производительность неприемлемы. К таким сценариям относятся:

  • Обход стека до тех пор, пока не будет найден класс непосредственно вызывающего кода. Каждый caller-sensitive API в JDK (API, поведение которого зависит от вызывающего кода) ищет класс непосредственно вызывающего кода, чтобы определить своё поведение. Например, методы Class::forName и ResourceBundle::getBundle используют загрузчик классов непосредственно вызывающего кода, чтобы загрузить соответственно класс и пакет ресурсов. Рефлексивные API, такие как Class::getMethod, используют загрузчик классов непосредственно вызывающего кода, чтобы определить, какие проверки безопасности нужно выполнить.

  • Обход стека с отсеиванием кадров определённых классов реализации, чтобы найти первый неотсеянный кадр. API java.util.logging, Log4j и среда выполнения Groovy отсеивают промежуточные кадры стека (обычно кадры, специфичные для реализации, и кадры рефлексии), чтобы найти класс вызывающего кода.

  • Обход стека для поиска всех доменов защиты, пока не встретится первый привилегированный кадр. Это нужно для проверки разрешений.

  • Обход всего стека, возможно с ограничением глубины. Это нужно, чтобы сформировать трассировку стека любого объекта Throwable и реализовать метод Thread::dumpStack.

Описание

Этот JEP определит API для обхода стека, который поддерживает ленивость и фильтрацию кадров, короткие обходы, останавливающиеся на кадре, удовлетворяющем заданным критериям, а также длинные обходы всего стека.

JVM будет доработана, чтобы предоставить гибкий механизм обхода стека и материализации нужной информации о кадрах стека, а также эффективный ленивый доступ к дополнительным кадрам стека, когда они нужны. Переходы в нативный код JVM будут сведены к минимуму. Реализации потребуется стабильное представление стека потока: возвращать поток данных, хранящий указатель стека, для дальнейших неконтролируемых операций не получится, поскольку, как только фабрика потока данных вернёт управление, JVM сможет перестроить стек управления (например, при деоптимизации). Это повлияет на определение API.

API будет определять своё поведение при работе с менеджером безопасности, чтобы доступ к объектам Class в кадрах стека не нарушал безопасность.

Предлагается определить API StackWalker на основе возможностей (capability-based) для обхода стека. Проверка разрешений безопасности будет выполняться для каждого объекта StackWalker при его создании, а не при каждом использовании. Он определит следующие методы:

public <T> T walk(Function<Stream<StackFrame>, T> function);
public Class<?> getCallerClass();

Метод walk открывает последовательный поток данных StackFrame для текущего потока и затем применяет функцию к потоку данных StackFrame. Сплитератор этого потока данных обходит кадры стека по порядку. Объект Stream<StackFrame> можно обойти только один раз, и он будет закрыт, когда метод walk вернёт управление. После закрытия поток данных использовать нельзя. Например, чтобы найти первый вызывающий кадр, отсеивая известный список классов реализации:

Optional<Class<?>> frame = new StackWalker().walk((s) ->
{
    s.filter(f -> interestingClasses.contains(f.getDeclaringClass()))
     .map(StackFrame::getDeclaringClass)
     .findFirst();
});

Чтобы сделать снимок трассировки стека текущего потока,

List<StackFrame> stack =
     new StackWalker().walk((s) -> s.collect(Collectors.toList()));

Метод getCallerClass() служит для удобного поиска кадра вызывающего кода и заменяет sun.reflect.Reflection.getCallerClass. Эквивалентный способ получить класс вызывающего кода с помощью метода walk:

walk((s) -> s.map(StackFrame::declaringClass).skip(2).findFirst());

Альтернативы

Альтернативный вариант API — чтобы метод walk возвращал Stream<StackFrame>. Такой вариант не сработает, поскольку возвращённый объект потока данных может использоваться для дальнейших операций неконтролируемым образом. Когда создаётся поток данных кадров стека, то, как только фабрика потока данных вернёт управление, JVM может перестроить стек управления (например, при деоптимизации), и надёжного способа определить, изменился ли стек, нет.

Вместо этого, как и в случае AccessController::doPrivileged, нужно создать как минимум один нативный метод, который создаст собственный кадр стека и затем предоставит контролируемый доступ к логике обхода стека JVM для более старых кадров. Когда этот нативный метод вернёт управление, эта возможность должна быть отключена или сделана недоступной каким-либо другим способом. Так мы получаем эффективный ленивый доступ к кадрам стека при стабильном представлении собственного стека управления потока.