How does `try / catch` work in details

2019-02-09 11:59发布

I would like to the innards of how does try {} catch {} block and stack traces work.

I was reading this great article about exception handling anti-patterns and found the following paragraph:

catch (NoSuchMethodException e) {
  throw new MyServiceException("Blah: " +
      e.getMessage());
}

This destroys the stack trace of the original exception, and is always wrong.

After that I realized that I don't really know how try/catch works. My understanding is the following. Consider the example:

void top() {
    try {
        f();
    } catch (MyException ex) {
        handleIt(); 
    } finally {
        cleanup();
    }
}

void f() {
    g();
}

void g() {
    throw new MyException();
}

When I call top(), the call chain top -> f -> g leaves two stack frames on the call stack (for top and f functions). When the exception is raised in g, the program bubbles up the execution stack until it finds try/catch block that handles the exception. Meanwhile it frees the stack frames and attach stack trace information to some "magic" object that can be passed to catch and the stack trace can be printed.

How does it know that the called function is "surrounded" with the try/catch block? Is this information bound to the stack frame? Like, a pointer to error handling block (some switch selecting a matching catch block), and a pointer to finally block? Why e.getMessage() is destructive in the example above (see the comments)?

Note, I know how to use try/catch and exceptions, I want to know how does it work inside.

3条回答
Anthone
2楼-- · 2019-02-09 12:35

"How does it know that the called function is "surrounded" with the try/catch block?"

The code of each method contains Exception Table which describes all try-catch blocks of that method.

When a procedure (function, method) is called, the current stack frame is appended with the address of the calling instruction, so as to restore execution of that frame at the correct instruction (next after calling instruction).

When a throw statement is executed, the JVM examines each stack frame to find out if that frame can handle the exception. It can if its method contains a try-catch block which contains the calling instruction, and the type of block's exception is a supertype (or the same as) of the thrown exception. If such a frame is found, the frame restores its execution from the instruction pointed to from the try-catch block.

查看更多
对你真心纯属浪费
3楼-- · 2019-02-09 12:45

Lower level method just throws exception and we should handle them in upper level. Consider your example. It should like this

void top() {
try {
    f();
} catch (MyException ex) {
    handleIt(); 
} finally {
    cleanup();
 }
}

void f() throws MyException {
try{
   g();
}catch(MyException e){
  throws new MyException("Error in g()",e); 
}

}

void g() throws MyException{
throw new MyException();
}
查看更多
Juvenile、少年°
4楼-- · 2019-02-09 12:47

When an exception is thrown, the complete calling-stack information is attached not to some magic object, but to the exception object that is created. This doesn't happen while the exception "bubbles up" - it happens when it is created and it always contains the full call-chain.

The called function doesn't need to know it is surrounded by a try-catch-block, it just creates an Exception-object that contains the call-chain and passes it up to the calling method. This method has to decide wether it handles the Exception because it is caught by some catch-clause or if it passes it further up. Exception that aren't caught bubble up till they reach the top of the calling-chain and the VM handles them - usually by printing the stack-trace and terminating.

Regarding the e.getMessage-example: The full stack-information is contained only in the original exception. In the given example that original Exception object e is discarded, only the contained message is passed on to a newly created Exception-object. And that Exception only "knows" its own calling stack, so the original information attached to e is lost.

查看更多
登录 后发表回答