In the post Clean Code with Exception I promised to solve the problem of making Exception slow. So this post will go deeper into what Java will do when an Exception is thrown and explain what a stack trace is… and from there will optimize the use of Exception
1. What will Java do when an Exception is thrown?
To clearly understand what Java does when an Exception is thrown, let’s look at the code of the Throwable class, the parent of Exception ;))
| |
The above code shows us that all the public constructors of Throwable call the fillInStackTrace() function. So what does this function do and why do we need to call it? To get the answer, let’s go to the next section
2. What is a stack trace? Why do we need to use it?
Suppose we have the following code and try to run it
| |
The result will be the following error:
HighLevelException: MidLevelException: LowLevelException
at Junk.a(Junk.java:13)
at Junk.main(Junk.java:4)
Caused by: MidLevelException: LowLevelException
at Junk.c(Junk.java:23)
at Junk.b(Junk.java:17)
at Junk.a(Junk.java:11)
... 1 more
Caused by: LowLevelException
at Junk.e(Junk.java:30)
at Junk.d(Junk.java:27)
at Junk.c(Junk.java:21)
... 3 more
Analyzing the above error message, we have the following interpretation:
- An exception
HighLevelExceptionwas thrown. The cause ofHighLevelExceptionisMidLevelExceptionand the cause ofMidLevelExceptionisLowLevelException HighLevelExceptionappears in classJunkmethodafileJunk.javaline 13 and methodais called from classJunkmethodmainfileJunk.javaline 4MidLevelExceptionappears in classJunkmethodcfileJunk.javaline 23, methodcis called from classJunkmethodbfileJunk.javaline 17 and methodbis called from classJunkmethodafileJunk.javaline 11LowLevelExceptionis similar toMidLevelExceptionandHighLevelExceptionbut I also have a headache. Of course, if there is an error and need to trace, I still have to do it ;))
And from the above analysis, we can redraw the flow where the exception is thrown how it looks like:
My image above can also be considered a representation of stack trace. To be more specific, stack trace is an ordered list of function calls and is arranged in the order of calls from the most recent to the first call. It is very useful in analyzing, finding the cause and from there deciding whether to fix the error or not.
Back to the question in #1. Then we have the answer. The fillInStackTrace() function is to collect stack trace and it is very useful, isn’t it 🤟
3. Collect stack trace fast or slow?
The answer is very slow and to see how slow you can clone this repo. If you do variables, see #5.
4. Disable collect stack trace and optimize
I rewrote NSTException and simplified the 2 functions getStackTrace() and fillInStackTrace() as the following code:
| |
Now when NSTException is initialized, it will not collect stack trace anymore. Let’s see the results.
5. Benchmark
I will explain a little bit about how to benchmark. I have 3 methods callNotThrowException, callToThrowException and callToThrowNSTException used to simulate the following situations:
callNotThrowException: normal situation, noExceptionis throwncallToThrowException: situation whereExceptionis throwncallToThrowNSTException: situation whereNSTExceptionis thrown.NSTExceptionstands for No Stack Trace Exception, a custom exception I rewrote, turning off stack trace collection to increase performance
Due to differences in configuration, OS, Java version… the results may vary. And here are the results from my machine:
Benchmark (param) Mode Cnt Score Error Units
Jmh.callNotThrowException 1 thrpt 25 80504640.048 ± 15390305.644 ops/s
Jmh.callToThrowException 1 thrpt 25 703653.862 ± 58119.745 ops/s
Jmh.callToThrowNSTException 1 thrpt 25 41387559.456 ± 6619635.038 ops/s
We note that the 2 results of Jmh.callNotThrowException and Jmh.callToThrowException are 80504640.048 and 703653.862 (ignoring the ± error) then 80504640.048 / 703653.862 = 114 is a very large number.
Conclusion: By using NSTException to turn off collect stack trace, we have the following advantages:
- Speed up the application and use memory more efficiently
- Make the method signature easier to understand
- External control code is easier to understand when separating business code and error handling, I will also have a post about this part
But there is also a disadvantage that there is no stack trace, from which tracing errors will be difficult. Please watch the next post to solve it.