Java基礎之try catch finally的原理

「本文已參與好文召集令活動,點擊查看:後端、大前端雙賽道投稿,2萬元獎池等你挑戰!」前端

簡介

java作業務開發同窗常常會使用try catch finally捕捉異常,而使用起來有還有三種組合,try finally 和 try catch 和 try catch finally。java

示例

  • try finally的用法以下
try{
            System.out.println("業務執行");
            int i =  1 / 0;
            System.out.println("業務執行完成");
          }finally {
            System.out.println("釋放資源");
          }

複製代碼

有的同窗可能就要問了,寫了try不寫catch,還有這樣寫的嗎? 這樣寫的意義是啥???後端

  • try catch
try{
           System.out.println("業務執行");
           int i =  1 / 0;
           System.out.println("業務執行完成");
         }catch (Exception e){
           System.out.println("捕捉異常");
          }

複製代碼
  • try catch finally 這種通常是咱們平時用的作多寫法markdown

    try {
          System.out.println("業務執行");
        } catch (Exception e) {
            System.out.println("捕捉異常");
        } finally {
            System.out.println("釋放資源");
        }
    複製代碼

    既然java的語法支持這樣寫,那麼就有它的用法。在這裏我貼一段大名鼎鼎的線程池 的執行Work線程的代碼,這裏讓大家看下這樣的用法在java基礎框架中被大量運用 ,只是咱們只是還知道罷了。框架

final void runWorker(Worker w) {
       Thread wt = Thread.currentThread();
       Runnable task = w.firstTask;
       w.firstTask = null;
       w.unlock(); // allow interrupts
       boolean completedAbruptly = true;
       try {
           while (task != null || (task = getTask()) != null) {
               w.lock();
               // If pool is stopping, ensure thread is interrupted;
               // if not, ensure thread is not interrupted.  This
               // requires a recheck in second case to deal with
               // shutdownNow race while clearing interrupt
               if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                     runStateAtLeast(ctl.get(), STOP))) &&
                   !wt.isInterrupted())
                   wt.interrupt();
               try {
                   beforeExecute(wt, task);
                   try {
                       task.run();
                       afterExecute(task, null);
                   } catch (Throwable ex) {
                       afterExecute(task, ex);
                       throw ex;//拋出異常
                   }
                   //這裏沒有finnaly 由於沒有最後必定須要釋放的資源
               } finally {
                // 這裏沒有catch異常,主要緣由是爲後面的
                // completedAbruptly變量用來判斷執行任務是否拋出異常
               //1.若是上面的代碼拋出了異常,那麼必定是程序裏層的try部分                    // 裏task執行,捕捉的異常,而後拋出的異常,此時程序是走不到
                //completedAbruptly = false;這一行,由於外層try是
               // 沒有加catch異常的,
                //2.若是程序能completedAbruptly=false這一行,那麼此時
                //程序外層try住部分是沒有異常發生的。表明執行任務正常狀況。
                   task = null;
                   w.completedTasks++;
                   w.unlock();
               }
           }
           completedAbruptly = false;
       } finally {
           processWorkerExit(w, completedAbruptly);
       }
   }

複製代碼

關於try catch finally 中 return的執行原理

public static int test() {
       int i = 0;
       try {
           System.out.println("業務執行");
           i = 1 / 0;
           System.out.println("業務執行完成");
           i++;
           return i;
       } catch (Exception e) {
           System.out.println("捕捉異常");
           i++;
           return i;
       } finally {
           System.out.println("釋放資源");
           i++;
           return i;
       }
   }
複製代碼

執行結果以下:函數

image.png

下面是上面的函數javap反編譯出來的JVM的字節碼,因爲指令比較長,我會加好註釋方便理解ui

public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=0
         0: iconst_0       // 將常量0推送到操做數棧            
         1: istore_0    // 將操做數棧頂元素(這裏是0) 存儲到局部變量表的0的位置(注意: 這裏局部變量表0通常是存放是this指針,因爲這裏是static方法,因此沒有this指針)
         2: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream; // 獲取 PrintStream的靜態變量
         5: ldc           #25                 // String 業務執行 
         7: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V    //調用打印函數
        10: iconst_1  // 將常量 1 推送到操做數棧頂
        11: iconst_0  // 將常量 0 推送到操做數棧頂
        12: idiv    // 取出操做數棧兩個數進行除法運算
        13: istore_0  //將運算結果保存到函數的局部變量表中第0個位置。
        14: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;  
        17: ldc           #30                 // String "業務執行完成"常量推送到操做數棧頂
        19: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V    //pop操做數頂元素,調用打印函數,
        22: iinc          0, 1             // 執行局部變量表中索引0號位置數據加1
        25: iload_0                        // 執行局部變量表中索引0號位置數據推送到操做數棧
        26: istore_1                       // 將操做數棧頂元素保存到局部變量表的1號位置
        27: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: ldc           #32                 // String 釋放資源
        32: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V   調用函數打印"釋放資源"
        35: iinc          0, 1          // 執行局部變量表中索引0號位置數據加1
        38: iload_0                     // 執行局部變量表中索引0號位置數據推送到操做數棧
        39: ireturn                     // 執行返回
        40: astore_1                   // 將操做數棧頂元素保存到局部變量表索引爲1的位置
        41: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        44: ldc           #36                 // String 捕捉異常
        46: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        49: iinc          0, 1             // 執行局部變量表中索引0號位置數據加1
        52: iload_0                        // 執行局部變量表中索引0號位置數據推送到操做數棧
        53: istore_2                        // 將操做數棧頂元素保存到局部變量表索引爲2的位置
        54: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        57: ldc           #32                 // String 釋放資源
        59: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V    打印函數
        62: iinc          0, 1   // 執行局部變量表中索引0號位置數據加1
        65: iload_0             // 執行局部變量表中索引0號位置數據推送到操做數棧
        66: ireturn              //執行返回    
        67: astore_3             // 將操做數棧頂元素保存到局部變量表索引爲2的位置
        68: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        71: ldc           #32                 // String 釋放資源
        73: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V     
        76: iinc              0, 1   // 執行局部變量表中索引0號位置數據加1
        79: iload_0           // 執行局部變量表中索引0號位置數據推送到操做數棧
        80: ireturn          //執行返回操做數棧的元素
      Exception table:
         from    to  target type
             2    27    40   Class java/lang/Exception
             2    27    67   any
            40    54    67   any
      LineNumberTable:
        line 27: 0
        line 29: 2
        line 30: 10
        line 31: 14
        line 32: 22
        line 33: 25
        line 39: 27
        line 40: 35
        line 41: 38
        line 34: 40
        line 35: 41
        line 36: 49
        line 37: 52
        line 39: 54
        line 40: 62
        line 41: 65
        line 39: 67
        line 40: 76
        line 41: 79
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           41      26     1     e   Ljava/lang/Exception;
            2      79     0     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 40
          locals = [ int ]
          stack = [ class java/lang/Exception ]
        frame_type = 90 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]

複製代碼

執行結果是 2,因爲拋出異常因此try塊裏的i++沒有執行,首先執行catch的i++,而後 執行finally的i++,而後執行返回,而catch的return從字節碼裏面都是是忽略的,由於 finally已經有return了 那麼一樣的程序,將finally中return去掉會怎麼樣this

public static int test() {
        int i = 0;
        try {
            System.out.println("業務執行");
            i = 1 / 0;
            System.out.println("業務執行完成");
            i++;
            return i;
        } catch (Exception e) {
            System.out.println("捕捉異常");
            i++;
            return i;
        } finally {
            System.out.println("釋放資源");
            i++;
        }
    }
複製代碼

執行結果變成了1,這是爲何呢?spa

public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=0
         0: iconst_0
         1: istore_0
         2: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         5: ldc           #25                 // String 業務執行
         7: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        10: iconst_1
        11: iconst_0
        12: idiv
        13: istore_0
        14: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        17: ldc           #30                 // String 業務執行完成
        19: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        22: iinc          0, 1
        25: iload_0
        26: istore_1
        27: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        30: ldc           #32                 // String 釋放資源
        32: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        35: iinc          0, 1
        38: iload_1
        39: ireturn
        40: astore_1
        41: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        44: ldc           #36                 // String 捕捉異常
        46: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        49: iinc          0, 1
        52: iload_0
        53: istore_2
        54: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        57: ldc           #32                 // String 釋放資源
        59: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        62: iinc          0, 1
        65: iload_2
        66: ireturn
        67: astore_3
        68: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
        71: ldc           #32                 // String 釋放資源
        73: invokevirtual #27                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        76: iinc          0, 1
        79: aload_3
        80: athrow
      Exception table:
         from    to  target type
             2    27    40   Class java/lang/Exception
             2    27    67   any
            40    54    67   any

複製代碼

只是由於是因爲try裏面的return沒有執行到,那麼最終執行return由catch中執行返回, 從上圖的字節碼指令,catch的執行是將本地變量表中索引位置爲1的位置加1後,而後load 到操做數棧,最後store到本地變量表的索引爲2的位置。線程

image.png

而在執行finally裏面的執行時索引變量0的索引此時是1加1,變成2,而最後執行會執行ireturn是前的操做,是從iload_2是本地變量表的2號位置拿的返回值,因此finally 裏面的執行i++,只是改變了本地變量表的0號位置值,並不改變返回值。 image.png

總結

本文主要總結java基礎try catch finally的執行原理的過程,但願以後本身寫代碼和看別人的代碼,能明白其中道理。

相關文章
相關標籤/搜索