Java 9 StackWalker

java-9-stackwalker-feature-image

In this tutorial, we’re gonna look at efficient way of stack walking for lazy access, filtering stack trace with Java 9 StackWalker.

I. StackWalker
1. Overview

The way we usually access the stack information before is using a Throwable to get the stack trace.

			StackTraceElement[] stackTrace = new Throwable().getStackTrace();
			
			for (StackTraceElement stackTraceElement : stackTrace) {
				System.out.println(stackTraceElement);
			}

Use this old method, we have to get all available elements of entire stack. If the caller only want to get top few frames on the stack, there is no way to reduce the cost.
In addition, Throwable::getStackTrace method return an array of StackTraceElement objects, which contain class names and method names, not the actual Class instances.

Java 9 defines a stack walking API that provides laziness and frame filtering. Now we can make short walk that stops at a frame for a condition, or long walk on the entire stack.

2. StackFrame

A StackFrame object represents a method invocation returned by StackWalker.
StackFrame contains methods to get stack information:

    public static interface StackFrame {
	
        public String getClassName();
        public String getMethodName();
        public Class getDeclaringClass();
		public int getByteCodeIndex();
		public String getFileName();
		public int getLineNumber();
		public boolean isNativeMethod();
		public StackTraceElement toStackTraceElement();
    }

Notice that getDeclaringClass() method will throw an UnsupportedOperationException if StackWalker is not configured with Option.RETAIN_CLASS_REFERENCE.

3. StackWalker

StackWalker object allows us to traverse and access to stacks. It contains some useful and powerful methods:

    public  T walk(Function, ? extends T> function);
    public void forEach(Consumer action);
    public Class getCallerClass();

The most important method is walk() that helps:
+ open a StackFrame stream for the current thread.
+ then apply the function with that StackFrame stream.
Because it is a Java 8 Stream, we can only traverse once and it will be closed when walk() method returns.

So, if we try to use the stream after walk() method returns, it will throw a java.lang.IllegalStateException:

			Stream stackStream = StackWalker.getInstance().walk(f -> f);

			List stackFrames = stackStream.collect(Collectors.toList());
			// throw java.lang.IllegalStateException

To get StackWalker object, we use StackWalker::getInstance method:

    public static StackWalker getInstance();
    public static StackWalker getInstance(Option option);
    public static StackWalker getInstance(Set

– If option parameter is empty, StackWalker skips all hidden frames and no class reference is retained.
If a security manager is present and input option contains Option.RETAIN_CLASS_REFERENCE, it calls its checkPermission() method for StackFramePermission("retainClassReference").

estimateDepth is the number of stack frames to be traversed.
This is an example for getting a StackWalker:

			StackWalker stackWalker = StackWalker.getInstance(
					Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE, StackWalker.Option.SHOW_HIDDEN_FRAMES), 16);
II. Example
1. Walk all StackFrames
List stacks = StackWalker.getInstance().walk(s -> s.map(frame -> "\n" + frame.getClassName() + "/" + frame.getMethodName())
							.collect(Collectors.toList()));
2. Skip some StackFrames

Using java.util.stream.Stream.skip(long n) helps us to skip a number of nearest StackFrames.

List stacksAfterSkip = StackWalker.getInstance().walk(s -> s.map(frame -> "\n" + frame.getClassName() + "/" + frame.getMethodName())
							.skip(3).collect(Collectors.toList()));
3. Limit StackFrames

Using java.util.stream.Stream.limit(long maxSize) helps us to limit a number of StackFrames. The Stream after limit() method only contains up to that number of StackFrames.

List stacksByLimit = StackWalker.getInstance().walk(s -> s.map(frame -> "\n" + frame.getClassName() + "/" + frame.getMethodName())
							.limit(3).collect(Collectors.toList()));

Because Stream is lazy, so limit reduces the cost of the capture.

4. Filter Frames by Class
			final List filterClasses = new ArrayList<>();

			filterClasses.add(StackWalking.class);
			filterClasses.add(Walker2.class);
			
			// get first Matching Frame
			Optional frameByClass = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
					.walk(s -> s.filter(f -> filterClasses.contains(f.getDeclaringClass())).findFirst());
				
			// get all Matching Frames
			List framesByClass = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(
					s -> s.filter(f -> filterClasses.contains(f.getDeclaringClass())).collect(Collectors.toList()))	
5. Get Caller Class
			Class callerClass = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass();
II. Source Code
package com.javasampleapproach.stackwalker;

import java.lang.StackWalker.StackFrame;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class StackWalking {

	public static void main(String[] args) {

		new StackWalking().walk();
	}

	private void walk() {

		new Walker1().walk();
	}

	private class Walker1 {

		public void walk() {

			new Walker2().walk();
		}
	}

	private class Walker2 {

		public void walk() {

			new Walker3().walk();
		}
	}

	private class Walker3 {

		public void walk() {
			Method1();
		}

		void Method1() {
			Method2();
		}

		void Method2() {
			Method3();
		}

		void Method3() {

			System.out.println("--- StackTrace with Throwable ---");
			StackTraceElement[] stackTrace = new Throwable().getStackTrace();

			for (StackTraceElement stackTraceElement : stackTrace) {
				System.out.println(stackTraceElement);
			}

			System.out.println("--- Java 9 StackWalker ---");
			StackWalker stackWalker = StackWalker.getInstance(
					Set.of(StackWalker.Option.RETAIN_CLASS_REFERENCE, StackWalker.Option.SHOW_HIDDEN_FRAMES), 16);

			Stream stackStream = StackWalker.getInstance().walk(f -> f);

			// This causes exception!
			// List newStackFrames = stackStream.collect(Collectors.toList());

			System.out.println("--- Walk all StackFrames ---");
			List stacks = walkAllStackframes();
			System.out.println(stacks);

			System.out.println("--- Skip some StackFrames ---");
			List stacksAfterSkip = walkSomeStackframes(3);
			System.out.println(stacksAfterSkip);
			
			System.out.println("--- Limit StackFrames ---");
			List stacksByLimit = walkLimitStackframes(3);
			System.out.println(stacksByLimit);

			System.out.println("--- filter Frame by Class ---");
			final List filterClasses = new ArrayList<>();

			filterClasses.add(StackWalking.class);
			filterClasses.add(Walker2.class);

			System.out.println("--- filter Frame by Class >> get first Matching Frame ---");
			Optional frameByClass = findFrameByClass(filterClasses);
			System.out.println(frameByClass.toString());

			System.out.println("--- filter Frame by Class >> get all Matching Frames ---");
			List framesByClass = findAllFramesByClass(filterClasses);
			System.out.println(framesByClass);

			System.out.println("--- get Caller Class ---");
			Class callerClass = getCallerClass();
			System.out.println(callerClass);
		}

		private List walkAllStackframes() {
			return StackWalker.getInstance()
					.walk(s -> s.map(frame -> "\n" + frame.getClassName() + "/" + frame.getMethodName())
							.collect(Collectors.toList()));
		}

		private List walkSomeStackframes(int numberOfFrames) {
			return StackWalker.getInstance()
					.walk(s -> s.map(frame -> "\n" + frame.getClassName() + "/" + frame.getMethodName())
							.skip(numberOfFrames).collect(Collectors.toList()));
		}
		
		private List walkLimitStackframes(int numberOfFrames) {
			return StackWalker.getInstance()
					.walk(s -> s.map(frame -> "\n" + frame.getClassName() + "/" + frame.getMethodName())
							.limit(numberOfFrames).collect(Collectors.toList()));
		}

		private Optional findFrameByClass(List filterClasses) {
			return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
					.walk(s -> s.filter(f -> filterClasses.contains(f.getDeclaringClass())).findFirst());
		}

		private List findAllFramesByClass(List filterClasses) {
			return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).walk(
					s -> s.filter(f -> filterClasses.contains(f.getDeclaringClass())).collect(Collectors.toList()));
		}

		private Class getCallerClass() {
			return StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE).getCallerClass();
		}
	}
}

Run code above, the results:

--- StackTrace with Throwable ---
com.javasampleapproach.stackwalker.StackWalking$Walker3.Method3(StackWalking.java:56)
com.javasampleapproach.stackwalker.StackWalking$Walker3.Method2(StackWalking.java:50)
com.javasampleapproach.stackwalker.StackWalking$Walker3.Method1(StackWalking.java:46)
com.javasampleapproach.stackwalker.StackWalking$Walker3.walk(StackWalking.java:42)
com.javasampleapproach.stackwalker.StackWalking$Walker2.walk(StackWalking.java:35)
com.javasampleapproach.stackwalker.StackWalking$Walker1.walk(StackWalking.java:27)
com.javasampleapproach.stackwalker.StackWalking.walk(StackWalking.java:20)
com.javasampleapproach.stackwalker.StackWalking.main(StackWalking.java:15)
--- Java 9 StackWalker ---
--- Walk all StackFrames ---
[
com.javasampleapproach.stackwalker.StackWalking$Walker3/walkAllStackframes, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/Method3, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/Method2, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/Method1, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/walk, 
com.javasampleapproach.stackwalker.StackWalking$Walker2/walk, 
com.javasampleapproach.stackwalker.StackWalking$Walker1/walk, 
com.javasampleapproach.stackwalker.StackWalking/walk, 
com.javasampleapproach.stackwalker.StackWalking/main]
--- Skip some StackFrames ---
[
com.javasampleapproach.stackwalker.StackWalking$Walker3/Method1, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/walk, 
com.javasampleapproach.stackwalker.StackWalking$Walker2/walk, 
com.javasampleapproach.stackwalker.StackWalking$Walker1/walk, 
com.javasampleapproach.stackwalker.StackWalking/walk, 
com.javasampleapproach.stackwalker.StackWalking/main]
--- Limit StackFrames ---
[
com.javasampleapproach.stackwalker.StackWalking$Walker3/walkLimitStackframes, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/Method3, 
com.javasampleapproach.stackwalker.StackWalking$Walker3/Method2]
--- filter Frame by Class ---
--- filter Frame by Class >> get first Matching Frame ---
Optional[com.javasampleapproach.stackwalker.StackWalking$Walker2.walk(StackWalking.java:35)]
--- filter Frame by Class >> get all Matching Frames ---
[com.javasampleapproach.stackwalker.StackWalking$Walker2.walk(StackWalking.java:35), com.javasampleapproach.stackwalker.StackWalking.walk(StackWalking.java:20), com.javasampleapproach.stackwalker.StackWalking.main(StackWalking.java:15)]
--- get Caller Class ---
class com.javasampleapproach.stackwalker.StackWalking$Walker3


By grokonez | March 15, 2017.

Last updated on September 12, 2018.



Related Posts


Got Something To Say:

Your email address will not be published. Required fields are marked *

*