Skip to content
欢迎扫码关注公众号

JEP 259: Stack-Walking API | 堆栈遍历 API

摘要

定义一个高效的标准 API,用于堆栈跟踪,允许筛选堆栈跟踪中的信息并进行惰性访问。

非目标

  • 本 JEP 的目标不是将 JDK 中所有现有的堆栈跟踪代码转换为使用此新 API。

动机

没有标准 API 可以有效地遍历执行栈中选定的帧,并访问每个帧的 Class 实例。

现有 API 提供了访问线程堆栈的功能:

  • Throwable::getStackTraceThread::getStackTrace 返回一个 StackTraceElement 对象数组,其中包含每个堆栈跟踪元素的类名和方法名。

  • SecurityManager::getClassContext 是一个受保护的方法,允许 SecurityManager 子类访问类上下文。

这些 API 需要 VM 急切地捕获整个堆栈的快照,并返回表示整个堆栈的信息。如果调用者仅对堆栈顶部的几个帧感兴趣,则无法避免检查所有帧的成本。 Throwable::getStackTraceThread::getStackTrace 方法都返回一个 StackTraceElement 对象的数组,其中包含类名和方法名,但不包含实际的 Class 实例。对于对整个堆栈感兴趣的应用程序,规范允许 VM 实现省略堆栈中的一些帧以提高性能。换句话说,Thread::getStackTrace 可能返回部分堆栈跟踪。

这些 API 不满足那些当前依赖于 JDK 内部 sun.reflect.Reflection::getCallerClass 方法的使用情况,否则它们的性能开销是无法容忍的。这些用例包括:

  • 遍历堆栈,直到找到即时调用者的类。每个 JDK 调用者敏感的 API 都会在其立即调用者的类中查找,以确定 API 的行为。例如,Class::forNameResourceBundle::getBundle 方法使用立即调用者的类加载器分别加载类和资源包。反射 API,例如 Class::getMethod 使用立即调用者的类加载器来确定要执行的安全检查。

  • 遍历堆栈,过滤特定实现类的堆栈帧,以找到第一个非过滤帧。 java.util.logging API、Log4j 和 Groovy 运行时过滤中间堆栈帧(通常是实现特定和反射帧),以找到调用者的类。

  • 遍历整个堆栈,直到找到第一个特权帧为止,以查找所有保护域。这是为了进行权限检查。

  • 遍历整个堆栈,可能有深度限制。这是生成任何 Throwable 对象的堆栈跟踪和实现 Thread::dumpStack 方法所需的。

描述

此 JEP 将定义一个堆栈遍历 API,允许惰性和帧过滤,支持停止在匹配给定条件的框架的短程行走,并支持遍历整个堆栈的长程行走。

将增强 JVM 以提供灵活的机制来遍历和实现所需的堆栈帧信息,并在需要时允许有效的惰性访问附加堆栈帧。将最小化本地 JVM 转换。实现将需要线程的堆栈的稳定视图:返回一个保持堆栈指针以进行进一步操作的流无法工作,因为一旦流工厂返回,JVM 将可以重新组织控制堆栈(例如,通过取消优化)。这将影响 API 的定义。

API 将指定其在使用安全管理器时的行为,以便访问堆栈帧中的 Class 对象不会影响安全性。

建议是定义一个基于能力的 StackWalker API 来遍历堆栈。当构建 StackWalker 对象时,将在每个 StackWalker 对象上执行安全权限检查,而不是每次使用该对象时执行。它将定义以下方法:

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

walk” 方法为当前线程打开一个“StackFrame”顺序流,然后应用具有“StackFrame”流的函数。流的拆分器以有序方式执行堆栈帧遍历。一旦“walk”方法返回,可以对“Stream<StackFrame>”对象进行一次遍历,并且在关闭时会被关闭。流在关闭后变得无效。例如,要找到过滤已知实现类的第一个调用者:

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

要快照当前线程的堆栈跟踪,

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

getCallerClass() 方法是为了方便找到调用者的框架,并替换 sun.reflect.Reflection.getCallerClass。使用 walk 方法获取调用者类的等价方法是:

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

备选方案

替代的 API 选择是使“walk”方法返回“Stream<StackFrame>”。这样的替代方案将不起作用,因为返回的流对象可能以不受控制的方式用于进一步操作。当创建堆栈帧流时,一旦流工厂返回,JVM 就可以自由地重新组织控制堆栈(例如,通过取消优化),并且没有稳健的方法来检测堆栈是否已发生变异。

相反,类似于 AccessController::doPrivileged,必须创建至少一个本机方法,该方法将建立自己的堆栈帧,然后提供对 JVM 的堆栈遍历逻辑的受控访问,用于旧帧。当此本机方法返回时,该能力必须被停用,否则以某种其他方式使其无法访问。通过这种方式,我们可以对线程自身控制堆栈的稳定视图进行有效的惰性访问。

Page Layout Max Width

Adjust the exact value of the page width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the page layout
A ranged slider for user to choose and customize their desired width of the maximum width of the page layout can go.

Content Layout Max Width

Adjust the exact value of the document content width of VitePress layout to adapt to different reading needs and screens.

Adjust the maximum width of the content layout
A ranged slider for user to choose and customize their desired width of the maximum width of the content layout can go.