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について]。