為了在堆疊追蹤時更為方便,JDK9 新增了 Stack-Walking API,具體來說,可以透過 java.lang.StackWalker
的 getInstance()
方法,取得 StackWalker
實例後,運用 forEach()
方法來循序走訪 StackWalker.StackFrame
,每個 StackFrame
代表著 JVM Stack 中的一個 Stack Frame。例如,可改寫〈取得 StackTraceElement〉的 StackTraceDemo
如下:
package cc.openhome;
import static java.lang.System.out;
public class StackWalkerDemo {
public static void main(String[] args) {
c();
}
static void c() {
b();
}
static void b() {
a();
}
static void a() {
out.printf("Stack trace of thread %s:%n",
Thread.currentThread().getName());
StackWalker stackWalker = StackWalker.getInstance();
stackWalker.forEach(out::println);
}
}
堆疊追蹤顯示結果會是:
Stack trace of thread main:
cc.openhome/cc.openhome.StackWalkerDemo.a(StackWalkerDemo.java:21)
cc.openhome/cc.openhome.StackWalkerDemo.b(StackWalkerDemo.java:15)
cc.openhome/cc.openhome.StackWalkerDemo.c(StackWalkerDemo.java:11)
cc.openhome/cc.openhome.StackWalkerDemo.main(StackWalkerDemo.java:7)
StackWalker
本身有個 getCallerClass()
方法,而 StackFrame
有個 getDeclaringClass()
,然而預設是不能從這兩個方法取得 Class
實例,必須在呼叫 StackWalker
的 getInstance()
時指定 StackWalker.Option
為 RETAIN_CLASS_REFERENCE
,否則會引發 java.lang.UnsupportedOperationException
。
例如,若只想顯示類別與方法名稱,可以如下:
package cc.openhome;
import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
import static java.lang.System.out;
public class StackWalkerDemo2 {
public static void main(String[] args) {
c();
}
static void c() {
b();
}
static void b() {
a();
}
static void a() {
out.printf("Stack trace of thread %s:%n",
Thread.currentThread().getName());
StackWalker stackWalker =
StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
out.printf("Caller class %s%n",
stackWalker.getCallerClass().getName());
stackWalker.forEach(stackFrame -> {
out.printf("%s.%s%n",
stackFrame.getDeclaringClass(),
stackFrame.getMethodName());
});
}
}
堆疊追蹤顯示結果會是:
Stack trace of thread main:
Caller class cc.openhome.StackWalkerDemo2
class cc.openhome.StackWalkerDemo2.a
class cc.openhome.StackWalkerDemo2.b
class cc.openhome.StackWalkerDemo2.c
class cc.openhome.StackWalkerDemo2.main
如果你只對某幾個 StackFrame
感興趣,或者想對 StackFrame
做轉換或過濾,可以使用 StackWalker
實例的 walk()
方法,例如,只想找到第一個 StackFrame
:
Optional<StackFrame> frame =
stackWalker.walk(frameStream -> frameStream.findFirst());
在 walk()
的 Lambda 運算式中,會傳入 Stream<? super StackFrame>
,而 Lambda 運算式的傳回值就是 walk()
的傳回值,因此,你可以惰性操作傳入的 Stream<? super StackFrame>
,基本上,Stream
定義的操作,像是 filter()
、map()
、collect()
、count()
等都可以使用。
有些 Stack Frame 與反射或者特定的 JVM 特定實作相關,在不指定參數的情況下,StackWalker.getInstance()
預設是不可取得 Class
實例且不顯示這些 Stack Frame,如果想要顯示反射相關的 Stack Frame,可以使用 SHOW_REFLECT_FRAMES
,若想顯示隱藏的 Stack Frame(包含反射相關Stack Frame),可以使用 SHOW_HIDDEN_FRAMES
。
底下是個簡單的示範,使用了反射機制來呼叫 c()
方法,看看在不同選項下,StackWalker
走訪的 Stack Frame 有何不同:
package cc.openhome;
import static java.lang.StackWalker.Option.*;
import static java.lang.System.out;
import java.util.List;
public class StackWalkerDemo3 {
public static void main(String[] args) throws Exception {
StackWalkerDemo3.class.getDeclaredMethod("c").invoke(null);
}
static void c() {
b();
}
static void b() {
a();
}
static void a() {
List<StackWalker> stackWalkers =
List.of(
StackWalker.getInstance(),
StackWalker.getInstance(SHOW_REFLECT_FRAMES),
StackWalker.getInstance(SHOW_HIDDEN_FRAMES)
);
stackWalkers.forEach(
stackWalker -> {
out.println();
stackWalker.forEach(out::println);
}
);
}
}
執行結果如下:
cc.openhome/cc.openhome.StackWalkerDemo3.lambda$a$0(StackWalkerDemo3.java:33)
java.base/java.lang.Iterable.forEach(Iterable.java:75)
cc.openhome/cc.openhome.StackWalkerDemo3.a(StackWalkerDemo3.java:30)
cc.openhome/cc.openhome.StackWalkerDemo3.b(StackWalkerDemo3.java:18)
cc.openhome/cc.openhome.StackWalkerDemo3.c(StackWalkerDemo3.java:14)
cc.openhome/cc.openhome.StackWalkerDemo3.main(StackWalkerDemo3.java:10)
cc.openhome/cc.openhome.StackWalkerDemo3.lambda$a$0(StackWalkerDemo3.java:33)
java.base/java.lang.Iterable.forEach(Iterable.java:75)
cc.openhome/cc.openhome.StackWalkerDemo3.a(StackWalkerDemo3.java:30)
cc.openhome/cc.openhome.StackWalkerDemo3.b(StackWalkerDemo3.java:18)
cc.openhome/cc.openhome.StackWalkerDemo3.c(StackWalkerDemo3.java:14)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.base/java.lang.reflect.Method.invoke(Method.java:564)
cc.openhome/cc.openhome.StackWalkerDemo3.main(StackWalkerDemo3.java:10)
cc.openhome/cc.openhome.StackWalkerDemo3.lambda$a$0(StackWalkerDemo3.java:33)
cc.openhome/cc.openhome.StackWalkerDemo3$$Lambda$23/25126016.accept(Unknown Source)
java.base/java.lang.Iterable.forEach(Iterable.java:75)
cc.openhome/cc.openhome.StackWalkerDemo3.a(StackWalkerDemo3.java:30)
cc.openhome/cc.openhome.StackWalkerDemo3.b(StackWalkerDemo3.java:18)
cc.openhome/cc.openhome.StackWalkerDemo3.c(StackWalkerDemo3.java:14)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.base/java.lang.reflect.Method.invoke(Method.java:564)
cc.openhome/cc.openhome.StackWalkerDemo3.main(StackWalkerDemo3.java:10)
可以看到預設的堆疊追蹤記錄最為簡潔,而 SHOW_REFLECT_FRAMES
顯示了一些反射相關的 Stack Frame,至於 SHOW_HIDDEN_FRAMES
除了顯示反射相關的 Stack Frame 之外,還多了個 JVM 特定實作的 Stack Frame。
如果需要在 StackWalker.getInstance()
時指定多個選項,可以使用 Set
來指定,這時結合 Set.of()
會很方便,例如:
StackWalker stackWalker = StackWalker.getInstance(
Set.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES)
);
如果你想限制可取得的 Stack Frame 深度,可以使用 StackWalker.getInstance()
的另一個版本:
StackWalker stackWalker = StackWalker.getInstance(
Set.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES),
10 // 深度最多為10
);