A quick comparison of various methods to get line number information in Java. Compiles and runs with Java 8+.
For Java 8 we compare the public Throwable::getStackTrace()[2]
with the private Throwable::getStackTraceElement(2)
method to get the line number information from the third, top-most frame for various stack depth.
<JAVA-8_HOME>/bin/java -XX:+UseSerialGC -Xbatch -XX:-TieredCompilation -XX:-UseOnStackReplacement GetLineNumber
Throwable::getStackTrace()[2] = GetLineNumber.recursive(GetLineNumber.java:102)
0 : 2063ns
8 : 4791ns
16 : 7611ns
32 : 13340ns
64 : 24638ns
128 : 50544ns
256 : 96262ns
512 : 188946ns
Throwable::getStackTraceElement(2) [reflective] = GetLineNumber.recursive(GetLineNumber.java:102)
0 : 1045ns
8 : 1052ns
16 : 1296ns
32 : 1984ns
64 : 3167ns
128 : 5469ns
256 : 9719ns
512 : 18108ns
As you can see, the performance of both versions depends on the stack depth but the private method performs much better. That's because while it still acquires a full stack trace, it only allocates the a single StackTraceElement
whereas the public Throwable::getStackTrace()
method allocates StackTraceElement
for all stack frames although we're only interested in a single frame in this example.
Java 9 redesigned the stack walking code and added a new Stack walking API. During this re-implementation (see JDK-8150778), the private Throwable::getStackTraceElement()
method was removed in favour of the new Throwable::getStackTraceElements()
method which does a single down-call to the VM to fetch all StackTraceElement
s at once.
<JAVA-11_HOME>/bin/java -XX:+UseSerialGC -Xbatch -XX:-TieredCompilation -XX:-UseOnStackReplacement GetLineNumber
Throwable::getStackTrace()[2] = GetLineNumber.recursive(GetLineNumber.java:102)
0 : 1392ns
8 : 3056ns
16 : 4510ns
32 : 7723ns
64 : 13989ns
128 : 26340ns
256 : 52131ns
512 : 102076ns
StackWalker::walk(s -> s.limit(3).skip(2)) [reflective] = GetLineNumber.recursive(GetLineNumber.java:102)
0 : 1841ns
8 : 1405ns
16 : 1393ns
32 : 1401ns
64 : 1377ns
128 : 1531ns
256 : 1419ns
512 : 1432ns
As you can see, the new StackWalker
API doesn't depend on the absolute stack depth any more if we're only interested in some of the top frames. The other nice thing is that the new implementation also improves the performance of the old Throwable::getStackTrace()[2]
method (mostly because of "JDK-8216302: StackTraceElement::fill_in can use cached Class.name" which was integrated in JDK 13 and backported to 11.0.3).
Notice however that the estimateDepth
argument which we pass to StackWalker.getInstance()
isn't directly honoured. Instead the current implementation enforces a minimum of MIN_BATCH_SIZE
(equals to 8) frames on each down-call to the VM (of which the first two entries are reserved for internal bookkeeping).
Out of interest I've checked how the popular Log4j2 logging framework is computing line number information. The corresponding StackLocator::calcLocation()
method is in the log4j-api.jar library which is a multi-release jar file and contains two versions of the StackLocator
class, one for JDK 8 and a second one for JDK 9+. The old version uses Throwable::getStackTrace()
while the new one is using StackWalker::walk()
.