JVM 字节码(四)静态方法、构造代码、this 以及 synchronized 关键字

JVM 字节码(四)静态方法、构造代码、this 以及 synchronized 关键字

一、静态代码

public class ByteCodeStatic {
    private static final String DEFAULT_VALUE = "default_value";
    private static String str = DEFAULT_VALUE;
    static {
        System.out.println("hello");
    }
}

编译后查看对应的字节码,生成了两个方法

  • <cinit> 静态赋值和静态代码块的集合,执行顺序和代码一致。注意不包含常量的赋值。
  • <init> 构造方法,包含普通变量值赋值和构造函数。

cinit 的代码块如下,不包含常量 DEFAULT_VALUE 的赋值,这是通过常量值(ConstantValue)进行赋值的。

 0 ldc #3 <default_value>
 2 putstatic #4 <com/github/binarylei/jvm/bytecode/ByteCodeStatic.str>
 5 getstatic #5 <java/lang/System.out>
 8 ldc #6 <hello>
10 invokevirtual #7 <java/io/PrintStream.println>
13 return

但常量就一定是通过 ConstantValue 赋值吗?如果编译期无法确定常量的值那也是需要通过静态代码块来赋值的,也就是会出现在 cinit 代码中。

public class ByteCodeStatic {
    private static final String DEFAULT_VALUE = new String("default_value");
    private static String str = DEFAULT_VALUE;
    static {
        System.out.println("hello");
    }
}

将 DEFAULT_VALUE 的值修改为一个对象,这个对象需要在运行期才能确定值,重新编译后的指定集如,很明显出现了常量的赋值语句(前五个语句):

 0 new #2 <java/lang/String>
 3 dup
 4 ldc #3 <default_value>
 6 invokespecial #4 <java/lang/String.<init>>
 9 putstatic #5 <com/github/binarylei/jvm/bytecode/ByteCodeStatic.DEFAULT_VALUE>
12 getstatic #5 <com/github/binarylei/jvm/bytecode/ByteCodeStatic.DEFAULT_VALUE>
15 putstatic #6 <com/github/binarylei/jvm/bytecode/ByteCodeStatic.str>
18 getstatic #7 <java/lang/System.out>
21 ldc #8 <hello>
23 invokevirtual #9 <java/io/PrintStream.println>
26 return

二、构造方法

public class ByteCodeConstructor {

    private String str = "binarylei";

    {
        System.out.println("hello");
    }

    public ByteCodeConstructor() {
    }

    public ByteCodeConstructor(String str) {
        this.str = str;
    }
}

编译后查看对应的字节码,生成了两个 构造方法。

第一个构造器:

 0 aload_0                                          // this 参数(所有的非静态方法都包含这个参数)
 1 invokespecial #1 <java/lang/Object.<init>>       // 执行父类构造方法
 4 aload_0
 5 ldc #2 <binarylei>                               // 加载字符串 binarylei
 7 putfield #3 <com/github/binarylei/jvm/bytecode/ByteCodeConstructor.str>  // str 赋值
10 getstatic #4 <java/lang/System.out>
13 ldc #5 <hello>                               
15 invokevirtual #6 <java/io/PrintStream.println>   // System.out.println("hello");
18 return

第二个构造器:

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init>>
 4 aload_0
 5 ldc #2 <binarylei>
 7 putfield #3 <com/github/binarylei/jvm/bytecode/ByteCodeConstructor.str>
10 getstatic #4 <java/lang/System.out>
13 ldc #5 <hello>
15 invokevirtual #6 <java/io/PrintStream.println>

18 aload_0                  // this 参数(所有的非静态方法都包含这个参数)
19 aload_1                  // str 参数
20 putfield #3 <com/github/binarylei/jvm/bytecode/ByteCodeConstructor.str>
23 return

总结: 构造方法会将普通常量和普通代码块整合到其构造器字节块中,每个构造方法都会拼凑一份。

三、this 参数

对于 Java 类中的每一个实例方法(非 static 方法),其在编译后所生成的字节码中,方法参数的数量总是会比源代码中方法的数量多一个(this),它位于方法的第一个参数位置。 这样,我们可以在 Java 的实例方法中使用 this 来去访问当前对象的属性以及其方法。

这个操作是在编译期间完成的,即由 Javac 编译器在编译的时候对 this 的访问转化为一个普通实例方法参数的访问;接下来在运行期由 JVM 在调用实例方法时,自动向实例方法传入该 this 参数,所以,在实例方法的局部变量表中,至少会有一个指向当前对象的局部变量。

四、synchronized

public void test2() {
    synchronized (this) {
    }
}

编译后字节码如下:

 0 aload_0
 1 dup
 2 astore_1
 3 monitorenter
 4 aload_1
 5 monitorexit
 6 goto 14 (+8)
 9 astore_2
10 aload_1
11 monitorexit
12 aload_2
13 athrow
14 return

monitorenter 和 monitorexit 获取锁和释放锁

参考:

  1. 周志明,深入理解Java虚拟机 - 第 6 章:类文件结构
  2. Java 反编译工具 - jclasslib(比 javap -v 信息更详细,可以在 IDEA 插件中直接下载)
原文地址:https://www.cnblogs.com/binarylei/p/10519948.html