在java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的(增加了开销,但是更加灵活,动态扩展就是依赖与此)
一:生命周期
类的生命周期:加载----验证----准备----解析----初始化----使用----卸载 (7个阶段)
其中:验证、准备、解析3个部分统称为连接阶段。
顺序: 加载、验证、准备、初始化和卸载,这五个阶段顺序是确定的,类加载过程中严格遵守顺序。而解析阶段在某些情况下可以在初始化之后再开始(为了支持JAVA的动态绑定)
二:什么时候初始化?
虚拟机规范严格规定了,有且只有5种情况必须立即对类进行“初始化”(而加载、验证、准备自然需要在此之前开始[严格的顺序])。
1:遇到new、getstatic、putstatic或者invokestatic这4条字节码命令时,如果类没有初始化,则触发 |
2:使用java.lang.reflect包的方法对类进行反射调用时,如果类没有初始化,则触发 |
3:当初始化一个类时没,如果父类没有初始化没,则先出发其父类初始化 |
4:当虚拟机启动时,用户需要指定一个要执行的主类(含有main方法的类),虚拟机会先初始化这个主类 |
5:当使用JDK1.7的动态语言时,如果一个MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄对应的类没有初始化,则需要先出发其初始化。 |
说明:
1:第一条的使用场景,使用new关键字实例化对象的时候、读取或设置一个类的静态字段时候(被final修饰、在编译期把结果放入常量池的静态字段除外)、调用一个类的静态方法的时候。
2:除了以上5条之外,所有引用类的方法都不会触发初始化。(被动引用)
代码清单1:被动引用例子一
//初始化ConstClass时会先触发SuperClass的初始化,如果main方法放在ConstClass中,则先初始化ConstClass //因为含有main方法的主类会被虚拟机优先初始化。 public class SuperClass { static{ System.out.println("SpuerClass init!"); } public static int CCC = 123; } public class ConstClass extends SuperClass { static{ System.out.println("ConstClass init!"); } } public class Test{ public static void main(String[] args){ System.out.println(ConstClass.value); } } //打印结果:SpuerClass init! // 123
从子类调用父类静态变量,子类并未被初始化。由此可见,对于静态字段的调用,只有直接定义这个字段的类才会被初始化。
代码清单2:被动引用例子二
//通过数组定义来引用类,不会触发此类的初始化 public class Test{ public static void main(String[] args){ SuperClass[] as = new SuperClass[10]; } }
SuperClass并没有被初始化,但是这个段代码触发了另一个名为“Lorg.fenixsoft.classloading.SuperClass”的类的初始化,这是由虚拟机自动生成的、创建动作由newarray指令触发。
代码清单3:被动引用例子三
//SuperClass不会被初始化 public class SuperClass { static{ System.out.println("SpuerClass init!"); } public static final String HELLO = "hello"; } public class Test{ public static void main(String[] args){ System.out.println(SuperClass.HELLO); } }
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发这个类的初始化。对HELLO的引用被直接放到Test类的常量池中。
三:接口和类加载的不同点:
接口与类的加载,真正有所区别的是前面有且只有5种场景的第三种:一个接口在初始化时,并不要求父类接口全部都完成了初始化,只有在真正使用到父接口的时候才会初始化。