• Java 中类的初始化过程


    先来一张 JVM 中的内存模型 。 

    在Java 虚拟机原理这本书中介绍了类会被初始化的 5 种情况 。

    1 遇到 new getstatic putstatic 和 invokestatic 这 4 条指令时,这4 条指定分别对应使用 new 关键字创建对象,读取和设置一个静态字段(被 final 修饰的静态字段除外,因为已经在编译期间把结果放到常量池中了)和调用一个类的静态方法 。

    2 对类进行反射调用时 。

    3 当其父类没有被初始化时,要初始化父类 。

    4 当虚拟机启动时,用户需要指定一个包含 main 方法的类,虚拟机会优先初始化这个类。

    5 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。

    对照着这些再来看一下我们经常混淆的类中结构的加载顺序 ,可能会有更加深刻的认识 。

    关于类中结构的加载顺序 ,首次创建对象时 ,类中的静态方法 / 静态字段首次被访问时 ,Java 解释器必须先查找类路径 ,以定位.class 文件;然后载入 .class (这将创建一个 Class 对象) ,有关静态初始化的所有动作都会执行 。因此 ,静态初始化只在 Class 对象首次加载的时候进行一次 。当用 new 创建对象时 ,首先在堆上为对象分配足够的存储空间 。然后将堆中的属性分别赋上默认的初始值 。为属性显示赋值(如果有的话) 。最后执行构造器 。

    综上我们可以得出这样的结论 ,类的加载顺序整体上为 “ 父类静态—》子类静态—》父类非静态—》父类构造器—》子类非静态—》子类构造 。” 

    下面说一些看似类被初始化 ,其实并没有的情况 。

    A 通过子类应用父类静态字段 ,不会导致子类初始化 。

    public class SuperClass {
        static {
            System.out.println("SuperClass Init !!!");
        }
        public static int value = 123;
    }
    
    public class SubClass extends SuperClass{
        static {
            System.out.println("SubClass Init !!!");
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            System.out.println(SubClass.value);
        }
    }
    
    //SuperClass Init !!! 
    //123

    B 通过数组定义来引用类 ,不会触发此类的初始化 。( 左右拖动屏幕查看代码 )

    public class Test{
        public static void main(String[] args) {
            //System.out.println(SubClass.value);
            SuperClass[] superClasses = new SuperClass[10];
        }
    }

    运行结果并没有打印出 ”SuperClass Init !!!” ,这里并没有触发 SuperClass 类的初始化 。这里触发了另一个名为 “ [Lcom.sun.jojo.noinitclass.SuperClass ” 的类的初始化 ,他是虚拟机自动创建的直接继承于 java.lang.Object 的子类 ,创建动作由字节码指令 newarray 触发 。

    C 常量在编译期间就会调入类的常量池中 ,所以直接引用变量的类并没有被初始化 。( 左右拖动屏幕查看代码 )

    public class ConstClass {
        static {
            System.out.println("ConstClass Init !!!");
        }
        public static final int value = 123;
    }
    
    public class Test{
        public static void main(String[] args) {
            //System.out.println(SubClass.value);
            //SuperClass[] superClasses = new SuperClass[10];
            System.out.println(ConstClass.value);
        }
    }
    // 123   PS. ConstClass Init !!! 并没有打印

    接口的初始化和类的初始化类似 ,区别在于 5 种情况的第三种 :子类的初始化过程中其父类必须先初始化 ,但接口初始化时不要求其父接口也进行初始化 ,只有在用到父接口时 ,才会去初始化 。

  • 相关阅读:
    CAS与ABA问题产生和解决
    OnCheckedChangeListener和setChecked之间冲突问题解决
    【二】 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否函数该整数。
    【一】设计一个类,我们只能生成该类的一个实例。
    深入学习semaphore
    RF-For循环使用
    【RF库Collections测试】List Should Contain Value
    RF采用SSHLibary库执行sudo命令,提示sudo: sorry, you must have a tty to run sudo错误的解决办法
    【RF库Collections测试】List Should Contain Value
    RF判断列表、字典、整数、字符串类型是否相同方法
  • 原文地址:https://www.cnblogs.com/YJK923/p/9447245.html
Copyright © 2020-2023  润新知