Java 9’s other new enhancements, Part 5: Stack-Walking API

Published by: 0

colors paper 100717679 large - Java 9’s other new enhancements, Part 5: Stack-Walking API

An efficient standard API for stack walking that allows easy filtering and lazy access to stack trace information

JEP 259: Stack-Walking API defines an efficient standard API for stack walking that allows the easy filtering of and lazy access to stack trace information. This API supports short walks that stop at a stack frame matching given criteria, and also supports long walks that traverse the entire stack. This post introduces you to the Stack-Walking API.

From StackTraceElement to StackWalker

Java 1.4 introduced the java.lang.StackTraceElement class to describe an element representing a stack frame in a stack trace. This class provides methods that return the fully qualified name of the class containing the execution point represented by this stack trace element along with other useful information. Java 1.4 also introduced the StackTraceElement getStackTrace() method to the java.lang.Thread and java.lang.Throwable classes. This method respectively returns an array of stack trace elements representing the invoking thread’s stack dump and provides programmatic access to the stack trace information printed by printStackTrace().

There are several reasons why you might want to access stack trace elements. I’ve listed three reasons below — you can probably add to this list:

  • Understand an application’s behavior.
  • Log stack trace element details to assist with debugging.
  • Find out who called a certain method in order to identify the source of a resource leak.

Before Java 9, you might obtain a stack trace by instantiating Throwable and invoking its getStackTrace() method, as shown here:

StackTraceElement stackTrace = new Throwable().getStackTrace();

Unfortunately, this approach to obtaining a stack trace is rather costly and impacts performance. The Java Virtual Machine (JVM) eagerly captures a snapshot of the entire stack (except for hidden stack frames), even when you only need the first few frames. Also, your code will probably have to process frames that are of no interest, which is also time-consuming. Finally, you cannot access the actual java.lang.Class instance of the class that declared the method represented by a stack frame. To access this Class object, you’re forced to extend java.lang.SecurityManager in order to access the protected getClassContext() method, which returns the current execution stack as an array of Class objects.

These APIs don’t satisfy the use cases that depend on the JDK-internal sun.reflect.Reflection::getCallerClass method, or else their performance overhead is intolerable. These use cases include:

  • Walk the stack until the immediate caller’s class is found. Every JDK caller-sensitive API looks up its immediate caller’s class to determine the API’s behavior. For example, the Class::forName and ResourceBundle::getBundle methods use the immediate caller’s classloader to load a class and a resource bundle, respectively. Reflective APIs such as Class::getMethod use the immediate caller’s classloader to determine the security checks to be performed.
  • Walk the stack, filtering out the stack frames of specific implementation classes to find the first non-filtered frame. The java.util.logging API filters intermediate stack frames (typically implementation-specific and reflection frames) to find the caller’s class.
  • Walk the stack to find all protection domains, until the first privileged frame is reached. This is required in order to do permission checks.
  • Walk the entire stack, possibly with a depth limit. This is required to generate the stack trace of any Throwable object, and to implement the Thread::dumpStack method.

Java 9 introduces the Stack-Walking API as a more performant and capable alternative to the StackTraceElement– and SecurityManager-related APIs. The Stack-Walking API primarily consists of the java.lang.StackWalker class with its nested Option class and StackFrame interface. However, Stack-Walking also includes the java.lang.IllegalCallerException class.

StackWalker basics

The StackWalker class is easy to use. In this section, I’ll focus on the basics by showing you first how to obtain a StackWalker instance and then how to use this instance to walk all or only a few stack frames.

Obtaining a StackWalker

StackWalker provides four static getInstance() methods that return StackWalkers. The methods differ in whether or not the walkers also access hidden frames or refective frames (a subset of hidden frames) and retain Class references:

  • StackWalker getInstance(): Return a StackWalker instance that’s configured to skip all hidden frames and that doesn’t retain any Class reference.
  • StackWalker getInstance(StackWalker.Option option): Return a StackWalker instance with the given option specifying the stack frame information that it can access.
  • StackWalker getInstance(Set<StackWalker.Option> options): Return a StackWalker instance with the given options specifying the stack frame information that it can access. If the given options is empty, this StackWalker is configured to skip all hidden frames and to not retain any Class reference.
  • StackWalker getInstance(Set<StackWalker.Option> options, int estimatedDepth): Return a StackWalker instance with the given options specifying the stack frame information that it can access. If the given options is empty, this StackWalker is configured to skip all hidden frames and to not retain any Class reference. Furthermore, estimatedDepth specifies the estimated number of stack frames that this StackWalker instance will traverse. StackWalker could use this value as a hint for its buffer size.

The value passed to option or included in options is one of StackWalker.Option.RETAIN_CLASS_REFERENCE, StackWalker.Option.SHOW_HIDDEN_FRAMES, or StackWalker.Option.SHOW_REFLECT_FRAMES.

The following examples demonstrate these methods:

import static java.lang.StackWalker.Option.*;
StackWalker sw1 = StackWalker.getInstance();
StackWalker sw2 = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
StackWalker sw3 = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE, 
SHOW_HIDDEN_FRAMES));
StackWalker sw4 = StackWalker.getInstance(Set.of(RETAIN_CLASS_REFERENCE), 
16);

The first example skips all hidden frames and doesn’t retain any Class reference. The second example is like the first example except that it retains Class references by passing RETAIN_CLASS_REFERENCE.

The third example also retains Class references, and shows hidden frames by passing SHOW_HIDDEN_FRAMES. The final example retains this reference and also sets the estimated traversal depth to 16. Note that the third and fourth examples demonstrate Java 9’s convenience factory methods enhancement in a java.util.Set context. (I discussed this enhancement in Part 1 of this series.)

Walking all stack frames with forEach()

Once you have a StackWalker instance, you can access stack frames by invoking the forEach() and walk() methods. The forEach() method header appears below:

void forEach(Consumer<? super StackWalker.StackFrame> action)

This method walks the stack, performing the given action on each element of the current thread’s stream of StackFrames. Traversal starts at the stack’s top-most frame, which identifies the method that called forEach().

Listing 1 presents the source code to an application that demonstrates forEach().

Listing 1. SWDemo.java (version 1)

public class SWDemo
{
public static void main(String args)
{
a();
}
public static void a()
{
b();
}
public static void b()
{
c();
}
public static void c()
{
StackWalker sw = StackWalker.getInstance();
sw.forEach(System.out::println);
}
}

main() starts a chain of method invocations. The final invocation instantiates StackWalker and, on this object, invokes forEach() with a System.out::println method reference to print out all stack frames.

Compile Listing 1 as follows:

javac SWDemo.java

Run the resulting application as follows:

java SWDemo

You should observe the following output — the first line identifies the top stack frame:

SWDemo.c(SWDemo.java:21)
SWDemo.b(SWDemo.java:15)
SWDemo.a(SWDemo.java:10)
SWDemo.main(SWDemo.java:5)

Walking all or fewer stack frames with walk()

It’s often the case that you’ll want to limit the number of stack frames that are walked, for performance or another reason. StackWalker provides the walk() generic method for this task:

<T> T walk(Function<? super Stream<StackWalker.StackFrame>, ? extends T> function)

walk() opens a sequential stream of StackFrames for the current thread and then applies the given function to walk the StackFrame stream. The stream reports stack frames in order, from the top-most frame that represents the execution point at which the stack was generated (and which identifies the method that called walk()) to the bottom-most frame. walk() returns the type of java.util.function.Function‘s return value (the R in Function<T,R>).

Listing 2 presents the source code to an application that demonstrates walk().

Listing 2. SWDemo.java (version 2)

import java.util.List;
import java.util.stream.Collectors;
public class SWDemo
{
public static void main(String args)
{
a();
}
public static void a()
{
b();
}
public static void b()
{
c();
}
public static void c()
{
StackWalker sw = StackWalker.getInstance();
List<StackWalker.StackFrame> frames;
frames = sw.walk(frames_ -> frames_.collect(Collectors.toList()));
frames.forEach(System.out::println);
System.out.println();
long numFrames = sw.walk(frames_ -> frames_.count());
System.out.printf("Total number of frames: %d%n%n", numFrames);
frames = sw.walk(frames_ -> frames_.limit(2).collect(Collectors.toList()));
frames.forEach(System.out::println);
}
}

c() instantiates StackWalker and then uses this object to walk all stack frames (equivalent to forEach()), count all stack frames (count() returns a long), and walk only the first two stack frames. The following output is generated:

SWDemo.c(SWDemo.java:27)
SWDemo.b(SWDemo.java:19)
SWDemo.a(SWDemo.java:14)
SWDemo.main(SWDemo.java:9)
Total number of frames: 4
SWDemo.c(SWDemo.java:34)
SWDemo.b(SWDemo.java:19)

StackWalker advanced

Having mastered the basics of StackWalker, it’s time for you to pursue more advanced topics. We’ll begin by considering why the walk() method was designed to receive a function instead of return a stream.

Understanding a design curiosity

Each thread has its own execution stack. You might think of this stack as a stable data structure that the JVM modifies only at the top, by adding or removing a single frame each time a method is entered or exited. In reality, the JVM can restructure a thread’s stack any time it sees fit, to improve performance.

For a stack walker to observe a consistent stack, it must make certain that the stack is stable while building stack frames. This can only happen when the stack walker controls the stack, which occurs while walk() is executing. As a result, stream processing must occur during the call, and so the stream cannot be returned.

If you return the stream by passing an identity function to walk(), which I demonstrate below, you’ll be rewarded with a thrown java.lang.IllegalStateException object when you try to process the stream:

Stream<StackWalker.StackFrame> dangerousSW = stackWalker.walk(frames -> frames);
dangerousSW.count(); // IllegalStateException is thrown.

Obtaining the caller class

StackWalker supports the concept of a caller class, which the JDK 9 documentation defines as the Class object of the caller who invoked the method that invoked StackWalker‘s Class<?> getCallerClass() method. This method is the replacement for sun.reflect.Reflection.getCallerClass(), which may not be available in future Java releases.

In its search for the caller class, getCallerClass() disregards reflection frames, method handles, and hidden frames regardless of the SHOW_REFLECT_FRAMES and SHOW_HIDDEN_FRAMES options with which this StackWalker object has been configured.

How to Upgrade your Android Mobile to Latest Software Version | Reliance Jio

This video tells you how to upgrade your android mobile to latest software version. It is important to upgrade your device ...