Getting the stack trace versus throwing an Exception. What is common and what is different

(Last Updated On: 11th June 2018)

Motivation

At the first glance these two things (e.g. getting the stack traces and throwing an exception) might seem very unrelated, however, they are quite similar up to a certain extent in the way they behave under the hood. Current article aims to reveal such similarities and differences.

A closer look inside Thread::getStackTrace API

If we dig inside JDK sources for Thread::getStackTrace method, we find below implementation:

public StackTraceElement[] getStackTrace() {
    if (this != Thread.currentThread()) {
        // first it checks for getStackTrace permission
        // then collects the stack traces
        // ...
    } else { 
        // for current Thread
        return (new Exception()).getStackTrace(); 
    } 
} 

As the code reveals, under the hood Thread::getStackTrace creates an instance of Exception class and calls getStackTrace() method on that instance.

Now let’s move further and inspect what really happens when the Exception instance is created.

What happens when throwing an Exception

When throwing an Exception (or any other Throwable derived class), the Exception class constructor dispatches the call to Throwable default constructor which fills in the stack trace, as per below JDK sources:

public Exception() {
    // default constructor dispatches the call to Throwable constructor
    super(); 
}

public Throwable() {
    // default constructor fills in the execution stack trace
    fillInStackTrace();
}

Now, having understood what happens under the hood, we can roughly describe similarities and differences between these two.

Similarities

As we have already noticed, in both cases:

  • a new Exception instance is created
  • the current state of the stack frames for the actual thread is recorded in some internal representation (i.e. fillInStackTrace() method call)

Differences

In addition to above similarities, Thread::getStackTrace translates the stack frames into a Java representation, returning a StackTraceElement[] array to the caller (i.e. getStackTrace() method call)

Microbenchmark

I have created a short benchmark to test the performance between these two, even if they are not quite equivalent since one complements the other due to an extra operation.

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations = 5, timeUnit = TimeUnit.NANOSECONDS)
@Measurement(iterations = 5, timeUnit = TimeUnit.NANOSECONDS)
@Fork(value = 3, warmups = 1)
@State(Scope.Benchmark)
public class GetStackTraceVsThrowExceptionJmh {

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
            .include(GetStackTraceVsThrowExceptionJmh.class.getName())
            .build();
        new Runner(opt).run();
    }

    @Benchmark
    public Exception throwException() {
        return new Exception();
    }

    @Benchmark
    public StackTraceElement[] getStackTrace() {
        return Thread.currentThread().getStackTrace();
    }
}

I have tested above benchmark with JDK 10.0.1.

Benchmark          Mode     Cnt         Score     Error    Units
throwException     avgt      15        937.815 ± 46.736    ns/op
getStackTrace      avgt      15     10,512.959 ± 212.659   ns/op

Tests triggered on my machine (CPU: Intel i7-6700HQ Skylake; MEMORY: 16GB DDR4 2133 MHz; OS: Ubuntu 16.04.2)

Conclusion:
  • getStackTrace() seems around 10x times slower than throwing an exception. This basically means that not filling the stack trace itself takes the majority amount of time, but converting it to a Java representation.

NB: What really happens when a stack trace is converted to a Java representation, is that Virtual Machine fills in the StackTraceElement[] array representation relying on a native method call, which is costly.

If throwing an exception is costly (i.e. creating the Exception instance and filling in the stack trace), collecting the exception stack trace is even heavier!

For normal applications, if these two operations can be split and asynchronously triggered (i.e. throwing the exception first and then, separately, collecting the stack trace within another thread) there might be an overall performance improvement for the normal execution flow!

Authorionutbalosin

Software Architect, Technical Trainer

Leave a Reply

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