07 Java源码字节码层面简单分析

1-1 对象/实例初始化(字节码层面)

1-1-1 静态成员初始化:cinit()V

静态变量与静态代码块

()V:静态变量的初始化再字节码层面是当作class初始化,cinit是class initialize的缩写。

public class Demo3_8_1 {
    static int i = 10;
    static {
        i = 20;
	} static {
		i = 30;
	}
}

静态变量初始化字节码(代码块与单独的静态变量会收集在一起)

    static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        10
         2: putstatic     #2                  // Field i:I
         5: bipush        20
         7: putstatic     #2                  // Field i:I
        10: bipush        30
        12: putstatic     #2                  // Field i:I
        15: return
      LineNumberTable:
        line 4: 0
        line 6: 5
        line 8: 10
        line 9: 15

总结:无论是静态变量还是静态代码块,编译器会按从上至下的顺序,收集所有 static 静态代码块和静态成员赋值的代码合并为一个特殊的方法: ()V

  • 上面静态变量的最终值最后一次赋值为30

1-1-2 实例的初始化: init()V

test1类

package part3;
public class test1 {
    private String a = "s1";
    {
        b = 20;
    }
    private int b = 10;
    {
        a = "s2";
    }
    public test1(String a, int b) {
        this.a = a;
        this.b = b;
    }

    public static void main(String[] args) {
        test1 d = new test1("s3",30);
        System.out.println(d.a);    // s3
        System.out.println(d.b);    // 30
    }
}

类初始化对应的字节码(字节码层面将代码块与构造函数的字节码统一到init:V内部)

      Code:
      stack=2, locals=3, args_size=3
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String s1
         7: putfield      #3                  // Field a:Ljava/lang/String;
        10: aload_0
        11: bipush        20
        13: putfield      #4                  // Field b:I
        16: aload_0
        17: bipush        10
        19: putfield      #4                  // Field b:I
        22: aload_0
        23: ldc           #5                  // String s2
        25: putfield      #3                  // Field a:Ljava/lang/String;
        28: aload_0
        29: aload_1
        30: putfield      #3                  // Field a:Ljava/lang/String;
        33: aload_0
        34: iload_2
        35: putfield      #4                  // Field b:I
        38: return
        LineNumberTable: ...
        LocalVariableTable:
        Start Length Slot Name Signature
        0 39 0 this Lcn/itcast/jvm/t3/bytecode/Demo3_8_2;
        0 39 1 a Ljava/lang/String;
        0 39 2 b I
        MethodParameters: ...
  • 编译器会按从上至下的顺序,收集所有 {} 代码块和成员变量赋值的代码,形成新的构造方法,但原始构造方法内的代码总是在最后

1-2 方法初始化(动态绑定与静态绑定在字节码层面的体现)

源码

package part3;
public class demo {
    public demo(){}
    private void test1(){}
    private final void test2(){}
    public void test3(){}
    public static void test4(){}
    protected void test5(){};
    void test6(){};
    public static void main(String[] args) {
        demo d = new demo();
        d.test1();
        d.test2();
        d.test3();
        d.test4();
        demo.test4();
        d.test5();
        d.test6();
    }
}

main函数字节码


  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class part3/demo    创建未初始化的对象引用
         3: dup                               // 复制栈顶对象引用,并入栈
         4: invokespecial #3                  // Method "<init>":()V ,调用对象初始化方法,初始化实例,
         7: astore_1                          // 存储实例引用
         8: aload_1                           // 加载实例引用
         9: invokespecial #4                  // Method test1:()V
        12: aload_1
        13: invokespecial #5                  // Method test2:()V
        16: aload_1
        17: invokevirtual #6                  // Method test3:()V
        20: aload_1
        21: pop                               // 静态方法不需要用到实例对象,所以将实例对象从操作数栈弹出
        22: invokestatic  #7                  // Method test4:()V
        25: invokestatic  #7                  // Method test4:()V
        28: aload_1
        29: invokevirtual #8                  // Method test5:()V
        32: aload_1
        33: invokevirtual #9                  // Method test6:()V
        36: return
      LineNumberTable:
        line 12: 0
        line 13: 8
        line 14: 12
        line 15: 16
        line 16: 20
        line 17: 25
        line 18: 28
        line 19: 32
        line 20: 36
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      37     0  args   [Ljava/lang/String;
            8      29     1     d   Lpart3/demo;
}
SourceFile: "demo.java"

1-2-1方法调用的字节码指令总结(重要)

命令 适用的函数 特点
invokespecial private和构造函数,以及final修饰函数 静态绑定函数,转换为字节码的时候就已经确定
invokestatic static修饰的函数 静态绑定函数,转换为字节码的时候就已经确定
invokevirtual,invokeinterface(通过接口引用调用方法) public和protected修饰的函数,默认的函数 动态绑定函数,链接的时候根据vtable查找执行方法的字节码

1-3 多态的原理

工具的具体使用可以参考:Java字节码角度分析多态原理 ——提升硬实力8

1-3-1 简单的多态实例:通过父类引用子类对象。

package part3;
import java.io.IOException;
/**
 * 演示多态原理,注意加上下面的 JVM 参数,禁用指针压缩,指针压缩会将类的实际地址进行压缩。
 * -XX:-UseCompressedOops -XX:-UseCompressedClassPointers
 */
public class test2 {
    public static void test(Animal animal) {
        animal.eat();
        System.out.println(animal.toString());
    }
    public static void main(String[] args) throws IOException {
        test(new Cat());
        test(new Dog());
        System.in.read();
    }
}

abstract class Animal {
    public abstract void eat();
    @Override
    public String toString() {
        return "我是" + this.getClass().getSimpleName();
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("啃骨头");
    }
}

class Cat extends Animal {

    @Override
    public void eat() {
        System.out.println("吃鱼");
    }
}

执行结果

吃鱼
我是Cat
啃骨头
我是Dog

原理:Java多态的实现是通过方法区的类的方法表(vtable)查表确定具体调用哪一个方法,方法的确定是在类加载的过程中链接时确定的,因此称为动态绑定。

1-3-2 多态原理总结(重要)

注意:动态绑定的函数才叫多态

构造函数为什么不能是虚函数

当执行 invokevirtual 指令时,
1. 先通过栈帧中的对象引用找到对象
2. 分析对象头,找到对象的实际 Class(注意对象头中包含mark word和Klass word,Kclass word是一个指针,指向对象所从属的class)
3. Class 结构中有 vtable,它在类加载的链接阶段就已经根据方法的重写规则生成好了
4. 查表得到方法的具体地址
5. 执行方法的字节码

Java的多态原理

1-3-3 学会借助HSDB 工具查看类的结构

操作步骤

step1: 运行查看程序,通过jps获取程序的ID,然后运行HDSB工具。
在JDK包安装目录下使用该命令:java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB
C:Program FilesJavajdk1.8.0_131>java -cp ./lib/sa-jdi.jar sun.jvm.hotspot.HSDB

step2:根据进程PID,让工具连接进程(attach to hotspot process)。

step3:通过tools->class browser找到对应的Dog class

1-4 字节码层面的异常的处理体现

1-4-1 try-catch 字节码命令实例

源码+code部分字节码

   public class test3 {
    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        }
    }
}
========================================================================================================================
  Code:
      stack=1, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        10           
         4: istore_1              // 2,4行作用即 i = 10
         5: goto          12
         8: astore_2            //  astore_2 是将异常对象引用存入局部变量表的 slot 2 位置
         9: bipush        20
        11: istore_1           //   9,11作用 即 i = 20
        12: return
      Exception table:           // 该处代码的异常表,检测范围是从from到to即[2,5),在这个范围内的代码出现异常都会被检测          
         from    to  target type
             2     5     8   Class java/lang/Exception
      LineNumberTable:
        line 5: 0
        line 7: 2
        line 10: 5
        line 8: 8
        line 9: 9
        line 11: 12
      LocalVariableTable:                             // 局部变量表中slot2用于存储异常对象的引用
        Start  Length  Slot  Name   Signature
            9       3     2     e   Ljava/lang/Exception;
            0      13     0  args   [Ljava/lang/String;
            2      11     1     i   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 8
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/Exception ]
        frame_type = 3 /* same */
}
总结:
  • 字节码指令通过Exception table 的结构检测固定范围内的字节码执行异常
    • 如果出现异常需要匹配异常的类型,并使得指令跳转到执行位置。
    • 没有异常,在上面的示例中catch代码块由于go to 命令不会执行。

1-4-2 单个try多个catch的异常处理

源码+code部分字节码

public class test4 {
    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (ArithmeticException e) {
            i = 30;
        } catch (NullPointerException e) {
            i = 40;
        } catch (Exception e) {
            i = 50;
        }
    }
}
======================================================================================================
    Code:
      stack=1, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        10
         4: istore_1
         5: goto          26
         8: astore_2
         9: bipush        30
        11: istore_1
        12: goto          26
        15: astore_2
        16: bipush        40
        18: istore_1
        19: goto          26
        22: astore_2
        23: bipush        50
        25: istore_1
        26: return
      Exception table:
         from    to  target type
             2     5     8   Class java/lang/ArithmeticException
             2     5    15   Class java/lang/NullPointerException
             2     5    22   Class java/lang/Exception
      LineNumberTable:
        line 5: 0
        line 7: 2
        line 14: 5
        line 8: 8
        line 9: 9
        line 14: 12
        line 10: 15
        line 11: 16
        line 14: 19
        line 12: 22
        line 13: 23
        line 15: 26
      LocalVariableTable:                            // 三种类型的异常无论哪种发生都只会存储在局部变量表的slot2位置
        Start  Length  Slot  Name   Signature
            9       3     2     e   Ljava/lang/ArithmeticException;
           16       3     2     e   Ljava/lang/NullPointerException;
           23       3     2     e   Ljava/lang/Exception;
            0      27     0  args   [Ljava/lang/String;
            2      25     1     i   I
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 8
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/ArithmeticException ]
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/NullPointerException ]
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/Exception ]
        frame_type = 3 /* same */
}
SourceFile: "test4.java"

总结:单个try多个catch情况下,异常出现时,只能进入 Exception table 中一个分支,所以局部变量表 slot 2 位置被共用

1-4-3 multi-catch 的情况(JDK1.7新支持的语法)

源码+code部分字节码

package part3;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class test5 {
        public static void main(String[] args){
            try{
                Method test = test5.class.getMethod("test");
                test.invoke("test");
            }catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e){
                e.printStackTrace();
            }
        }
        public static void test() {
            System.out.println("ok");
        }
}
===========================================================================================================s
  public part3.test5();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 6: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lpart3/test5;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=2, args_size=1
         0: ldc           #2                  // class part3/test5
         2: ldc           #3                  // String test
         4: iconst_0
         5: anewarray     #4                  // class java/lang/Class
         8: invokevirtual #5                  // Method java/lang/Class.getMethod:(Ljava/lang/String;[Ljava/lang/Class;)Ljava/lang/reflect/Method;
        11: astore_1
        12: aload_1
        13: ldc           #3                  // String test
        15: iconst_0
        16: anewarray     #6                  // class java/lang/Object
        19: invokevirtual #7                  // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
        22: pop
        23: goto          31
        26: astore_1                           // 将异常引用存入到局部变量表的slot1
        27: aload_1                            // 将异常引用放入操作数栈
        28: invokevirtual #11                 // Method java/lang/ReflectiveOperationException.printStackTrace:()V
        31: return
      Exception table:                       // 异常表,发生异常跳转到同一target位置(标号为26的位置)
         from    to  target type
             0    23    26   Class java/lang/NoSuchMethodException
             0    23    26   Class java/lang/IllegalAccessException
             0    23    26   Class java/lang/reflect/InvocationTargetException
      LineNumberTable:
        line 9: 0
        line 10: 12
        line 13: 23
        line 11: 26
        line 12: 27
        line 14: 31
      LocalVariableTable:                  // 局部变量表,共有2个slot,可以看到slot被方法的引用以及异常对象的引用共享
        Start  Length  Slot  Name   Signature
           12      11     1  test   Ljava/lang/reflect/Method;
           27       4     1     e   Ljava/lang/ReflectiveOperationException;
            0      32     0  args   [Ljava/lang/String;
      StackMapTable: number_of_entries = 2
        frame_type = 90 /* same_locals_1_stack_item */
          stack = [ class java/lang/ReflectiveOperationException ]
        frame_type = 4 /* same */

  public static void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #13                 // String ok
         5: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 16: 0
        line 17: 8
}
SourceFile: "test5.java"
    

总结:新增的语法,虽然有多个异常,但最多只有一个异常发生,通过将异常表中的target指向同一个目标实现异常的捕获

      Exception table:                       // 异常表,发生异常跳转到同一target位置(标号为26的位置)
         from    to  target type
             0    23    26   Class java/lang/NoSuchMethodException
             0    23    26   Class java/lang/IllegalAccessException
             0    23    26   Class java/lang/reflect/InvocationTargetException

1-4-4 finally关键字在字节码层面的体现

public class test6 {
    public static void main(String[] args) {
        int i = 0;
        try {
            i = 10;
        } catch (Exception e) {
            i = 20;
        } finally {
            i = 30;
        }
    }
}
============================================================================================
  public part3.test6();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 2: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lpart3/test6;
  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=4, args_size=1
         0: iconst_0
         1: istore_1
                  
         2: bipush        10
         4: istore_1                  // 情况1:没有异常发生后,执行finally内代码。
         5: bipush        30          // finally块内字节码第一个次出现
         7: istore_1
         8: goto          27          
                  
        11: astore_2
        12: bipush        20
        14: istore_1                  // 情况2:catch匹配到的异常后,执行finally内代码。
        15: bipush        30          // finally块内字节码第二次出现
        17: istore_1
        18: goto          27
                  
        21: astore_3                  // 情况3:catch无法匹配的异常的处理后,执行finally内代码
        22: bipush        30          // finally块内字节码第三次出现
        24: istore_1
                  
        25: aload_3                   // 无法匹配的异常会存入到局部变量表的slot3后,然后抛出。
        26: athrow                    // athrow命令会抛出异常
                  
        27: return
      Exception table:
         from    to  target type
             2     5    11   Class java/lang/Exception
             2     5    21   any
            11    15    21   any
      LineNumberTable:
        line 4: 0
        line 6: 2
        line 10: 5
        line 11: 8
        line 7: 11
        line 8: 12
        line 10: 15
        line 11: 18
        line 10: 21
        line 12: 27
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           12       3     2     e   Ljava/lang/Exception;
            0      28     0  args   [Ljava/lang/String;
            2      26     1     i   I
      StackMapTable: number_of_entries = 3
        frame_type = 255 /* full_frame */
          offset_delta = 11
          locals = [ class "[Ljava/lang/String;", int ]
          stack = [ class java/lang/Exception ]
        frame_type = 73 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 5 /* same */
}
SourceFile: "test6.java"

总结:finally块内代码对应的字节码被重复了三次,分别放在了三个位置,从而确保finally块内代码一定会执行

1-4-5 练习题:finally代码块的辨析(不要再finally里面return,会吞掉异常)

问题:下面代码的返回结果是10还是20?

源码+code部分字节码

public class test7 {
    public static void main(String[] args) {
            int result = test();
            System.out.println(result);    // 20
    }
    public static int test() {
        try {
            return 10;
        } finally {
            return 20;
        }
    }
}
============================================================================================ 
  public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=0
         0: bipush        10          // 将10放入栈顶
         2: istore_0                  // 弹出10放入slot 0
             
         3: bipush        20          // 将20放入栈顶     (finally代码块)
         5: ireturn                   // 返回栈顶20
             
         6: astore_1                  // 如发生异常,把异常的引用存入到slot 1     
         7: bipush        20          // 将20放入栈顶
         9: ireturn                   // 返回栈顶元素     (finally代码块)
      Exception table:
         from    to  target type
             0     3     6   any
      LineNumberTable:
        line 10: 0
        line 12: 3
      StackMapTable: number_of_entries = 1
        frame_type = 70 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
}
SourceFile: "test7.java"

总结:可以看到 return 10 这个语句在字节码层面的表现如下所示,并没有返回,因为必须执行finally语句之后才能返回

0: bipush        10          // 将10放入栈顶
2: istore_0                  // 弹出10放入slot 0
  • 操作数栈顶元素的返回必须执行完finally语句的字节码指令。通过将 finally 中的 ireturn 被插入了所有可能的流程,返回结果肯定以 finally 的为准
  • 1-4-4代码中finally的字节码会有athrow字节码抛出异常而这个例子里没有,这启示我们如果在 finally 中出现了 return,会吞掉异常这个非常危险

实例finally里面return会导致程序的异常无法被捕获

public class test7 {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);
    }
    public static int test() {
        try {
            int i = 1/0;
            return 10;
        } finally {
            return 20;
        }
    }
}

1-4-6 练习题:finally代码块的辨析

问题:下面代码的返回结果是10还是20?

package part3;
public class test8 {
    public static void main(String[] args) {
        int result = test();
        System.out.println(result);      // 10
    }
    public static int test(){
        int i = 10;
        try {
            return i;
        } finally {
            i = 20;
        }
    }
}
=================================================================================================
public static int test();
    descriptor: ()I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=3, args_size=0
         0: bipush        10
         2: istore_0                // 局部变量表的slot0:10
         3: iload_0
         4: istore_1                // 弹出,存储到局部变量表的slot1:10
             
         5: bipush        20        // finally 语句第二次出现
         7: istore_0                //弹出,存储到局部变量表的slot0:20
         8: iload_1
         9: ireturn                 // 没有异常发生,直接将局部变量表的slot 1的值即10返回
             
        10: astore_2                // 局部变量表的slot2:发生的异常对象引用
        11: bipush        20        // finally 语句第一次出现,此时的修改无法影响返回值
        13: istore_0                // 弹出,存储到局部变量表的slot0:20
        14: aload_2                 // 有异常发生,加载异常对象引用
            
        15: athrow                  // 抛出异常
      Exception table:
         from    to  target type
             3     5    10   any
      LineNumberTable:
        line 10: 0
        line 12: 3
        line 14: 5
        line 12: 8
        line 14: 10
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            3      13     0     i   I
      StackMapTable: number_of_entries = 1
        frame_type = 255 /* full_frame */
          offset_delta = 10
          locals = [ int ]
          stack = [ class java/lang/Throwable ]
}
SourceFile: "test8.java"

总结:在finally的对i的修改与返回的i的值在局部变量表的不同slot

1-5 synchronized关键字在字节码层面的体现

实例源码与code属性字节码

public class test9 {
    public static void main(String[] args) {
        Object lock = new Object();
        synchronized (lock) {             /*问题:字节码层面如何保证锁的进入与退出成对?*/
            System.out.println("ok");
        }
    }
}
===========================================================================
{
  public part3.test9();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lpart3/test9;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=4, args_size=1
         0: new           #2                  // class java/lang/Object   创建object对象,并将对象引用压入操作数栈
         3: dup                               // 复制对象引用压入操作数栈
 /*2个对象引用作用(对应4,7,8)
    1)一个弹出用于invokespecial,用于调用对象的构造方法
    2)另外一个弹出放入到局部变量表的slot1即lock变量存储对象的引用
 */
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V 
         7: astore_1
         8: aload_1

                  
         9: dup
 /* 2个对象引用作用(对应4,7,8):
     1)一个弹出用于monitorenter,加锁
     2)另外一个弹出存储到局部变量表之后用于monitorexit,解锁。
 */         
        10: astore_2                         // 弹出,局部变量表slot2:锁对象引用
                  
        11: monitorenter                      // 加锁
                  
        12: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
        15: ldc           #4                  // String ok
        17: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        20: aload_2
                  
        21: monitorexit                       // 解锁
        22: goto          30                  // 没有发生异常则返回
  /* 
        synchronized代码块内异常处理。
  */                     
        25: astore_3                         // 存储异常引用到局部变量表slot3
        26: aload_2                          // 加载 局部变量表slot2:锁对象引用 到操作数栈
        27: monitorexit                      // 异常情况下保证解锁!!!!!!!!!!!                
        28: aload_3                          // 加载异常引用到操作数栈
        29: athrow                           // 抛出异常
        30: return
      Exception table:
         from    to  target type
            12    22    25   any
            25    28    25   any            // 25-28如果一直发生异常会一直执行,确保解锁。
      LineNumberTable:
        line 5: 0
        line 6: 8
        line 7: 12
        line 8: 20
        line 9: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      31     0  args   [Ljava/lang/String;
            8      23     1  lock   Ljava/lang/Object;
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class "[Ljava/lang/String;", class java/lang/Object, class java/lang/Object ]
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 4
}
SourceFile: "test9.java"

总结:锁的进入与退出需要考虑发生异常与不发生异常两种情况

参考资料

01 JVM基础课程

02 JDK8:Chapter 6. The Java Virtual Machine Instruction Set

03 JDK8:Chapter 3. Compiling for the Java Virtual Machine

03 同类整理好的博客

04 Java字节码角度分析多态原理 ——提升硬实力8

05 同类整理好的博客

原文地址:https://www.cnblogs.com/kfcuj/p/14659773.html