在〈認識堆疊追蹤〉中曾經看過例外發生時,利用例外物件自動收集的 Stack Frame 來顯示堆疊追蹤,自動收集的 Stack Frame 是來自於主執行緒的 JVM Stack,實際上,每個執行緒都會有自己專屬的 JVM Stack,顧名思義,JVM Stack 是個先進後出結構,每呼叫一個方法,JVM 就會建立一個 Stack Frame 來儲存區域變數、方法、類別等資訊並置放至 JVM Stack,方法呼叫結束後 Stack Frame 就從 JVM Stack 中跳出銷毀。
也許是為了瞭解應用程式的行為,或者是進行除錯,自行取得堆疊追蹤有其需求,為此,JDK 從 1.4 開始就在 Throwable
與 Thread
類別上提供了 getStackTrace()
方法,可用來取得當時活動執行緒的 JVM Stack,並以 StackTraceElement
陣列傳回 JVM Stack 中全部的 Stack Frame。
如果你想主動取得 Stack Frame,方式之一是建立 Throwable
實例並呼叫其 getStackTrace()
方法:
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
另一個方式是取得 Thread
實例,像是透過 Thread.currentThread()
,並呼叫其 getStackTrace()
方法:
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
每個 StackTraceElement
實例代表著 JVM Stack 中的一個 Stack Frame,可以使用 StackTraceElement
的 getClassName()
、getFileName()
、getLineNumber()
、getMethodName()
,或者是 JDK9 新增的 getModuleName()
、getModuleVersion()
、getClassLoaderName()
等方法取得對應的資訊。例如,可改寫〈認識堆疊追蹤〉中的範例,主動顯示堆疊追蹤:
package cc.openhome;
import static java.lang.System.out;
import java.util.List;
public class StackTraceDemo {
public static void main(String[] args) {
c();
}
static void c() {
b();
}
static void b() {
a();
}
static void a() {
Thread currentThread = Thread.currentThread();
StackTraceElement[] stackTrace = currentThread.getStackTrace();
out.printf("Stack trace of thread %s:%n", currentThread.getName());
List.of(stackTrace).forEach(out::println);
}
}
這個範例程式中,c()
方法呼叫 b()
方法,b()
方法呼叫 a()
方法,因而堆疊追蹤顯示結果會是:
Stack trace of thread main:
java.base/java.lang.Thread.getStackTrace(Thread.java:1654)
cc.openhome/cc.openhome.StackTraceDemo.a(StackTraceDemo.java:21)
cc.openhome/cc.openhome.StackTraceDemo.b(StackTraceDemo.java:16)
cc.openhome/cc.openhome.StackTraceDemo.c(StackTraceDemo.java:12)
cc.openhome/cc.openhome.StackTraceDemo.main(StackTraceDemo.java:8)
每個執行緒會有自己的 JVM Stack,因此底下的程式會顯示執行緒個別的追蹤:
package cc.openhome;
import static java.lang.System.out;
import java.util.List;
public class StackTraceDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> c());
t.start();
t.join(); // 這是為了能循序顯示個別的堆疊追蹤
c();
}
static void c() {
b();
}
static void b() {
a();
}
static void a() {
Thread currentThread = Thread.currentThread();
StackTraceElement[] stackTrace = currentThread.getStackTrace();
out.printf("Stack trace of thread %s:%n", currentThread.getName());
List.of(stackTrace).forEach(out::println);
}
}
執行結果如下:
Stack trace of thread Thread-0:
java.base/java.lang.Thread.getStackTrace(Thread.java:1654)
cc.openhome/cc.openhome.StackTraceDemo2.a(StackTraceDemo2.java:24)
cc.openhome/cc.openhome.StackTraceDemo2.b(StackTraceDemo2.java:19)
cc.openhome/cc.openhome.StackTraceDemo2.c(StackTraceDemo2.java:15)
cc.openhome/cc.openhome.StackTraceDemo2.lambda$main$0(StackTraceDemo2.java:8)
java.base/java.lang.Thread.run(Thread.java:844)
Stack trace of thread main:
java.base/java.lang.Thread.getStackTrace(Thread.java:1654)
cc.openhome/cc.openhome.StackTraceDemo2.a(StackTraceDemo2.java:24)
cc.openhome/cc.openhome.StackTraceDemo2.b(StackTraceDemo2.java:19)
cc.openhome/cc.openhome.StackTraceDemo2.c(StackTraceDemo2.java:15)
cc.openhome/cc.openhome.StackTraceDemo2.main(StackTraceDemo2.java:11)
如果只是想取得簡單的資訊,基本上可以使用以上的方式,然而,getStackTrace()
會取得全部的 Stack Frame,如果你只是想察看前幾個,就會顯得沒有效率;若是想取得方法所在的類別資訊呢?StackTraceElement
只提供 getClassName()
傳回字串,你得自己想辦法,像是透過反射(Reflection)等機制來達到目的,如果想要進一步取得呼叫方法那方的類別資訊呢?JDK8 或以前的版本,沒有標準 API 可以從事這個任務(真的硬是要取得呼叫方法那方的類別資訊的話,在 JDK 內部 API 中,sun.reflect.Reflection
是有個 getCallerClass()
方法)。