天天看點

Java 的 finally 代碼塊的代碼一定會執行嗎?

一、背景

對于很多初學者而言,會想當然地認為 “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);

四、總結

學習時一定要抱着不滿足的心态,這樣才能有機會學的更加深入,了解地更好。