4
头图

Hello, I am crooked.

For example, the following reader:

He was watching my "God!" Abnormal information disappeared suddenly? " after this article.

Since reading my article brought further thoughts, it just so happens that I just know.

Although few people read this kind of articles, I'm still here to fill in a hole.

Harm, it's really a warm boy.

How the exception was thrown.

Let's start with a simple code snippet:

Everyone is very familiar with the results of the operation.

Just looking at these few lines of code, we can't find anything valuable.

We all know that the result of the operation is like this, there is nothing wrong with it.

This is knowingly.

So why?

Therefore, it is hidden in the bytecode behind the code.

After compiling by javap, the bytecode of the above code is like this:

We mainly focus on the following parts. I will also comment on the meaning of bytecode instructions later:

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1 //将int型的1推送至栈顶
       1: iconst_0 //将int型的0推送至栈顶
       2: idiv     //将栈顶两int型数值相除并将结果压入栈顶
       3: istore_1 //将栈顶int型数值存入第二个本地变量
       4: return   //从当前方法返回 void

Don't ask me how I know the meaning of bytecodes, just look at the table, who can remember this stuff.

Through the bytecode, it seems that there is no mystery.

However, remember this first, and I will show you a transformation right away:

public class MainTest {

    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Wrap the code with try-catch to catch the exception.

After compiling with javap again, the bytecode becomes like this:

It can be clearly seen that the bytecode has changed, at least it has become longer.

Mainly focus on the part I framed.

Let's compare the bytecodes of the two cases:

The comparison makes it clear that after adding try-catch, there are many original bytecode instructions on one line.

What is not framed is the extra bytecode instructions.

The extra part, which is called Exception table, is particularly obvious:

.png)

The exception table, this thing, is used by the JVM to handle exceptions.

As for the meaning of each parameter here, we directly bypass the "second-hand" information on the Internet and find the document on the official website:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.3

It seems that there is a lot of English and it’s very stressful, but don’t be afraid, there is me, I will pick the key ones to say to you:

First, start_pc and end_pc are a pair of parameters, corresponding to from and to in the Exception table, indicating the coverage of the exception.

For example, the previous from is 0 and to is 4, and the bytecode index of the abnormal coverage represented is this range:

0: iconst_1 //将int型的1推送至栈顶
1: iconst_0 //将int型的0推送至栈顶
2: idiv     //将栈顶两int型数值相除并将结果压入栈顶
3: istore_1 //将栈顶int型数值存入第二个本地变量

There is a detail, I don’t know if you noticed it.

The range does not contain 4, and the range interval is like this [start_pc, end_pc).

As for why end_pc is not included, this is a bit interesting.

Take it out and talk about it.

The fact that end_pc is exclusive is a historical mistake in the design of the Java Virtual Machine: if the Java Virtual Machine code for a method is exactly 65535 bytes long and ends with an instruction that is 1 byte long, then that instruction cannot be protected by an exception handler. A compiler writer can work around this bug by limiting the maximum size of the generated Java Virtual Machine code for any method, instance initialization method, or static initializer (the size of any code array) to 65534 bytes.

Not including end_pc is a historical error in the JVM design process.

Because if the compiled code of a method in the JVM is exactly 65535 bytes long and ends with a 1-byte instruction, then the instruction cannot be protected by the exception handling mechanism.

Compiler authors can solve this error by limiting the maximum length of code generated by any method, instance initialization method, or static initializer.

The above is the explanation of the official website, anyway, it seems to understand but not understand.

It doesn't matter, just run an example to know:

When there is only one method in my code and the length is 16391 lines, the length of the compiled bytecode is 65532.

Through the previous analysis, we know that one line of a=1/0 code will be compiled into 4 lines of bytecode.

So as long as I add another line of code, the limit will be exceeded. What problems will occur when the code is compiled at this time?

Look at the picture:

Direct compilation fails, telling you that the code is too long.

So you now know a knowledge point: the length of a method is limited from the bytecode level. But this limit is relatively large, and normal people cannot write codes of this length.

Although this knowledge point is not very useful, if you really encounter a method with thousands of lines in length at work, even if the bytecode length limit is not triggered, I will give you a word: run.

Then talk about the next parameter handler_pc, which corresponds to the target in the Exception table.

In fact, it is very easy to understand, which refers to the index corresponding to the instruction at the beginning of the exception handler.

For example, the target here is 7, which corresponds to the astore_1 instruction:

.png)

That is to tell the JVM, if an exception occurs, please start processing from here.

Finally, look at the catch_type parameter, which corresponds to the type in the Exception table.

Here is the exception caught by the program.

For example, I modified the program to this, catching three types of exceptions:

Then the types that the exception table corresponding to the compiled bytecode can handle become these three:

As for why I can't write a String here?

Don't ask, ask is the grammar.

What are the specific grammar rules?

Right here in the exception table:

The compiler will check whether the class is Throwable or a subclass of Throwable.

I won’t go into details about Throwable, Exception, Error, RuntimeException, just generate an inheritance diagram for everyone to see:

So, summarize the above news:

  • from: The starting point instruction index subscript at which an exception may occur (inclusive)
  • to: The index subscript of the end point instruction where an exception may occur (not included)
  • target: In the range of from and to, after an exception occurs, the index index of the instruction to start processing the exception
  • type: the exception type information that can be handled by the current scope

After knowing the exception table, you can answer this question: How is the exception thrown?

JVM throws it out for us through the exception table.

What's in the exception table?

I said earlier, so I won't repeat it.

How to use the exception table?

Briefly describe:

1. If an exception occurs, the JVM will look for the exception table in the current method to see if the exception is caught.
2. If an exception is matched in the exception table, call the instruction corresponding to the index subscript of target to continue execution.

Okay, then the problem comes again. What if the exception is not matched?

I found the answer here in the official website document:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.12

Its sample code is like this:

Then there is such a description below:

This means that if the thrown value does not match the parameters of any of the catch clauses of catchTwo, the Java virtual machine will rethrow the value without calling the code in any of the catch clauses of catchTwo.

What's the meaning?

To put it bluntly, I can't handle it anyway, I will throw the exception to the caller.

This is common sense in programming, of course everyone knows it.

But when common sense things are displayed in front of you in such a normative description, it feels quite amazing.

When someone asks you why this is the calling process, you say that this is a rule.

When someone asks you where the regulations are, you can take out the official website documents and throw them on his face, pointing and saying: This is it.

Although, it seems to be useless.

A slightly special case

Let’s briefly introduce the situation of finally:

public class MainTest {
   public static void main(String[] args) {
       try {
           int a = 1 / 0;
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           System.out.println("final");
       }
   }
}

After javap compilation, three records appeared in the exception table:

The first recognition is the exception that we actively caught.

The second and third items are any, what is this?

The answer is here:

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-3.html#jvms-3.13

It mainly depends on where I draw the line:

A try statement with a finally clause is compiled to have a special exception handler, which can handle (any) any exception thrown in the try statement.

All, to translate the above exception table is:

  • If an exception of type Exception occurs between the instructions from 0 to 4, the instruction with index 15 is called to start processing the exception.
  • If between the instructions from 0 to 4, no matter what exception occurs, the instruction with index 31 is called (the place where the finally code block starts)
  • If between 15 and 20 instructions (that is, the catch part), no matter what exception occurs, the instruction with index 31 is called.

Next, let's focus on this part:

How is it, did you find it? Just ask you if God is magical?

In the source code, output statements that appear only once in the finally code block appear three times in the bytecode.

The code in the finally code block has been copied twice and placed after the try and catch statements respectively. Together with the exception table, you can achieve the effect that the finally statement will be executed.

In the future, you will never be afraid of the interviewer asking you why you will finally execute.

Although there should be no interviewer who would ask such boring questions.

After asking, let him analyze the wave from the perspective of bytecode.

Of course, if you have to give me a System.exit and talk about the situation of 060ff92df95b9a, it doesn't make much sense.

Finally, about finally, let’s discuss this scenario again:

public class MainTest {
    public static void main(String[] args) {
        try {
            int a = 1 / 0;
        } finally {
            System.out.println("final");
        }
    }
}

In this scenario, there is nothing to say, an exception is thrown in try, which triggers the finally output statement, and then it is thrown out and printed on the console:

What if I add a return to finally?

As you can see, no exceptions were thrown in the running results:

why?

The answer lies in the bytecode:

In fact, it is already clear at a glance.

There is return in the finally on the right, and there is no athrow instruction, so the exception is not thrown at all.

This is one of the reasons why it is recommended that you do not write return in the finally statement.

Trivia

Let me add another cold knowledge about anomalies.

Still the screenshot above. Do you feel a little strange?

In the dead of night, have you ever thought about such a question:

There is no place to print logs in the program, so who printed the days of the console through where?

Who did it?

This question is easy to answer, and you can guess it. The JVM helped us.

Where?

The answer to this question is hidden in this place in the source code. I will give you a breakpoint and run it. Of course, I suggest that you also run it with a breakpoint:

java.lang.ThreadGroup#uncaughtException

Put a breakpoint in this place, and follow the call stack to find this place:

java.lang.Thread#dispatchUncaughtException

See the notes on the method:

This method is intended to be called only by the JVM.

The translation is: this method can only be called by the JVM.

Since the source code says so, we can look for the corresponding source code.

https://hg.openjdk.java.net/jdk7u/jdk7u/hotspot/file/5b9a416a5632/src/share/vm/runtime/thread.cpp

In the source code of thread.cpp of openJdk, the place where this method is called is indeed found:

And this method has an interesting usage.

Look at the following program and output results:

We can customize the UncaughtExceptionHandler current thread and do some tricks in it.

Do you have a taste of the global exception handling mechanism?

Okay, come to the last question:

I have asked this, so the answer is definitely not necessarily.

Just think about it, use your little head to think hard, under what circumstances the code inside try throws an exception, and the outside catch will not catch it?

Come, look at the picture:

Didn't expect it?

Deal with it this way, the outside catch will not catch the exception.

Do you really want to hit me.

Don't panic, the dolls above are so boring.

Take a look at my code:

public class MainTest {
    public static void main(String[] args) {
        try {
            ExecutorService threadPool = Executors.newFixedThreadPool(1);
            threadPool.submit(()->{
               int a=1/0;
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

You can execute it directly, and there will be no output from the console.

Look at the animation:

Isn’t it amazing?

Don't panic, there are even more absolutes.

Modify the above code from threadPool.submit threadPool.execute and the exception information will be printed out:

But if you look carefully, you will find that although the exception information is printed, it is not because of the existence of the catch code block.

What's the specific reason?

Refer to this article, I have talked about it in detail before: "I will say this interview question about throwing exceptions in multithreading for the last time! 》

One last word

Okay, see here to arrange a like. Thank you for reading, I insist on originality, very welcome and thank you for your attention.


why技术
2.2k 声望6.8k 粉丝