跳到主要内容

JEP 259:堆栈行走 API

概括

为堆栈遍历定义一个高效的标准 API,允许轻松过滤和延迟访问堆栈跟踪中的信息。

非目标

  • 目标并不是将 JDK 中所有现有的堆栈遍历代码转换为使用这个新 API。

动机

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

现有的 API 可以提供对线程堆栈的访问:

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

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

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

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

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

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

  • 遍历堆栈以查找所有保护域,直到到达第一个特权帧。这是进行权限检查所必需的。

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

描述

该 JEP 将定义一个堆栈遍历 API,该 API 允许惰性和帧过滤,支持在符合给定条件的帧处停止的短程遍历,并且还支持遍历整个堆栈的长程遍历。

JVM 将得到增强,以提供灵活的机制来遍历和具体化所需的堆栈帧信息,并允许在需要时有效地延迟访问其他堆栈帧。本机 JVM 转换将被最小化。该实现需要有一个稳定的线程堆栈视图:返回一个包含堆栈指针的流以不受控制的方式进行进一步操作是行不通的,因为一旦流工厂返回,JVM 将可以自由地重新组织控制堆栈(例如,通过去优化)。这将影响 API 的定义。

API 将指定与安全管理器一起运行时的行为,以便访问Class堆栈帧中的对象不会损害安全性。

该提案是定义一个基于功能的StackWalkerAPI 来遍历堆栈。安全权限检查将在每个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方法 return Stream<StackFrame>。这种替代方案将不起作用,因为返回的流对象可能会以不受控制的方式用于进一步操作。创建堆栈帧流时,一旦流工厂返回,JVM 就可以自由地重新组织控制堆栈(例如,通过去优化),并且没有可靠的方法来检测堆栈是否已发生变化。

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