Hello, I am why.
Guess this time I am going to write another knowledge point that is useless?
Sorry, I asked a little bit early, and I didn't give any hints, so guess what, right?
First give you the last code:
public class ExceptionTest {
public static void main(String[] args) {
String msg = null;
for (int i = 0; i < 500000; i++) {
try {
msg.toString();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
Come on, just this code, guess what flower to write?
Of course, there are friends who have guessed, and friends who have not guessed.
Very good, so please quickly pull to the end of the article if you have guessed it, and you can go out after completing the one-click three-link task.
Classmates who haven't guessed it, as soon as I run the code, you will know what I am going to say:
What happened in an instant, have you seen it? Is it magical? Are there any questions?
It’s okay, if you don’t see it clearly, I can still take a screenshot for you:
After a certain number of null pointer exceptions are thrown, the exception stack is gone.
This is what my title says: Isn't it too bullshit? The abnormal information suddenly disappeared.
.png)
Why are you talking about?
Why?
This matter has to start in 2004.
That year, Sun released JDK 5 at 18:00 on September 30.
There is such a passage in its release-notes:
https://www.oracle.com/java/technologies/javase/release-notes-introduction.html
This sentence is mainly framed. It doesn't matter if you don't understand it. I will translate it for you in my eight and a half levels of English.
We come in one sentence:
The compiler in the server VM now provides correct stack backtraces for all "cold" built-in exceptions.
For all built-in exceptions, the compiler can provide the correct exception stack traceback.
For performance purposes, when such an exception is thrown a few times, the method may be recompiled.
For performance reasons, when an exception is thrown several times, the method may be recompiled. (important)
After recompilation, the compiler may choose a faster tactic using preallocated exceptions that do not provide a stack trace.
After recompilation, the compiler may choose a faster strategy, that is, pre-allocated exceptions that do not provide exception stack traces. (important)
To disable completely the use of preallocated exceptions, use this new flag: -XX:-OmitStackTraceInFastThrow.
If you want to prohibit the use of pre-allocated exceptions, please use this new parameter: -XX:-OmitStackTraceInFastThrow.
Regardless of whether you understand these words. But at least know that the scene it describes here is not the scene just demonstrated by the code?
It finally mentions a parameter -XX:-OmitStackTraceInFastThrow
, apart from anything else, let’s use it first, let’s take a look at the effect and then talk about it:
The same code, after adding the startup parameter, the exception stack will indeed be printed from beginning to end.
I don't know if you feel it. After adding this startup parameter, the program running time is obviously much slower.
This parameter is not added on my machine, the program running time is 2826 ms, plus the parameter running time is 5885 ms.
Explain that there is indeed a function to improve performance.
How did it improve? The next section says.
Let me talk about other things first.
JVM parameters are mentioned here, I will share another website by the way:
https://club.perfma.com/topic/OmitStackTraceInFastThrow
The website provides many functions, here are a few of them:
The JVM parameter query function must have:
It's very easy to use. You can check it on this website if you encounter JVM parameters that you don't know what they are used for.
Why on earth?
As mentioned earlier, for performance reasons, the abnormal stack loss phenomenon will occur starting from JDK 5.
So where is the performance problem?
Let's take a look at the most common null pointer exception.
Take this article as an example, look at the call path when an exception is thrown:
Will eventually come to this native method:
java.lang.Throwable#fillInStackTrace(int)
fill In Stack Trace, as the name suggests, fill in the stack trace.
This method will crawl the stack, and this process is a relatively performance-consuming process.
Why is it time-consuming?
Let me show you a more intuitive one:
This kind of exception stack is more common to us. Does such a long stack information consume performance?
Now, let’s go back and read this sentence:
For performance purposes, when such an exception is thrown a few times, the method may be recompiled. After recompilation, the compiler may choose a faster tactic using preallocated exceptions that do not provide a stack trace.
For performance reasons, when an exception is thrown several times, the method may be recompiled. After recompilation, the compiler may choose a faster strategy, that is, pre-allocated exceptions that do not provide exception stack traces.
So, you can understand that the phrase "for performance considerations" specifically refers to saving the performance consumption of fillInStackTrace (stack climbing).
For a more in-depth research and comparison, you can take a look at this link:
http://java-performance.info/throwing-an-exception-in-java-is-very-slow
I will post the conclusion here:
Regarding the elimination of abnormal performance consumption, he proposed three solutions:
Refactor your code to not use them.
Cache exception instance.
Override the fillInStackTrace method.
Through the small day... the Japanese site where I had a good life, went online scientifically and entered the key information, this link on Zhihu ranked second:
https://www.zhihu.com/question/21405047
Below this question, there is a big R answer, paste it for you to see:
Everyone mentioned the rewrite of the fillInStackTrace method, this performance optimization trick, that is, we can customize the exception like this:
Test it in a less rigorous way, you just see what it means:
Override the fillInStackTrace method to directly return the object of this, which is not a bit faster than calling the original method of the stack climbing method.
In fact, in addition to rewriting the fillInStackTrace method, JDK 7 also provides such a method:
java.lang.Throwable#Throwable(java.lang.String, java.lang.Throwable, boolean, boolean)
You can use writableStackTrace to enter parameters to control whether you need to climb the stack.
So when should we use such a performance optimization method?
In fact, R's answer is very clear:
In fact, when we write business code, it is necessary to print exception information.
But for some frameworks that pursue performance, this advantage can be used.
For example, I found such optimized landing source code in the source code of disruptor and kafka.
Look at the disruptor first:
com.lmax.disruptor.AlertException
- Overridden so the stack trace is not filled in for this exception for performance reasons.
- For performance reasons, the stack trace after reloading will not be filled with this exception.
Look at kafka again:
org.apache.kafka.common.errors.ApiException
- avoid the expensive and useless stack trace for api exceptions
- Avoid expensive and useless stack traces of api exceptions
And have you noticed that in the above two frames, synchronized are all killed directly. If you also plan to rewrite, then you can also analyze whether you can remove synchronized in your scene, and the performance can be improved a bit.
In addition, R's answer also mentioned that this optimization is an optimization of C2.
We can simply prove it.
Hierarchical compilation
The aforementioned C2 actually has a corresponding C1. The C1 and C2 mentioned here are all just-in-time compilers.
If you are unfamiliar with C1 and C2, let me put it another way.
C1 is actually the Client Compiler, which is characterized by shorter compilation time but lower output code optimization.
C2 is actually Server Compiler, that is, the server-side compiler, which is characterized by long compilation time but higher quality of output code optimization.
The JVM that you often mention helps us do a lot of "radical" optimizations to improve performance, such as inlining, fast and slow path analysis, peephole optimization, including the "no exception stack display" mentioned in this article, all of which are done by C2. Things.
One more thing, in JDK 10, the Graal compiler was launched, the purpose of which is to replace C2.
As for why you want to replace C2, uh, one of the reasons is this...
http://icyfenix.cn/tricks/2020/graalvm/graal-compiler.html
The history of C2 has been very long. It can be traced back to the work of Cliff Click during his Ph.D. The compiler written in C++ is still effective, but it has become so complicated that even Cliff Click himself is unwilling to continue to maintain it.
Look at the features of C1 and C1 that I mentioned earlier, they just complement each other.
Therefore, in order to find a balance between program startup, response speed and program operating efficiency, after JDK 6, JVM has supported a mode called layered compilation.
It is also the root cause and theoretical support for everyone saying: "Java code will run faster and faster, and Java code needs to be warmed up".
Here, I quote the content of Section 7.2.1 [Layered Compilation] in the book "In-Depth Understanding of the Java Virtual Machine HotSpot", to let everyone briefly understand what this is.
First, we can use -XX:+TieredCompilation
enable hierarchical compilation, which introduces four additional compilation levels.
- Level 0: Interpretation and execution.
- Level 1: Compile C1, turn on all optimizations (without Profiling). Profiling is profiling.
- Level 2: C1 compilation, profiling information with call count and return edge count (restricted profiling).
- Level 3: C1 compilation, with all profiling information (full profiling).
- Level 4: C2 compilation.
The common hierarchical compilation level conversion path is shown in the figure below:
- 0→3→4: common level conversion. Compile completely with C1, if the subsequent method is executed frequently enough, then go to level 4.
- 0→2→3→4: C2 compiler is busy. First compile quickly at level 2, wait until enough profiling information is collected, then switch to level 3, and finally switch to level 4 when C2 is no longer busy.
- 0→3→1/0→2→1: Level 2/3 is converted to level 1 after compilation because the method is not important. If C2 cannot be compiled, it will go to level 1.
- 0→(3→2)→4: C1 compiler is busy, the compilation task can either wait for C1 or quickly shift to level 2, and then shift from level 2 to level 4.
If you didn't know about hierarchical compilation before, it doesn't matter, just have such a concept now. Don't take the exam in the interview, don't worry.
Next, we will mention a parameter:
-XX:TieredStopAtLevel=___
Look at the name, you know, the function of this parameter is to stop the hierarchical compilation at a certain layer, the default value is 4, which means to compile to C2.
Then if I change the value to 3, can I only use C1, then I can't use C2 to help me optimize exceptions?
Experiment wave:
Sure enough, R Dacheng did not deceive me.
Regarding hierarchical compilation, give such a brief introduction.
There is a lot of knowledge, if you are interested, you can go to research.
the above.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。