首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java finally 區塊神秘面紗(一):執行時機、異常情況、返回值陷阱與 JVM 實現原理

     編輯:Java 相關知識

finally 真的是「一定會執行」嗎?本文透過 4 個反直覺案例、官方規範原文、JVM 子程式插入機制、返回值保留槽原理,徹底揭開 finally 的真實面貌。

一、finally 不執行的 4 大場景

1. try 區塊前返回/拋異常


public static int test() {
    int i = 1;
    // return 0; // 若取消註解
    i = i / 0;  // 拋 ArithmeticException
    try { return i; } finally { System.out.println("finally"); }
}

結果:finally 不執行。原因:try 區塊根本未進入,對應的 finally 自然不執行。

2. try 區塊中呼叫 System.exit(0)


try {
    System.out.println("try");
    System.exit(0); // 終止 JVM
    return 1;
} finally {
    System.out.println("finally"); // 不執行
}

結果:JVM 直接終止,finally 無機會運行。

3. 執行緒被中斷/終止

執行 try/catch 期間,執行緒 interrupt() 或 stop()(已棄用),finally 可能不執行。

4. 硬體故障/斷電

極端物理環境,JVM 無法保證任何代碼執行。

官方規範原文證據

The Java Tutorials:
The finally block always executes when the try block exits. This ensures that the finally block is executed even if an unexpected exception occurs. But finally is useful for more than just exception handling — it allows the programmer to avoid having cleanup code accidentally bypassed by a return, continue, or break.

Note:If the JVM exits while the try or catch code is being executed, then the finally block may not execute. Likewise, if the thread executing the try or catch code is interrupted or killed, the finally block may not execute even though the application as a whole continues.

二、try-catch-finally 執行順序規範 (JLS)

JLS 12.4:
If a finally clause is present with a try, its code is executed after all other processing in the try is complete. This happens no matter how completion was achieved, whether normally, through an exception, or through a control flow statement such as return or break.

三、finally 執行時機:在 return/throw/break/continue 之前

案例 1:try 中 return


try { System.out.println("try"); return; } 
finally { System.out.println("finally"); }

輸出:try → finally。finally 在 return 前執行。

案例 2:catch 中 return


try { System.out.println("try"); int i = 1/0; return 1; }
catch (Exception e) { System.out.println("catch"); return 2; }
finally { System.out.println("finally"); }

輸出:try → catch → finally。finally 在 catch 的 return 前執行。

四、返回值陷阱:finally 中的 return 會覆蓋 try/catch 的返回值

陷阱 1:finally 直接 return


public static int getValue() {
    try { return 0; }
    finally { return 1; } // 覆蓋 try 的返回值
}

結果:返回 1。finally 的 return 截獲控制權並返回。

陷阱 2:finally 修改局部變數不影響返回值


public static int getValue() {
    int i = 1;
    try { return i; } // 返回值 1 已保存到局部變數表
    finally { i++; }  // 修改 i 為 2,但返回值槽已鎖定為 1
}

結果:返回 1(非 2)。

五、JVM 實現原理:子程式插入與返回值保留槽

編譯器策略

  1. 將 finally 代碼編譯為子程式(JVM jsr/ret 指令,現代編譯器多改為內聯複製)。
  2. 在 try/catch 的每個控制轉移點(return、throw、break、continue)插入子程式調用。

返回值保留槽

// 簡化邏輯
try {
    int result = compute(); // 計算返回值
    // 保存到局部變數表槽位
    localVarTable[slot] = result;
    // 調用 finally 子程式
    jsr finallySubroutine;
    // 從槽位恢復
    result = localVarTable[slot];
    ireturn; // 返回
} finally {
    // finally 代碼...
}
關鍵點:保留槽機制僅適用於 return/throw(有返回值/異常對象)。break/continue 無返回值,不觸發保留槽邏輯。

六、最佳實踐建議

系列導航

  1. 第一篇(本文):執行時機、不執行場景、返回值陷阱、JVM 子程式機制。
  2. 第二篇(規劃):try-with-resources 深度解析、鎖釋放模式、異常鏈保護、測試策略。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
場景建議
資源清理優先用 try-with-resources (JDK 7+),自動關閉,無 finally 陷阱
必須用 finally不要在 finally 中 return/throw/break/continue
修改返回值在 try/catch 完成計算後、finally 前賦值給最終變數,再返回
異常處理catch 區塊處理異常,finally 只做清理,不改變控制流