Java Blog

StackWalker and Stack-Walking API

Der Stack-Trace, den Java über die StackTraceElement bietet, ist relativ arm an Informationen, und die Standardausgabe über die Throwable-Methode printStackTrace(…) ist unübersichtlich. Aus Performance-Gründen können sogar Einträge fehlen, so dokumentiert es die Javadoc an der Methode:

Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this thread is permitted to return a zero-length array from this method.

Zudem fehlen spannende Informationen wie Zeitpunkte, und ein Thread.getAllStackTraces() ist bei vielen Threads und tiefen Aufrufhierarchien sehr langsam. Summa summarum: Ein guter Debugger oder ein Analysetool mit Zugriff auf die JVM-Eigenschaften ist für die ernsthafte Entwicklung unumgänglich.

In Java 9 hat Oracle die “JEP 259: Stack-Walking API” umgesetzt. Ein java.lang.StackWalker wandert die Aufrufhierarchie ab und repräsentiert die Aufrufhierarchie als StackFrame-Objekte von dort, wo der Stack generiert wurde, nach unten zur Aufrufstelle. Es gibt mehrere überladenen statische getInstance(…)-Methoden, die einen StackWalker generieren. Wir können dann mit

über die StackFrames laufen.

Beispiel:

public static void showTrace() {

  List<StackFrame> frames =

    StackWalker.getInstance( Option.RETAIN_CLASS_REFERENCE )

               .walk( stream  -> stream.collect( Collectors.toList() ) );

  for ( StackFrame stackFrame : frames )

    System.out.println( stackFrame );

}

Da alle Informationen zu liefern die Geschwindigkeit senkt, und vielleicht unnötig viel Arbeit verursacht, deklariert die Aufzählung StackWalker.Option die Konstanten RETAIN_CLASS_REFERENCE, SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES für unterschiedliche Vollständigkeit der Informationen.  Die Aufzählungen sind ein Argument für getInstance(…). Um die Class-Objekte über getDeclaringClass() abrufen zu können, muss getInstance(…) mit der Option.RETAIN_CLASS_REFERENCE gesetzt sein.

Beispiel: Die forEach(…)-Methode eines Streams konsumiert einen Consumer auf jedem Element. Finde heraus, ob unsere Consumer-Methode accept(…) indirekt von einer Klasse aus dem Paket  java.util.concurrent aufgerufen wurde:

Consumer<String> walkStack = String -> {

  StackWalker.getInstance( Option.RETAIN_CLASS_REFERENCE )

        .walk( stream -> stream.map( StackFrame::getDeclaringClass )

                               .map( Class::getPackageName )

                               .filter( s -> s.startsWith( "java.util.concurrent" ) )

                               .findFirst() )

                               .ifPresentOrElse( e -> System.out.println( "Durch j.u.c gelaufen" ),

                                                 () -> System.out.println( "Nicht durch j.u.c gelaufen" ) );

};

Stream.of( "Hallo" ).forEach( walkStack );            // Nicht durch j.u.c gelaufen

Stream.of( "Hallo" ).parallel().forEach( walkStack ); // Durch j.u.c gelaufen