• 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 插件中直接下载)
  • 相关阅读:
    Android Button的四种点击事件
    Android中StateListDrawable的种类(状态的种类)
    Android中StateListDrawable的种类(状态的种类)
    信息系统项目管理师 高级 初始 寻挂靠
    lib 和 dll 的区别、生成以及使用详解
    如何为WPF添加Main()函数 程序入口点的修改
    pragma pack(非常有用的字节对齐用法说明)
    MFC DestroyWindow窗口对象和窗口句柄的销毁
    VS2008生成的程序无法在其它电脑上运行,提示系统无法执行指定的程序
    MFC修改任务栏图标及程序运行exe图标
  • 原文地址:https://www.cnblogs.com/binarylei/p/10519948.html
Copyright © 2020-2023  润新知