1. Introduction
1.介绍
In this quick article, we will have a look at Java 9’s StackWalking API.
在这篇简短的文章中,我们将了解一下Java 9的StackWalking API。
The new functionality provides access to a Stream of StackFrames, allowing us to easily browse stack in both directly and making good use of the powerful Stream API in Java 8.
新功能提供了对StackFrames的Stream的访问,使我们能够轻松地直接浏览堆栈,并很好地利用了Java 8中强大的Stream API 。
2. Advantages of a StackWalker
2.StackWalker的优势
In Java 8, the Throwable::getStackTrace and Thread::getStackTrace returns an array of StackTraceElements. Without a lot of manual code, there was no way to discard the unwanted frames and keep only the ones we are interested in.
在Java 8中,Throwable::getStackTrace和Thread::getStackTrace返回一个StackTraceElement的数组。在没有大量手工代码的情况下,没有办法丢弃不需要的帧,只保留我们感兴趣的帧。
In addition to this, the Thread::getStackTrace may return a partial stack trace. This is because the specification allows the VM implementation to omit some stack frames for the sake of performance.
除此之外,Thread::getStackTrace可能会返回一个部分堆栈跟踪。这是因为规范允许虚拟机实现为了性能而省略一些堆栈帧。
In Java 9, using the walk() method of the StackWalker, we can traverse a few frames that we are interested in or the complete stack trace.
在Java 9中,使用StackWalker的walk()方法,我们可以遍历我们感兴趣的几个帧或整个堆栈跟踪。
Of course, the new functionality is thread-safe; this allows multiple threads to share a single StackWalker instance for accessing their respective stacks.
当然,新功能是线程安全的;这允许多个线程共享一个StackWalker实例来访问各自的堆栈。
As described in the JEP-259, the JVM will be enhanced to allow efficient lazy access to additional stack frames when required.
正如JEP-259中所描述的那样,JVM将得到增强,以便在需要时可以高效地懒惰地访问额外的堆栈帧。
3. StackWalker in Action
3.StackWalker在行动
Let’s start by creating a class containing a chain of method calls:
让我们从创建一个包含方法调用链的类开始。
public class StackWalkerDemo {
public void methodOne() {
this.methodTwo();
}
public void methodTwo() {
this.methodThree();
}
public void methodThree() {
// stack walking code
}
}
3.1. Capture the Entire Stack Trace
3.1.捕获整个堆栈跟踪
Let’s move ahead and add some stack walking code:
让我们继续前进,添加一些堆栈行走代码。
public void methodThree() {
List<StackFrame> stackTrace = StackWalker.getInstance()
.walk(this::walkExample);
}
The StackWalker::walk method accepts a functional reference, creates a Stream of StackFrames for the current thread, applies the function to the Stream, and closes the Stream.
StackWalker::walk方法接受一个函数引用,为当前线程创建一个StackFrames的Stream,对Stream施加函数,并关闭Stream。
Now let’s define the StackWalkerDemo::walkExample method:
现在我们来定义StackWalkerDemo::walkExample方法。
public List<StackFrame> walkExample(Stream<StackFrame> stackFrameStream) {
return stackFrameStream.collect(Collectors.toList());
}
This method simply collects the StackFrames and returns it as a List<StackFrame>. To test this example, please run a JUnit test:
这个方法只是收集了StackFrames,并以List<StackFrame>的形式返回。为了测试这个例子,请运行一个JUnit测试。
@Test
public void giveStalkWalker_whenWalkingTheStack_thenShowStackFrames() {
new StackWalkerDemo().methodOne();
}
The only reason to run it as a JUnit test is to have more frames in our stack:
将其作为JUnit测试运行的唯一原因是为了在我们的堆栈中拥有更多的框架。
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 20
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 15
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 11
class com.baeldung.java9.stackwalker
.StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
class org.junit.runners.model.FrameworkMethod$1#runReflectiveCall, Line 50
class org.junit.internal.runners.model.ReflectiveCallable#run, Line 12
...more org.junit frames...
class org.junit.runners.ParentRunner#run, Line 363
class org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference#run, Line 86
...more org.eclipse frames...
class org.eclipse.jdt.internal.junit.runner.RemoteTestRunner#main, Line 192
In the entire stack trace, we are only interested in top four frames. The remaining frames from org.junit and org.eclipse are nothing but noise frames.
在整个堆栈跟踪中,我们只对前四个帧感兴趣。剩下的来自org.junit和org.eclipse的帧只不过是噪音帧。
3.2. Filtering the StackFrames
3.2.过滤StackFrames
Let’s enhance our stack walking code and remove the noise:
让我们加强我们的堆栈行走代码,去除噪音。
public List<StackFrame> walkExample2(Stream<StackFrame> stackFrameStream) {
return stackFrameStream
.filter(f -> f.getClassName().contains("com.baeldung"))
.collect(Collectors.toList());
}
Using the power of the Stream API, we are keeping only the frames that we are interested in. This will clear out the noise, leaving the top four lines in the stack log:
利用Stream API的力量,我们只保留我们感兴趣的帧。这将清除噪音,在堆栈日志中留下前四行。
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 27
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 15
class com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 11
class com.baeldung.java9.stackwalker
.StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
Let’s now identify the JUnit test that initiated the call:
现在让我们来确定启动该调用的JUnit测试。
public String walkExample3(Stream<StackFrame> stackFrameStream) {
return stackFrameStream
.filter(frame -> frame.getClassName()
.contains("com.baeldung") && frame.getClassName().endsWith("Test"))
.findFirst()
.map(f -> f.getClassName() + "#" + f.getMethodName()
+ ", Line " + f.getLineNumber())
.orElse("Unknown caller");
}
Please note that here, we are only interested in a single StackFrame, which is mapped to a String. The output will only be the line containing StackWalkerDemoTest class.
请注意,在这里,我们只对单个StackFrame感兴趣,它被映射到String。输出将只有包含StackWalkerDemoTest类的一行。
3.3. Capturing the Reflection Frames
3.3.捕捉反射框架
In order to capture the reflection frames, which are hidden by default, the StackWalker needs to be configured with an additional option SHOW_REFLECT_FRAMES:
为了捕捉默认情况下隐藏的反射帧,StackWalker需要配置一个附加选项SHOW_REFLECT_FRAMES。
List<StackFrame> stackTrace = StackWalker
.getInstance(StackWalker.Option.SHOW_REFLECT_FRAMES)
.walk(this::walkExample);
Using this option, all the reflections frames including Method.invoke() and Constructor.newInstance() will be captured:
使用这个选项,包括Method.invoke()和Constructor.newInstance()在内的所有反射帧将被捕获。
com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 40
com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 16
com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 12
com.baeldung.java9.stackwalker
.StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
jdk.internal.reflect.NativeMethodAccessorImpl#invoke0, Line -2
jdk.internal.reflect.NativeMethodAccessorImpl#invoke, Line 62
jdk.internal.reflect.DelegatingMethodAccessorImpl#invoke, Line 43
java.lang.reflect.Method#invoke, Line 547
org.junit.runners.model.FrameworkMethod$1#runReflectiveCall, Line 50
...eclipse and junit frames...
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner#main, Line 192
As we can see, the jdk.internal frames are the new ones captured by SHOW_REFLECT_FRAMES option.
我们可以看到,jdk.internal帧是由SHOW_REFLECT_FRAMES选项捕获的新帧。
3.4. Capturing Hidden Frames
3.4.捕捉隐藏的画面
In addition to the reflection frames, a JVM implementation may choose to hide implementation specific frames.
除了反射框架外,JVM实现还可以选择隐藏特定的实现框架。
However, those frames are not hidden from the StackWalker:
然而,这些框架并没有从StackWalker中隐藏。
Runnable r = () -> {
List<StackFrame> stackTrace2 = StackWalker
.getInstance(StackWalker.Option.SHOW_HIDDEN_FRAMES)
.walk(this::walkExample);
printStackTrace(stackTrace2);
};
r.run();
Note that we are assigning a lambda reference to a Runnable in this example. The only reason is that JVM will create some hidden frames for the lambda expression.
请注意,在这个例子中,我们是将一个lambda引用分配给一个Runnable。唯一的原因是,JVM将为lambda表达式创建一些隐藏的框架。
This is clearly visible in the stack trace:
这在堆栈跟踪中可以清楚地看到。
com.baeldung.java9.stackwalker.StackWalkerDemo#lambda$0, Line 47
com.baeldung.java9.stackwalker.StackWalkerDemo$$Lambda$39/924477420#run, Line -1
com.baeldung.java9.stackwalker.StackWalkerDemo#methodThree, Line 50
com.baeldung.java9.stackwalker.StackWalkerDemo#methodTwo, Line 16
com.baeldung.java9.stackwalker.StackWalkerDemo#methodOne, Line 12
com.baeldung.java9.stackwalker
.StackWalkerDemoTest#giveStalkWalker_whenWalkingTheStack_thenShowStackFrames, Line 9
jdk.internal.reflect.NativeMethodAccessorImpl#invoke0, Line -2
jdk.internal.reflect.NativeMethodAccessorImpl#invoke, Line 62
jdk.internal.reflect.DelegatingMethodAccessorImpl#invoke, Line 43
java.lang.reflect.Method#invoke, Line 547
org.junit.runners.model.FrameworkMethod$1#runReflectiveCall, Line 50
...junit and eclipse frames...
org.eclipse.jdt.internal.junit.runner.RemoteTestRunner#main, Line 192
The top two frames are the lambda proxy frames, which JVM created internally. It is worthwhile to note that the reflection frames that we captured in the previous example are still retained with SHOW_HIDDEN_FRAMES option. This is because SHOW_HIDDEN_FRAMES is a superset of SHOW_REFLECT_FRAMES.
最上面的两个框架是lambda代理框架,是JVM内部创建的。值得注意的是,我们在前面的例子中捕获的反射框架仍然保留了SHOW_HIDDEN_FRAMES选项。这是因为SHOW_HIDDEN_FRAMES是SHOW_REFLECT_FRAMES的一个超集。
3.5. Identifying the Calling Class
3.5.识别调用类别
The option RETAIN_CLASS_REFERENCE retails the object of Class in all the StackFrames walked by the StackWalker. This allows us to call the methods StackWalker::getCallerClass and StackFrame::getDeclaringClass.
选项RETAIN_CLASS_REFERENCE在StackWalker走过的所有StackFrames中重新购买了Class的对象。这使得我们可以调用StackWalker::getCallerClass和StackFrame::getDeclaringClass方法。
Let’s identify calling class using the StackWalker::getCallerClass method:
让我们使用StackWalker::getCallerClass方法识别调用类。
public void findCaller() {
Class<?> caller = StackWalker
.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
.getCallerClass();
System.out.println(caller.getCanonicalName());
}
This time, we’ll call this method directly from a separate JUnit test:
这一次,我们将从一个单独的JUnit测试中直接调用这个方法。
@Test
public void giveStalkWalker_whenInvokingFindCaller_thenFindCallingClass() {
new StackWalkerDemo().findCaller();
}
The output of caller.getCanonicalName(), will be:
caller.getCanonicalName()的输出,将是。
com.baeldung.java9.stackwalker.StackWalkerDemoTest
Please note that the StackWalker::getCallerClass should not be called from the method at the bottom of the stack. as it will result in IllegalCallerException being thrown.
请注意,StackWalker::getCallerClass不应该从堆栈底部的方法中调用,因为它将导致IllegalCallerException被抛出。
4. Conclusion
4.结论
With this article, we’ve seen how easy it is to deal with StackFrames using the power of the StackWalker combined with the Stream API.
通过这篇文章,我们看到了利用StackWalker与Stream API相结合的力量来处理StackFrames是多么容易。
Of course, there are various other functionalities we can explore – such as skipping, dropping, and limiting the StackFrames. The official documentation contains a few solid examples for additional use cases.
当然,我们还可以探索其他各种功能–例如跳过、丢弃和限制StackFrames。官方文档中包含了一些关于其他用例的可靠示例。
And, as always, you can get the complete source code for this article over on GitHub.
而且,像往常一样,你可以在GitHub上获得本文的完整源代码。