Java类初始化顺序(变量赋值与静态代码块的执行时间)
前言:最近写代码的时候经常见到见到static代码块,不由对static的执行时间产生了兴趣,进而对类初始化顺序产生了兴趣.
类从编译到执行的过程:
在使用ClassLoader将字节码转换为JVM中的Class<Robot>对象时,要下图所示进行几个过程.
所以类的初始化就在第三步,总的来说遵守下面的规则:
1、 假如这个类还灭有被加载和连接,那就先加载和连接
2、 假如类中存在直接的父类,或者间接父类,并却该父类没有被初始化,则先初始化父类
3(核心)、 类初始化的顺序 :父类的静态成员变量赋值与静态代码块按顺序执行->子类的父类的静态成员变量赋值与静态代码块按顺序执行->父类的非静态成员变量赋值与非静态代码块的执行->父类的构造函数->子类的非静态成员变量赋值与非静态代码块的执行,如果类没有new那么只会执行静态的赋值.
实例:
输出为(只执行了静态):
如果在main里面new了一个Test7()
输出为:
额外的知识:
1.类什么时候会被加载
类的加载是通过类加载器(Classloader)完成的,它既可以是饿汉式[eagerly load](只要有其它类引用了它就加载)加载类,也可以是懒加载[lazy load](等到类初始化发生的时候才加载),虚拟机规范则严格规定了有且只有四种情况必须立即对类进行初始化.
1)使用new关键字实例化对象
2)读取一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)
3)设置一个类的静态字段(被final修饰、已在编译期把结果放在常量池的静态字段除外)
4)调用一个类的静态方法
2.类被加载后会放在哪里
没有new出来之前,加载的时候ClassLoader会在java堆中生成一个代表这个类的Class对象,作为访问方法区(元空间)中这些数据的入口。
3.static final修饰的类型
基本类型:用static final修饰的基本类型,编译器认为该变量是不会被修改的,所以在编译时就会在引用类中写死,以增加效率,所以编译后就变成了写死的值,而不是引用。
4.对象的内存分配问题
参考:https://blog.csdn.net/topdeveloperr/article/details/81194654
创建对象时,对象的成员变量先进行默认初始化,其中:
基本类型初始化为基本类型默认值,如: int 类型的默认值为0,boolean类型的默认值为false;
对象引用类型默认初始化为null,如: Object obj = new xxx();
引用变量的引用地址obj存放在栈(stack)内存中,对象的成员变量及值存放在堆内存中。
这里可以额外引申一点的就是关于线程安全的问题,在这里我们已经知道了,对象的成员变量会在堆中分配,但是局部变量(即方法体中的变量)不会。我们可以得到的的启示是:
局部变量肯定是线程安全的。每个线程执行时将会把局部变量放在各自栈帧的工作内存中,线程间不共享,故不存在线程安全问题。在方法中声明的变量可以是基本类型的变量,也可以是引用类型的变量:
当声明是基本类型的变量的时,其变量名及值(变量名及值是两个概念)是放在方法栈中。
当声明的是引用变量时,所声明的变量(该变量实际上是在方法中存储的是内存地址值)是放在方法的栈中,该变量所指向的对象是放在堆类存中的。
而成员变量就要看它初始化的模式。若是单例模式, 实例变量为对象实例私有,在虚拟机的堆中分配,若在系统中只存在一个此对象的实例,在多线程环境下,被某个线程修改后,其他线程对修改均可见,故线程非安全;如果每个线程执行都是在不同的对象中,那对象与对象之间的实例变量的修改将互不影响,故线程安全。
静态变量肯定不是线程安全的。静态变量在内存中的位置既不在堆中,也不在栈上,而是在方法区的运行时常量池中,而这个部分是所有线程共享的。
当创建一个对象的时候,堆里面只会存放这个对象的头信息(这里面会包含有指向这个对象对应的class以及methods的引用)以及它的字段。