一、背景
對于很多初學者而言,會想當然地認為 “finally 代碼塊一定會被執行”,是以我們可以看下面這個案例:
public class Demo {
public static void main(String[] args) {
try {
BufferedReader br = new BufferedReader(new FileReader("file.txt"));
System.out.println(br.readLine());
br.close();
} catch (IOException e) {
// 省略一些代碼
} finally {
System.out.println("Exiting the program");
}
}
}
問題是:該段代碼 finally 的代碼塊一定會被執行嗎?為什麼?
二、分析
通常實際編碼時,捕獲異常後會記錄日志或者将異常抛出等,此時 finally 代碼塊一般肯定會被執行到。
那麼如何才能不執行finally呢?
于是我們想到,如果讓虛拟機退出,問題不就解決了嗎? (就是這麼暴力)
是以填充代碼:
System.exit(2);
如果捕獲到 IO異常,則會執行 虛拟機退出指令,則不會執行finally 代碼塊。
System#exit 的源碼如下:
/**
* Terminates the currently running Java Virtual Machine. The
* argument serves as a status code; by convention, a nonzero status
* code indicates abnormal termination.
* <p>
* This method calls the <code>exit</code> method in class
* <code>Runtime</code>. This method never returns normally.
* The call <code>System.exit(n)</code> is effectively equivalent to
* the call:
* <blockquote><pre>
* Runtime.getRuntime().exit(n)
* </pre></blockquote>
*
* @param status exit status.
* @throws SecurityException
* if a security manager exists and its <code>checkExit</code>
* method doesn't allow exit with the specified status.
* @see java.lang.Runtime#exit(int)
*/
public static void exit(int status) {
Runtime.getRuntime().exit(status);
通過注釋我們可以了解到, 當 status 為 非0 時,表示異常退出。
底層調用到 Runtime#exit:
* Terminates the currently running Java virtual machine by initiating its
* shutdown sequence. This method never returns normally. The argument
* serves as a status code; by convention, a nonzero status code indicates
* abnormal termination.
* <p> The virtual machine's shutdown sequence consists of two phases. In
* the first phase all registered {@link #addShutdownHook shutdown hooks},
* if any, are started in some unspecified order and allowed to run
* concurrently until they finish. In the second phase all uninvoked
* finalizers are run if {@link #runFinalizersOnExit finalization-on-exit}
* has been enabled. Once this is done the virtual machine {@link #halt
* halts}.
* <p> If this method is invoked after the virtual machine has begun its
* shutdown sequence then if shutdown hooks are being run this method will
* block indefinitely. If shutdown hooks have already been run and on-exit
* finalization has been enabled then this method halts the virtual machine
* with the given status code if the status is nonzero; otherwise, it
* blocks indefinitely.
* <p> The <tt>{@link System#exit(int) System.exit}</tt> method is the
* conventional and convenient means of invoking this method. <p>
* @param status
* Termination status. By convention, a nonzero status code
* indicates abnormal termination.
* @throws SecurityException
* If a security manager is present and its <tt>{@link
* SecurityManager#checkExit checkExit}</tt> method does not permit
* exiting with the specified status
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkExit(int)
* @see #addShutdownHook
* @see #removeShutdownHook
* @see #runFinalizersOnExit
* @see #halt(int)
public void exit(int status) {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExit(status);
Shutdown.exit(status);
三、延伸
同樣的問題,請看下面代碼片段:
// 一些代碼
問題是:如果try 代碼塊部分發生IO異常,是否一定不會執行到 finally 代碼塊呢?
what? 上面不是說不會執行嗎?
我們再仔細看上面給出的 Runtime#exit 源碼,可以發現,如果 SecurityManager 不為 null ,則 會進行安全檢查。
public void exit(int status) {
// 如果有securityManager , 則調用 checkExit函數
// 檢查通過後退出
安全檢查通過才會執行 Shutdown#exit 執行最終的虛拟機退出。
是以如果我們可以修改 SecurityManager 如果檢查退出時抛出異常,那麼在 執行 System.exit(2) 時就會發生異常,最終依然會執行到 finally代碼塊。
// 修改 SecurityManager
System.setSecurityManager(new SecurityManager() {
@Override
public void checkExit(int status) {
throw new SecurityException("不允許退出");
}
});
System.exit(2);
四、總結
學習時一定要抱着不滿足的心态,這樣才能有機會學的更加深入,了解地更好。