我想了解try {} catch {}
块和堆栈跟踪如何工作的内部结构。
我正在阅读this great article about exception handling anti-patterns并找到以下段落:
catch (NoSuchMethodException e) { throw new MyServiceException("Blah: " + e.getMessage()); }
这会破坏原始异常的堆栈跟踪,并且总是错误的。
之后我意识到我确实知道try/catch
是如何工作的。我的理解如下。考虑一下这个例子:
void top() {
try {
f();
} catch (MyException ex) {
handleIt();
} finally {
cleanup();
}
}
void f() {
g();
}
void g() {
throw new MyException();
}
当我致电top()
时,调用链top -> f -> g
在调用堆栈上留下两个stack frames(用于top
和f
函数)。在g
中引发异常时,
程序冒泡执行堆栈,直到找到处理异常的try/catch
块。同时它释放堆栈帧并将堆栈跟踪信息附加到一些可以传递给catch
的“魔术”对象,并且可以打印堆栈跟踪。
怎么知道被调用的函数被try / catch块“包围”了?此信息是否绑定到堆栈帧?比如,指向错误处理块的指针(某些开关选择匹配的catch
块)和指向finally
块的指针?为什么e.getMessage()
在上面的示例中具有破坏性(请参阅注释)?
注意,我知道如何使用try / catch和异常,我想知道它是如何工作的在中。
答案 0 :(得分:8)
“怎么知道被调用的函数被try / catch块”包围“了?”
每个方法的代码都包含Exception Table,它描述了该方法的所有try-catch块。
当调用过程(函数,方法)时,当前堆栈帧附加调用指令的地址,以便在正确的指令(调用指令后的下一个)恢复该帧的执行。
执行throw语句时,JVM examines each stack frame将查找该帧是否可以处理异常。它可以包含一个包含调用指令的try-catch块,并且块的异常类型是抛出异常的超类型(或相同)。如果找到这样的帧,则帧将从try-catch块指向的指令恢复其执行。
答案 1 :(得分:3)
抛出异常时,完整的调用堆栈信息不会附加到某个魔术对象,而是附加到创建的异常对象。当异常“冒泡”时不会发生这种情况 - 它在创建时发生并且它总是包含完整的调用链。
被调用的函数不需要知道它被try-catch-block包围,它只是创建一个包含调用链的Exception对象并将其传递给调用方法。此方法必须决定它是否处理异常,因为它被某些catch子句捕获或者如果它进一步向上传递它。在它们到达调用链的顶部并且VM处理它们之前没有被捕获的异常 - 通常通过打印堆栈跟踪并终止。
关于e.getMessage
- 示例:
完整的堆栈信息仅包含在原始异常中。在给定示例中,丢弃原始异常对象e,仅将包含的消息传递给新创建的Exception对象。而且,Exception只“知道”自己的调用堆栈,因此附加到e的原始信息将丢失。
答案 2 :(得分:0)
低级方法只抛出异常,我们应该在上层处理它们。考虑你的例子。它应该是这样的
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();
}
答案 3 :(得分:0)
异常会从最初引发该异常的方法一直传播到调用堆栈,直到调用堆栈中的某个方法捕获到该异常为止。如果方法A调用B,而方法B调用C,则调用堆栈如下所示:
A
B
C
方法C返回时,调用堆栈仅包含A和B。 异常会从最初引发异常的方法一直传播到调用堆栈,直到调用堆栈中的方法捕获到异常为止。
引发异常时,该方法将在“ throw”语句之后立即停止执行。 “ throw”语句之后的任何语句都不会执行。当异常被“ catch”块捕获到某个地方时,程序将恢复执行。
如果某个方法调用另一个引发已检查异常的方法,则该调用方法将被强制传递该异常或将其捕获。捕获异常是通过try-catch块完成的,如果在try块内执行的任何调用的方法或语句未引发任何异常,则catch块将被忽略。 ,例如从除法中,调用方法callDivide的程序流被中断,就像除法中的程序流一样。程序流在可以捕获引发的异常的调用堆栈中的catch块处恢复。
如果在catch块中引发了异常并且未捕获到该异常,则catch块将被中断,就像try块一样。
http://tutorials.jenkov.com/java-exception-handling/basic-try-catch-finally.html https://www.geeksforgeeks.org/flow-control-in-try-catch-finally-in-java/