• 【深入理解Java虚拟机】类的初始化过程


    类的初始化过程

    类的加载过程.pngfile

    • 加载

      将 Class 文件以二进制的形式加载到内存中

    • 验证

      校验 Class 文件是否安全,是否被正确的修改等

    • 准备

      为类变量申请内存,设置默认值,(初始化变量的默认值,比如int初始化为0,reference初始化为null) 但是达到类的初始化之前都没有初始化为真正的值。

    零值.pngfile

    • 解析

      将符号引用转换为直接引用

    • 初始化

      搜集并执行static代码块,以及 方法的执行, 是静态变量以及static 代码块组成

    • 使用

      为新对象申请内存, 为示例变量初始化默认值,为实例对象正确的设置初值;生成 方法

    • 卸载

    在运行的时候加上虚拟机参数 +XX:+TraceClassLoading 可以详细的看到类加载的信息,同样的要看类卸载的信息,可以使用 -XX:TraceClassUnloading

    主动引用与被动引用

    主动引用

    • 遇到new , getstatic , putstatic,invokestatic 字节码指令的时候,如果没有初始化,进行性初始化
    • 反射的时候,比如: System.load("xxxx.xxxx.xx");
    • 初始化一个类,但是这个类的父类没有初始化的时候(一个接口初始化的时候并不要求其父接口全部初始化)
    • JVM 需要执行的主类
    • 遇到动态语言支持的时候

    被动引用

    • 通过子类引用父类的 静态变量 或者 静态方法,并不会初始化父类。 通过子类引用父类的静态属性,表示对父类的主动使用,而非对子类的主动使用
    • 通过构造类型的数组,不会初始化此类
    • 直接引用某个类的常亮的类型的时候,并不会对该对初始化

    示例代码

    class SuperClass {
      public static String msg = "Hello,World";
    
      static {
        System.out.println("SuperClass.static initializer");
      }
    }
    
    class SubClass extends SuperClass {
    
      public static String msg2 = "Hello,World";
    
      static {
        System.out.println("SubClass.static initializer");
      }
    }
    
    • 验证通过子类引用父类的常量 不会初始化子类
       // 验证通过子类引用父类的常量并不会初始化子类
        System.out.println(SubClass.msg);
    
    • 验证初始化子类的同时一定会初始化父类
        System.out.println(SubClass.msg2);
    
    • 验证初始类型数组的时候并不初始化该类型

    对于数组类型,其类型是JVM运行期间动态生成的,类型为[Lxxxx.xxxx.xxxx.xxxx.SubClasss; 其父类为Object,同理二维数组类型为[[Lxxx.xxx.xxx.SubClass; 其父类型为Object;

        SubClass[] subClasses = new SubClass[1];
    

    常量池的引用

    • 源码
    public class ReferenceExample002 {
    
      public static void main(String[] args) {
        //    实际运行的是 System.out.println(Hello, World);
        // 对于只有运行期才能确定的值,仍然会初始化类
        System.out.println(ExampleClass.msg);
      }
    }
    
    class ExampleClass {
    
      public static final String msg = "Hello, World";
    
      static {
        System.out.println("ExampleClass.static initializer");
      }
    }
    
    • 代码反编译后的信息中移除了对ExampleClass 的直接引用
    
    public class ReferenceExample002 {
        public ReferenceExample002() {
        }
    
        public static void main(String[] args) {
            System.out.println("Hello, World");
        }
    }
    
    • 反编译ReferenceExample002得到的助记符信息如下:

    反编译命令如: javap -c xxx.xxx.xxx

    public class com.zhoutao.example.ReferenceExample002 {
      public com.zhoutao.example.ReferenceExample002();
        Code:
           0: aload_0
           1: invokespecial #1                  // Method java/lang/Object."<init>":()V
           4: return
    
      public static void main(java.lang.String[]);
        Code:
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: ldc           #4                  // String Hello, World
           5: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
           8: return
    

    编译期间不确定的常量值所在的类将被初始化

    package com.zhoutao.classload;
    
    import java.util.UUID;
    
    public class ReferenceExample003 {
    
      public static void main(String[] args) {
    
        System.out.println(ExampleClass.uuid);
      }
    
      static class ExampleClass {
       
        // 此处引用的并非真正的常量值
        public static final String uuid = UUID.randomUUID().toString();
    
        static {
          System.out.println("ExampleClass.static initializer");
        }
      }
    }
    
    

    ExampleClass 的静态代码块将会被执行: uuid 的值在编译器并不能确定,所以仍然会初始化对应的类,注意和编译期间确定的常量值进行区分。

    接口中的常量

    ​ 当一个接口初始化时候,并不要求其父接口都完成初始化,然后类的初始化的时候要求父类完成初始化,而类的初始化的时候要求父类完成初始化

    public class ReferenceExample005 {
    
      public static void main(String[] args) {
        System.out.println(SubInterface.b);
      }
    
      static interface ParentInterface {
        public static int a = 1;
      }
    
      static interface SubInterface extends ParentInterface {
        public static int b = 2;
      }
    }
    

    JVM 中父接口并不会因为子接口或者其实现类的初始化而初始化,其仅仅在使用该接口的静态变量的时候才会进行初始化

    初始化过程的方法

    Java 会为每个类生成 方法 ,对于静态变量会生出 方法

    public class ReferenceExample006 {
    
      public static void main(String[] args) {
        ExampleClass exampleClass = ExampleClass.getInstance();
        System.out.println("a = " + ExampleClass.a);
        System.out.println("b = " + ExampleClass.b);
      }
    
      static class ExampleClass {
        public static int a;
    
        public static int b = 0;
    
        private static ExampleClass exampleClass = new ExampleClass();
    
        private ExampleClass() {
          a++;
          b++;
        }
    
        public static ExampleClass getInstance() {
          return exampleClass;
        }
      }
    }
    

    对于上面的代码,可以很简单的知道,其输出值为:

    a = 1
    b = 1
    

    如果将 ExampleClass 中的定义 b 放置于ExampleClass 的私有构造方法之后,那么其输出的值将为:

    a = 1
    b = 0
    

    这是因为在 方法的搜集static定义及以及代码块的时候,是按照顺序执行的,在私有构造方法时候,将会对b 进行赋值为1,然后在下一步 public static int b = 0; 又重新的将 b 定义为1

    file

    本文由博客群发一文多发等运营工具平台 OpenWrite 发布

  • 相关阅读:
    【转】我是一个线程
    前端之 JS 实现全选、反选、取消选中
    Python文件操作——逐行插入内容
    angularJs实现数据双向绑定的原理
    手机连接电脑调试页面
    工程化框架之feather
    网页上线后音频不能自动播放
    FormData对象
    地图热区自适应
    需求移交会
  • 原文地址:https://www.cnblogs.com/zhoutao825638/p/12388864.html
Copyright © 2020-2023  润新知