為了在堆疊追蹤時更為方便,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
);

