JEP 259: Stack-Walking API | 堆栈遍历 API
摘要
定义一个高效的标准 API,用于堆栈跟踪,允许筛选堆栈跟踪中的信息并进行惰性访问。
非目标
- 本 JEP 的目标不是将 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
方法的使用情况,否则它们的性能开销是无法容忍的。这些用例包括:
遍历堆栈,直到找到即时调用者的类。每个 JDK 调用者敏感的 API 都会在其立即调用者的类中查找,以确定 API 的行为。例如,
Class::forName
和ResourceBundle::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
对象上执行安全权限检查,而不是每次使用该对象时执行。它将定义以下方法:
public <T> T walk(Function<Stream<StackFrame>, T> function);
public Class<?> getCallerClass();
“walk
” 方法为当前线程打开一个“StackFrame
”顺序流,然后应用具有“StackFrame
”流的函数。流的拆分器以有序方式执行堆栈帧遍历。一旦“walk
”方法返回,可以对“Stream<StackFrame>
”对象进行一次遍历,并且在关闭时会被关闭。流在关闭后变得无效。例如,要找到过滤已知实现类的第一个调用者:
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 的堆栈遍历逻辑的受控访问,用于旧帧。当此本机方法返回时,该能力必须被停用,否则以某种其他方式使其无法访问。通过这种方式,我们可以对线程自身控制堆栈的稳定视图进行有效的惰性访问。