<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          寫(xiě)那么多BUG,你知道異常是怎么被拋出來(lái)的嗎?

          共 20040字,需瀏覽 41分鐘

           ·

          2024-11-29 18:01

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你關(guān)注,我們一起精進(jìn)!你星標(biāo),我們便有了更多故事!

          編輯:業(yè)余草

          來(lái)源:juejin.cn/post/7442232749606731828

          推薦:https://t.zsxq.com/rpgBj

          引言

          Java 虛擬機(jī)里面的異常使用 Throwable 或其子類的實(shí)例來(lái)表示,拋異常的本質(zhì)實(shí)際上是程序控制權(quán)的一種即時(shí)的、非局部(Nonlocal)的轉(zhuǎn)換——從異常拋出的地方轉(zhuǎn)換至處理異常的地方。絕大多數(shù)的異常的產(chǎn)生都是由于當(dāng)前線程執(zhí)行的某個(gè)操作所導(dǎo)致的,這種可以稱為是同步的異常。與之相對(duì)的,異步異常是指在程序的其他任意地方進(jìn)行的動(dòng)作而導(dǎo)致的異常。

          Java 虛擬機(jī)中異常的出現(xiàn)總是由下面三種原因之一導(dǎo)致的:

          1、虛擬機(jī)同步檢測(cè)到程序發(fā)生了非正常的執(zhí)行情況,這時(shí)異常將會(huì)緊接著在發(fā)生非正常執(zhí)行情況的字節(jié)碼指令之后拋出。

          • 字節(jié)碼指令所蘊(yùn)含的操作違反了 Java 語(yǔ)言的語(yǔ)義,如訪問(wèn)一個(gè)元素。
          • 類在加載或者鏈接時(shí)出現(xiàn)錯(cuò)誤。
          • 使用某些資源的時(shí)候產(chǎn)生資源限制,例如使用了太多的內(nèi)存

          2、athrow 字節(jié)碼指令被執(zhí)行。

          3、由于以下原因,導(dǎo)致了異步異常的出現(xiàn):

          • 調(diào)用了 Thread 或者 ThreadGroup 的
          • Java 虛擬機(jī)實(shí)現(xiàn)的內(nèi)部程序錯(cuò)誤。

          理解異常

          Java 異常的底層實(shí)現(xiàn)涉及到編譯器和虛擬機(jī)(JVM)兩個(gè)層面。包括編譯器如何處理異常代碼以及虛擬機(jī)如何在運(yùn)行時(shí)處理異常。

          編譯器層面

          示例

          try {
              // 可能引發(fā)異常的代碼
          catch (SomeException e) {
              // 處理 SomeException 的代碼
          finally {
              // 無(wú)論是否發(fā)生異常都會(huì)執(zhí)行的代碼
          }

          編譯器處理

          編譯器在將源代碼編譯成字節(jié)碼時(shí),會(huì)對(duì)異常相關(guān)的代碼進(jìn)行處理。

          1. 生成異常表(Exception Table): 編譯器會(huì)生成一個(gè)異常表,其中包含了 try 塊的起始和結(jié)束位置,以及每個(gè) catch 塊和 finally 塊的起始位置。這個(gè)表是在字節(jié)碼中的一部分,用于在運(yùn)行時(shí)確定異常處理邏輯。
          2. 異常處理代碼的插入: 編譯器會(huì)在可能引發(fā)異常的代碼周圍插入異常處理代碼,以確保異常發(fā)生時(shí)能夠跳轉(zhuǎn)到正確的 catch 塊或 finally 塊。

          虛擬機(jī)層面

          JVM實(shí)現(xiàn)

          JVM在運(yùn)行時(shí)負(fù)責(zé)執(zhí)行編譯生成的字節(jié)碼。

          1. 異常對(duì)象的創(chuàng)建: 當(dāng)在 try 塊中的代碼引發(fā)異常時(shí),JVM會(huì)創(chuàng)建一個(gè)異常對(duì)象,其中包含有關(guān)異常的信息,如類型、消息和堆棧跟蹤。
          2. 異常拋出: JVM使用 athrow 指令將異常對(duì)象拋出。這通常由 throw 關(guān)鍵字觸發(fā)。
          3. 異常處理表的使用: 當(dāng)異常被拋出時(shí),JVM會(huì)檢查當(dāng)前方法的異常處理表。它會(huì)逐個(gè)檢查 try 塊,看是否匹配拋出的異常。如果找到匹配的 catch 塊,JVM會(huì)跳轉(zhuǎn)到該塊的代碼執(zhí)行異常處理邏輯。
          4. finally 塊的執(zhí)行: 無(wú)論是否發(fā)生異常,JVM都會(huì)執(zhí)行 finally 塊中的代碼。這是通過(guò)在 try 塊的最后插入 finally 指令實(shí)現(xiàn)的。

          源碼示例

          以下是 try-catch-finally 示例

          package com.example.demo.exception;

          public class TryCatchFinallyExample {

              public static void main(String[] args) {
                  try {
                      int result = divide(100);
                      System.out.println("Result: " + result);
                  } catch (ArithmeticException e) {
                      System.out.println("Caught ArithmeticException: " + e.getMessage());
                  } finally {
                      System.out.println("Finally block executed");
                  }
              }

              public static int divide(int a, int b) {
                  return a / b;
              }
          }

          對(duì)應(yīng)的字節(jié)碼(使用 javap -c 命令查看):

          1. 先執(zhí)行編譯命令 javac TryCatchFinallyExample.java
          2. 在執(zhí)行 javap -c TryCatchFinallyExample
          Compiled from "TryCatchFinallyExample.java"
          public class com.example.demo.exception.TryCatchFinallyExample {
            public com.example.demo.exception.TryCatchFinallyExample();
              Code:
                 0: aload_0
                 1: invokespecial #1                  // Method java/lang/Object."<init>":()V
                 4return

            public static void main(java.lang.String[]);
              Code:
                 0: bipush        10
                 2: iconst_0
                 3: invokestatic  #2                  // Method divide:(II)I
                 6: istore_1
                 7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                10: iload_1
                11: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
                16: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                19: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                22: ldc           #6                  // String Finally block executed
                24: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                27: goto          68
                30: astore_1
                31: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                34: aload_1
                35: invokevirtual #8                  // Method java/lang/ArithmeticException.getMessage:()Ljava/lang/String;
                38: invokedynamic #9,  0              // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
                43: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                46: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                49: ldc           #6                  // String Finally block executed
                51: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                54: goto          68
                57: astore_2
                58: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                61: ldc           #6                  // String Finally block executed
                63: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                66: aload_2
                67: athrow
                68return
              Exception table:
                 from    to  target type
                     0    19    30   Class java/lang/ArithmeticException
                     0    19    57   any
                    30    46    57   any

            public static int divide(intint)
          ;
              Code:
                 0: iload_0
                 1: iload_1
                 2: idiv
                 3: ireturn
          }

          Exception table(異常表)

          Exception table 是Java字節(jié)碼中的一個(gè)部分,用于指定方法中的異常處理信息。它描述了在方法執(zhí)行期間,哪些字節(jié)碼范圍可能拋出異常,以及如何處理這些異常。

          我們具體解釋 Exception table 部分的含義:

          Exception table:
             from    to  target type
                 0    19    30   Class java/lang/ArithmeticException
                 0    19    57   any
                30    46    57   any

          每一行代表一個(gè)異常處理?xiàng)l目,它包含以下信息:

          • from: 起始字節(jié)碼索引,表示異常處理的起始位置。
          • to: 結(jié)束字節(jié)碼索引,表示異常處理的結(jié)束位置。
          • target: 處理異常時(shí)的目標(biāo)字節(jié)碼索引,表示異常被捕獲后應(yīng)該跳轉(zhuǎn)到的位置。
          • type: 異常類型,表示應(yīng)該捕獲的異常類型。

          第一行: 如果0到19之間,發(fā)生了ArithmeticException類型的異常,調(diào)用30的位置處理異常。

          • 異常處理范圍:從字節(jié)碼索引0到19。
          • 異常類型:java/lang/ArithmeticException。
          • 處理后跳轉(zhuǎn)到字節(jié)碼索引30。

          第二行: 如果0到19之間,發(fā)生了任何類型的異常,調(diào)用57的位置處理異常。

          • 異常處理范圍:從字節(jié)碼索引0到19。
          • 異常類型:any,表示捕獲任何異常。
          • 處理后跳轉(zhuǎn)到字節(jié)碼索引57。

          第三行: 如果30到46之間(即catch部分),發(fā)生了任何類型的異常,調(diào)用57的位置處理異常。

          • 異常處理范圍:從字節(jié)碼索引30到46。
          • 異常類型:any,表示捕獲任何異常。
          • 處理后跳轉(zhuǎn)到字節(jié)碼索引57。

          通過(guò)這個(gè)異常表的信息,它告訴Java虛擬機(jī)在執(zhí)行方法時(shí),如果在指定的范圍內(nèi)發(fā)生了異常,應(yīng)該如何處理。每個(gè)異常處理?xiàng)l目都包含了異常的類型和處理的范圍。如果異常發(fā)生在范圍內(nèi),程序?qū)凑债惓L幚肀碇兄付ǖ姆绞竭M(jìn)行處理,跳轉(zhuǎn)到相應(yīng)的目標(biāo)位置。

          再次分析上面的指令

           public static void main(java.lang.String[]);
              Code:
              
                 // try 獲取 finally 的代碼,如果沒(méi)有異常發(fā)生,則執(zhí)行輸出finally的操作,跳到goto的68位置,執(zhí)行返回操作。  
                 0: bipush        10
                 2: iconst_0
                 3: invokestatic  #2                  // Method divide:(II)I
                 6: istore_1
                 7: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                10: iload_1
                11: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(I)Ljava/lang/String;
                16: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                19: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                22: ldc           #6                  // String Finally block executed
                24: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                27: goto          68
                
                // catch 獲取 finally代碼,如果沒(méi)有異常發(fā)生,則執(zhí)行輸出finally的操作,跳到goto的68位置,執(zhí)行返回操作。
                30: astore_1
                31: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                34: aload_1
                35: invokevirtual #8                  // Method java/lang/ArithmeticException.getMessage:()Ljava/lang/String;
                38: invokedynamic #9,  0              // InvokeDynamic #1:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;
                43: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                46: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                49: ldc           #6                  // String Finally block executed
                51: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                54: goto          68
                
                // finally 的代碼如果被調(diào)用,既有可能是try的異常,也有可能是catch的異常。
                57: astore_2
                58: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
                61: ldc           #6                  // String Finally block executed
                63: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
                66: aload_2
                
                // 如果異常沒(méi)有被catch捕獲,到了這里,執(zhí)行完finally的語(yǔ)句后,也要把這個(gè)異常拋出去,傳遞給調(diào)用處。
                67: athrow
                68return

          關(guān)于指令的解釋

          • bipush 10:將整數(shù)值10推送到操作數(shù)棧上。
          • iconst_0:將整數(shù)值0推送到操作數(shù)棧上。
          • invokestatic #2:**(調(diào)用靜態(tài)方法)**調(diào)用靜態(tài)方法divide,傳入兩個(gè)整數(shù)參數(shù),并接收一個(gè)整數(shù)結(jié)果。
          • istore_1:將操作數(shù)棧頂?shù)恼麛?shù)值存儲(chǔ)到本地變量表的第一個(gè)位置。
          • getstatic #3:獲取System.out字段并將其推送到操作數(shù)棧上。
          • iload_1:將第一個(gè)局部變量(即從divide方法返回的結(jié)果)加載到操作數(shù)棧上。
          • invokedynamic #4, 0:**(調(diào)用動(dòng)態(tài)方法)**動(dòng)態(tài)生成并調(diào)用一個(gè)方法,該方法接受一個(gè)整數(shù)參數(shù),并返回一個(gè)字符串。
          • invokevirtual #5:**(調(diào)用實(shí)例方法)**調(diào)用PrintStream.println方法,打印出字符串。
          • getstatic #3:獲取System.out字段并將其推送到操作數(shù)棧上。
          • ldc #6:**( 將 int, float 或 String 型常量值從常量池中推送至棧頂。)**將常量池中的字符串"Finally block executed"加載到操作數(shù)棧上。
          • invokevirtual #5:調(diào)用PrintStream.println方法,打印出字符串。
          • goto 68:無(wú)條件跳轉(zhuǎn)至第68行。
          • astore_1:將操作數(shù)棧上的值存儲(chǔ)到本地變量表的第一個(gè)位置(發(fā)生異常時(shí),將異常對(duì)象存入這個(gè)位置)。
          • getstatic #3:獲取System.out字段并將其推送到操作數(shù)棧上。
          • aload_1:將第一個(gè)局部變量(即捕獲到的異常對(duì)象)加載到操作數(shù)棧上。
          • invokevirtual #8:調(diào)用ArithmeticException.getMessage方法,獲取異常消息并將其推送到操作數(shù)棧上。
          • invokedynamic #9, 0:動(dòng)態(tài)生成并調(diào)用一個(gè)方法,該方法接受一個(gè)字符串參數(shù),并返回一個(gè)字符串。
          • invokevirtual #5:調(diào)用PrintStream.println方法,打印出字符串。
          • getstatic #3:獲取System.out字段并將其推送到操作數(shù)棧上。
          • ldc #6:將常量池中的字符串"Finally block executed"加載到操作數(shù)棧上。
          • invokevirtual #5:調(diào)用PrintStream.println方法,打印出字符串。
          • goto 68:無(wú)條件跳轉(zhuǎn)至第68行。
          • astore_2:將操作數(shù)棧上的值存儲(chǔ)到本地變量表的第二個(gè)位置(發(fā)生異常時(shí),將新的異常對(duì)象存入這個(gè)位置)。
          • getstatic #3:獲取System.out字段并將其推送到操作數(shù)棧上。
          • ldc #6:將常量池中的字符串"Finally block executed"加載到操作數(shù)棧上。
          • invokevirtual #5:調(diào)用PrintStream.println方法,打印出字符串。
          • aload_2:將第二個(gè)局部變量(即新的異常對(duì)象)加載到操作數(shù)棧上。
          • athrow:將棧頂?shù)漠惓伋觥?
          • return:返回void。

          關(guān)于指令的操作,大家可以閱讀《Java虛擬機(jī)規(guī)范》- 第 6 章 Java 虛擬機(jī)指令集。

          總結(jié)

          當(dāng)程序執(zhí)行過(guò)程中發(fā)生異常時(shí),Java虛擬機(jī)(JVM)會(huì)按照以下流程處理異常:

          1. 執(zhí)行 try : 程序執(zhí)行到 try 塊中的字節(jié)碼指令。
          2. 檢測(cè)異常發(fā)生: 當(dāng)在 try 塊中發(fā)生異常時(shí),Java虛擬機(jī)會(huì)檢測(cè)到異常的發(fā)生。
          3. 異常表匹配: 異常表是在編譯時(shí)生成的,它包含了每個(gè) try-catch 塊的起始位置、結(jié)束位置、異常處理器的位置以及期望捕獲的異常類型。異常表將被檢查以查找與發(fā)生的異常類型匹配的處理器。
          4. 執(zhí)行字節(jié)碼指令:try 塊中的字節(jié)碼指令將繼續(xù)執(zhí)行,直到異常發(fā)生。
          5. 拋出異常: 當(dāng)異常發(fā)生時(shí),Java虛擬機(jī)會(huì)創(chuàng)建一個(gè)異常對(duì)象,并將其拋出。
          6. 查找匹配的異常處理器: 異常表中的每一項(xiàng)都將被檢查,如果發(fā)生的異常類型匹配,就會(huì)選擇相應(yīng)的異常處理器。
          7. 遇到異常處理指令: 當(dāng)匹配到異常處理器時(shí),控制流將跳轉(zhuǎn)到異常處理器的起始位置。這可能涉及到 goto 指令或其他控制流程的改變。
          8. 異常表中的處理器執(zhí)行: 執(zhí)行異常處理器(catch 塊或 finally 塊)中的字節(jié)碼指令。在 catch 塊中,會(huì)進(jìn)行對(duì)異常對(duì)象的處理,而 finally 塊則無(wú)論是否發(fā)生異常都會(huì)執(zhí)行。
          9. 執(zhí)行 catch 或 finally: 在異常處理器中執(zhí)行相應(yīng)的字節(jié)碼指令,處理異?;驁?zhí)行清理代碼。
          10. 控制流繼續(xù)執(zhí)行: 一旦異常處理完成,程序的控制流將繼續(xù)執(zhí)行異常處理代碼塊之后的代碼。

          瀏覽 48
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天做天天爱天天高潮 | 免费日本黄色一级片 | 久久精品禁一区二区三区四区五区 | 亚洲视频日韩精彩动漫一区二区 | 麻豆精品三级无码 |