【问题标题】:How does `try / catch` work in details`try/catch` 是如何工作的
【发布时间】:2013-11-14 22:25:48
【问题描述】:

我想了解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(用于topf 函数)。当g 中引发异常时, 程序在执行堆栈中冒泡,直到找到处理异常的try/catch 块。同时它释放堆栈帧并将堆栈跟踪信息附加到一些可以传递给catch 的“魔术”对象,然后可以打印堆栈跟踪。

它如何知道被调用的函数被 try/catch 块“包围”了?此信息是否绑定到堆栈帧?比如,指向错误处理块的指针(一些开关选择匹配的catch 块),以及指向finally 块的指针?为什么e.getMessage() 在上面的示例中具有破坏性(参见 cmets)?

注意,我知道如何使用 try/catch 和异常,我想知道它在内部是如何工作的。

【问题讨论】:

  • 嗯,你说得对,我没注意到throw :) 但剩下的问题仍然有效

标签: java exception exception-handling stack


【解决方案1】:

当抛出异常时,完整的调用堆栈信息不是附加到某个魔术对象上,而是附加到创建的异常对象上。这不会在异常“冒泡”时发生 - 它在创建时发生,并且始终包含完整的调用链。

被调用函数不需要知道它被try-catch-block包围,它只是创建一个包含调用链的异常对象并将其传递给调用方法。这个方法必须决定它是否处理异常,因为它被一些捕获子句捕获,或者它是否进一步向上传递。直到到达调用链的顶部并且 VM 处理它们时才被捕获的异常 - 通常通过打印堆栈跟踪并终止。

关于e.getMessage-示例: 完整的堆栈信息仅包含在原始异常中。在原始异常对象 e 被丢弃的给定示例中,仅将包含的消息传递给新创建的异常对象。而那个 Exception 只“知道”它自己的调用栈,所以附加到 e 的原始信息丢失了。

【讨论】:

    【解决方案2】:

    低层方法只是抛出异常,我们应该在高层处理它们。考虑你的例子。应该是这样的

    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】:

      “它怎么知道被调用的函数被 try/catch 块“包围”了?”

      每个方法的代码都包含Exception Table,它描述了该方法的所有try-catch 块。

      当一个过程(函数、方法)被调用时,当前堆栈帧会附加调用指令的地址,以便在正确的指令(调用指令的下一条)处恢复该帧的执行。

      当一个 throw 语句被执行时,JVM examines each stack frame 会查明该帧是否可以处理异常。如果它的方法包含一个包含调用指令的try-catch块,并且块的异常类型是抛出异常的超类型(或相同),则可以。如果找到这样的帧,则该帧将从 try-catch 块指向的指令恢复其执行。

      【讨论】:

        【解决方案4】:

        异常从最初抛出它的方法向上传播到调用堆栈,直到调用堆栈中的方法捕获它。如果方法 A 调用 B,而 B 调用 C,则调用堆栈如下所示:

        A
        B
        C
        

        当方法 C 返回时,调用堆栈只包含 A 和 B。 异常从最初抛出它的方法向上传播到调用堆栈,直到调用堆栈中的方法捕获它。

        当抛出异常时,方法会在“throw”语句之后立即停止执行。 “抛出”语句之后的任何语句都不会执行。当“catch”块在某处捕获到异常时,程序恢复执行。

        如果一个方法调用另一个抛出检查异常的方法,调用方法将被强制传递异常或捕获它。捕获异常是使用 try-catch 块完成的。如果在 try-block 内调用的任何方法或执行的语句都没有抛出异常,则简单地忽略 catch-block。如果在 try-block 内抛出异常,例如从divide方法开始,调用方法callDivide的程序流程就像divide里面的程序流程一样被中断。程序流在调用堆栈中可以捕获抛出的异常的捕获块处恢复。

        如果在 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/

        【讨论】:

          猜你喜欢
          • 2012-05-26
          • 2012-11-12
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-09-23
          相关资源
          最近更新 更多