Java 9 StackWalking APIの紹介
1前書き
この簡単な記事では、Java 9のhttp://openjdk.java.net/jeps/259[StackWalking API]について説明します。
-
新しい機能は
StackFrame
s ** の
Stream
へのアクセスを提供し、直接スタックと強力なリンク:/java-8-streams[Java 8の
Stream__ API]の両方を使用してスタックを簡単に閲覧できるようにします。
2
StackWalker
の利点
- Java 8では、__Throwable
-
getStackTrace
および
Thread :: getStackTrace
は、
StackTraceElement
__の配列を返します。多くの手動コードがなければ、不要なフレームを破棄して、関心のあるフレームだけを保持する方法はありませんでした。 - これに加えて、__Thread
-
getStackTrace__は部分スタックトレースを返す可能性があります。これは、仕様上、パフォーマンス上の理由からVMの実装で一部のスタックフレームを省略することを許可しているためです。
Java 9では、
StackWalker
の__walk()メソッドを使用して、興味のある数フレームまたは完全なスタックトレースをたどることができます。
もちろん、新しい機能はスレッドセーフです。これにより、複数のスレッドがそれぞれのスタックにアクセスするための単一の
StackWalker
インスタンスを共有できます。
JEP-259
で説明されているように、JVMは必要に応じて追加のスタックフレームへの効率的な遅延アクセスを可能にするように拡張されます。
3
StackWalker
in Action
一連のメソッド呼び出しを含むクラスを作成することから始めましょう。
public class StackWalkerDemo {
public void methodOne() {
this.methodTwo();
}
public void methodTwo() {
this.methodThree();
}
public void methodThree() {
//stack walking code
}
}
3.1. スタックトレース全体をキャプチャする
先に進んで、スタックウォーキングコードを追加しましょう。
public void methodThree() {
List<StackFrame> stackTrace = StackWalker.getInstance()
.walk(this::walkExample);
}
- __StackWalker
-
walk
メソッドは、関数参照を受け取り、現在のスレッドに対して
StackFrame
__の
Stream
を作成し、その関数を
Stream
に適用して
Stream__を閉じます。 - それでは、__StackWalkerDemo
-
walkExample__メソッドを定義しましょう。
public List<StackFrame> walkExample(Stream<StackFrame> stackFrameStream) {
return stackFrameStream.collect(Collectors.toList());
}
このメソッドは単に
__StackFrame
sを収集し、それを
List <StackFrame> __として返します。この例をテストするには、JUnitテストを実行してください。
@Test
public void giveStalkWalker__whenWalkingTheStack__thenShowStackFrames() {
new StackWalkerDemo().methodOne();
}
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
スタックトレース全体では、上位4つのフレームだけに関心があります。
org.junitと
org.eclipse
からの残りの
フレームはノイズフレーム
に他なりません。
3.2.
__StackFrame
__s
のフィルタリング
スタックウォーキングコードを強化し、ノイズを取り除きましょう。
public List<StackFrame> walkExample2(Stream<StackFrame> stackFrameStream) {
return stackFrameStream
.filter(f -> f.getClassName().contains("com.baeldung"))
.collect(Collectors.toList());
}
Stream
APIの機能を使用して、目的のフレームのみを保持します。これにより、ノイズが除去され、スタックログの上位4行が残ります。
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
それでは、電話をかけた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");
}
ここでは、
String
にマッピングされた単一の
StackFrameにのみ関心があることに注意してください。出力は
StackWalkerDemoTest__クラスを含む行だけです。
3.3. 反射フレームをキャプチャする
デフォルトで非表示になっているリフレクションフレームをキャプチャするには、
StackWalker
に追加オプション
SHOW
REFLECT
FRAMES
を設定する必要があります。
List<StackFrame> stackTrace = StackWalker
.getInstance(StackWalker.Option.SHOW__REFLECT__FRAMES)
.walk(this::walkExample);
このオプションを使用すると、
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
ご覧のとおり、
jdk.internal
フレームは
SHOW
REFLECT
FRAMES
オプションで取り込まれた新しいフレームです。
3.4. 隠しフレームのキャプチャ
リフレクションフレームに加えて、JVM実装は実装固有のフレームを隠すことを選択するかもしれません。
ただし、これらのフレームは
StackWalker
から隠されていません。
Runnable r = () -> {
List<StackFrame> stackTrace2 = StackWalker
.getInstance(StackWalker.Option.SHOW__HIDDEN__FRAMES)
.walk(this::walkExample);
printStackTrace(stackTrace2);
};
r.run();
この例では、ラムダ参照を
Runnable
に割り当てています。唯一の理由は、JVMがラムダ式のためにいくつかの隠しフレームを作成することです。
これは明らかにスタックトレースに表示されます。
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
上の2つのフレームは、JVMが内部で作成したラムダプロキシフレームです。前の例でキャプチャしたリフレクションフレームは、
SHOW
HIDDEN
FRAMES
オプションを使用しても保持されることに注意する必要があります。これは、
SHOW
HIDDEN
FRAMES
が
SHOW
REFLECT
FRAMES
のスーパーセットであるためです。
3.5. 呼び出し側クラスの識別
-
オプション
RETAIN
CLASS
REFERENCE
は、
StackWalker
がたどったすべての
__StackFrame
内の
Class
のオブジェクトを再小売します。これにより、
StackWalker -
getCallerClass
メソッドと
StackFrame :: getDeclaringClass__メソッドを呼び出すことができます。 - __StackWalker
-
getCallerClass__メソッドを使用して呼び出し元のクラスを識別しましょう。
public void findCaller() {
Class<?> caller = StackWalker
.getInstance(StackWalker.Option.RETAIN__CLASS__REFERENCE)
.getCallerClass();
System.out.println(caller.getCanonicalName());
}
今回は、このメソッドを別のJUnitテストから直接呼び出します。
@Test
public void giveStalkWalker__whenInvokingFindCaller__thenFindCallingClass() {
new StackWalkerDemo().findCaller();
}
caller.getCanonicalName()、
の出力は次のようになります。
com.baeldung.java9.stackwalker.StackWalkerDemoTest
- __StackWalker
-
getCallerClass
は、スタックの一番下にあるメソッドから呼び出さないでください。
IllegalCallerException__がスローされることになるためです。
4結論
この記事では、
StackWalker
の機能と
Stream
APIの組み合わせを使用して
__StackFrame
__を簡単に処理できることを確認しました。
もちろん、スキップ、ドロップ、
__StackFrame
__の制限など、探求できる機能は他にもあります。
公式文書
には、その他の使用例についての堅実な例がいくつか含まれています。
そしていつもどおり、この記事の完全なソースコードを入手することができますhttps://github.com/eugenp/tutorials/tree/master/core-java-9[GitHubについて]。