JVM_09 类加载与字节码技术(字节码指令3)

逃离我推掉我的手 2023-01-18 09:12 43阅读 0赞

1.异常处理

try-catch

  1. public class Demo3_11_1 {
  2. public static void main(String[] args) {
  3. int i = 0;
  4. try {
  5. i = 10;
  6. } catch (Exception e) {
  7. i = 20;
  8. }
  9. }
  10. }

注意:为了抓住重点,下面的字节码省略了不重要的部分

字节码如下:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=3, args_size=1
  6. 0: iconst_0
  7. 1: istore_1
  8. 2: bipush 10
  9. 4: istore_1
  10. 5: goto 12
  11. 8: astore_2
  12. 9: bipush 20
  13. 11: istore_1
  14. 12: return
  15. Exception table:
  16. from to target type
  17. 2 5 8 Class java/lang/Exception
  18. LineNumberTable: ...
  19. LocalVariableTable:
  20. Start Length Slot Name Signature
  21. 9 3 2 e Ljava/lang/Exception;
  22. 0 13 0 args [Ljava/lang/String;
  23. 2 11 1 i I
  24. StackMapTable: ...
  25. MethodParameters: ...
  26. }
  • 可以看到多出来一个 Exception table 的结构,[from, to) 是前闭后开的检测范围,一旦这个范围内的字节码执行出现异常,则通过 type 匹配异常类型,如果一致,进入 target 所指示行号
  • 8 行的字节码指令 astore_2 是将异常对象引用存入局部变量表的 slot 2 位置

多个 single-catch 块的情况

  1. public class Demo3_11_2 {
  2. public static void main(String[] args) {
  3. int i = 0;
  4. try {
  5. i = 10;
  6. } catch (ArithmeticException e) {
  7. i = 30;
  8. } catch (NullPointerException e) {
  9. i = 40;
  10. } catch (Exception e) {
  11. i = 50;
  12. }
  13. }
  14. }

字节码如下:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=3, args_size=1
  6. 0: iconst_0
  7. 1: istore_1
  8. 2: bipush 10
  9. 4: istore_1
  10. 5: goto 26
  11. 8: astore_2
  12. 9: bipush 30
  13. 11: istore_1
  14. 12: goto 26
  15. 15: astore_2
  16. 16: bipush 40
  17. 18: istore_1
  18. 19: goto 26
  19. 22: astore_2
  20. 23: bipush 50
  21. 25: istore_1
  22. 26: return
  23. Exception table:
  24. from to target type
  25. 2 5 8 Class java/lang/ArithmeticException
  26. 2 5 15 Class java/lang/NullPointerException
  27. 2 5 22 Class java/lang/Exception
  28. LineNumberTable: ...
  29. LocalVariableTable:
  30. Start Length Slot Name Signature
  31. 9 3 2 e Ljava/lang/ArithmeticException;
  32. 16 3 2 e Ljava/lang/NullPointerException;
  33. 23 3 2 e Ljava/lang/Exception;
  34. 0 27 0 args Ljava/lang/String;
  35. 2 25 1 i I
  36. StackMapTable: ...
  37. MethodParameters: ...
  • 因为异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用。

multi-catch 的情况

  1. public class Demo3_11_3 {
  2. public static void main(String[] args) {
  3. try {
  4. Method test = Demo3_11_3.class.getMethod("test");
  5. test.invoke(null);
  6. } catch (NoSuchMethodException | IllegalAccessException |
  7. InvocationTargetException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. public static void test() {
  12. System.out.println("ok");
  13. }
  14. }

字节码如下:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=3, locals=2, args_size=1
  6. 0: ldc #2
  7. 2: ldc #3
  8. 4: iconst_0
  9. 5: anewarray #4
  10. 8: invokevirtual #5
  11. 11: astore_1
  12. 12: aload_1
  13. 13: aconst_null
  14. 14: iconst_0
  15. 15: anewarray #6
  16. 18: invokevirtual #7
  17. 21: pop
  18. 22: goto 30
  19. 25: astore_1
  20. 26: aload_1
  21. 27: invokevirtual #11 // e.printStackTrace:()V
  22. 30: return
  23. Exception table:
  24. from to target type
  25. 0 22 25 Class java/lang/NoSuchMethodException
  26. 0 22 25 Class java/lang/IllegalAccessException
  27. 0 22 25 Class java/lang/reflect/InvocationTargetException
  28. LineNumberTable: ...
  29. LocalVariableTable:
  30. Start Length Slot Name Signature
  31. 12 10 1 test Ljava/lang/reflect/Method;
  32. 26 4 1 e Ljava/lang/ReflectiveOperationException;
  33. 0 31 0 args [Ljava/lang/String;
  34. StackMapTable: ...
  35. MethodParameters: ...

finally

  1. public class Demo3_11_4 {
  2. public static void main(String[] args) {
  3. int i = 0;
  4. try {
  5. i = 10;
  6. } catch (Exception e) {
  7. i = 20;
  8. } finally {
  9. i = 30;
  10. }
  11. }
  12. }

字节码如下:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=4, args_size=1
  6. 0: iconst_0
  7. 1: istore_1 // 0 -> i
  8. 2: bipush 10 // try --------------------------------------
  9. 4: istore_1 // 10 -> i |
  10. 5: bipush 30 // finally |
  11. 7: istore_1 // 30 -> i |
  12. 8: goto 27 // return -----------------------------------
  13. 11: astore_2 // catch Exceptin -> e ----------------------
  14. 12: bipush 20 // |
  15. 14: istore_1 // 20 -> i |
  16. 15: bipush 30 // finally |
  17. 17: istore_1 // 30 -> i |
  18. 18: goto 27 // return -----------------------------------
  19. 21: astore_3 // catch any -> slot 3 ----------------------
  20. 22: bipush 30 // finally |
  21. 24: istore_1 // 30 -> i |
  22. 25: aload_3 // <- slot 3 |
  23. 26: athrow // throw ------------------------------------
  24. 27: return
  25. Exception table:
  26. from to target type
  27. 2 5 11 Class java/lang/Exception
  28. 2 5 21 any // 剩余的异常类型,比如 Error
  29. 11 15 21 any // 剩余的异常类型,比如 Error
  30. LineNumberTable: ...
  31. LocalVariableTable:
  32. Start Length Slot Name Signature
  33. 12 3 2 e Ljava/lang/Exception;
  34. 0 28 0 args [Ljava/lang/String;
  35. 2 26 1 i I
  36. StackMapTable: ...
  37. MethodParameters: ...

可以看到 finally 中的代码被复制了 3 份,分别放入 try 流程,catch 流程以及 catch 剩余的异常类型流程。

2.练习:finally面试题

finally 出现了 return

下面的题目输出什么?

  1. public class Demo3_12_2 {
  2. public static void main(String[] args) {
  3. int result = test();
  4. System.out.println(result);// 20
  5. }
  6. public static int test() {
  7. try {
  8. return 10;
  9. } finally {
  10. return 20;
  11. }
  12. }
  13. }

字节码如下:

  1. public static int test();
  2. descriptor: ()I
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=2, args_size=0
  6. 0: bipush 10 // <- 10 放入栈顶
  7. 2: istore_0 // 10 -> slot 0 (从栈顶移除了)
  8. 3: bipush 20 // <- 20 放入栈顶
  9. 5: ireturn // 返回栈顶 int(20)
  10. 6: astore_1 // catch any -> slot 1
  11. 7: bipush 20 // <- 20 放入栈顶
  12. 9: ireturn // 返回栈顶 int(20)
  13. Exception table:
  14. from to target type
  15. 0 3 6 any
  16. LineNumberTable: ...
  17. StackMapTable: .
  • 由于 finally 中的 ireturn 被插入了所有可能的流程,因此返回结果肯定以 finally 的为准
  • 至于字节码中第 2 行,似乎没啥用,且留个伏笔,看下个例子。
  • 跟上例中的 finally 相比,发现没有 athrow 了,这告诉我们:如果在 finally 中出现了 return,会吞掉异常,可以试一下下面。

    .
    public class Demo3_12_1 {

    1. public static void main(String[] args) {
    2. int result = test();
    3. System.out.println(result);
    4. }
    5. public static int test() {
    6. try {
    7. int i = 1/0;
    8. return 10;
    9. } finally {
    10. return 20;
    11. }
    12. }

    }

finally 对返回值的影响

自己,下面的题目输出什么?

  1. }
  2. public class Demo3_12_2 {
  3. public static void main(String[] args) {
  4. int result = test();
  5. System.out.println(result);// 20
  6. }
  7. public static int test() {
  8. int i = 10;
  9. try {
  10. return i;
  11. } finally {
  12. i = 20;
  13. }
  14. }
  15. }

字节码如下:

  1. public static int test();
  2. descriptor: ()I
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=3, args_size=0
  6. 0: bipush 10 // <- 10 放入栈顶
  7. 2: istore_0 // 10 -> i
  8. 3: iload_0 // <- i(10)
  9. 4: istore_1 // 10 -> slot 1,暂存至 slot 1,目的是为了固定返回值
  10. 5: bipush 20 // <- 20 放入栈顶
  11. 7: istore_0 // 20 -> i
  12. 8: iload_1 // <- slot 1(10) 载入 slot 1 暂存的值
  13. 9: ireturn // 返回栈顶的 int(10)
  14. 10: astore_2
  15. 11: bipush 20
  16. 13: istore_0
  17. 14: aload_2
  18. 15: athrow
  19. Exception table:
  20. from to target type
  21. 3 5 10 any
  22. LineNumberTable: ...
  23. LocalVariableTable:
  24. Start Length Slot Name Signature
  25. 3 13 0 i I
  26. StackMapTable: ...

3.synchronized关键字

  1. public class Demo3_13 {
  2. public static void main(String[] args) {
  3. Object lock = new Object();
  4. synchronized (lock) {
  5. System.out.println("ok");
  6. }
  7. }
  8. }

字节码如下:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=2, locals=4, args_size=1
  6. 0: new #2 // new Object
  7. 3: dup
  8. 4: invokespecial #1 // invokespecial <init>:()V
  9. 7: astore_1 // lock引用 -> lock
  10. 8: aload_1 // <- lock (synchronized开始)
  11. 9: dup
  12. 10: astore_2 // lock引用 -> slot 2
  13. 11: monitorenter // monitorenter(lock引用)
  14. 12: getstatic #3 // <- System.out
  15. 15: ldc // <- "ok
  16. 17: invokevirtual #5 // invokevirtual println:
  17. (Ljava/lang/String;)V
  18. 20: aload_2 // <- slot 2(lock引用)
  19. 21: monitorexit // monitorexit(lock引用)
  20. 22: goto 30
  21. 25: astore_3 // any -> slot 3
  22. 26: aload_2 // <- slot 2(lock引用)
  23. 27: monitorexit // monitorexit(lock引用)
  24. 28: aload_3
  25. 29: athrow
  26. 30: return
  27. Exception table:
  28. from to target type
  29. 12 22 25 any
  30. 25 28 25 any
  31. LineNumberTable: ...
  32. LocalVariableTable:
  33. Start Length Slot Name Signature
  34. 0 31 0 args [Ljava/lang/String;
  35. 8 23 1 lock Ljava/lang/Object;
  36. StackMapTable: ...
  37. MethodParameters: ...

注意:方法级别的 synchronized 不会在字节码指令中有所体现

发表评论

表情:
评论列表 (有 0 条评论,43人围观)

还没有评论,来说两句吧...

相关阅读

    相关 JVM字节指令

    加载和存储指令 加载和存储指令用于将数据在栈帧中的局部变量表和操作数栈之间来回传输,这类指令包括如下内容。 将一个局部变量加载到操作栈: > iload、ilo