在阅读本文之前需要注意两点。第一,本文指的“类”包括类和接口,对于类和接口的不同之处,会特别指明。第二,本文指定“Class文件”并非存在磁盘上,这里说的“Class文件”是指一串二进制的字节流
类从被虚拟机加载到内存中到从内存中卸载,由以下几个过程(生命周期),如下图所示:
其实第一个类加载的时机,java虚拟机规范并没有进行强行约束,而是有虚拟机的具体实现来自动把握的。然而类的初始化,虚拟机规范就做出了规定了。
类的初始化有以下5个时机:
使用new实例化对象时、访问修改类的静态字段(用final修饰的静态字段除外,因为它在编译时已经放入了常量池了,不存在类的符号引用了)、调用一个类的静态方法时。在字节码层面就是上就是遇到new、puststatic、getstatic、invokesatic这几个字节码指令时,如果类没有被初始化,就进行类的初始化。如果对类进行反射调用时,如果没有进行类的初始化,则需要先触发其初始化。当初始化一个类时,发现其父类没有被初始化,就先触发其父类的初始化。当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法那个类),虚拟机先初始化这个类。当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例的最终的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有被进行过初始化,则需要先对其进行初始化。上面的5个时机称为对类的主动引用(需要对类进行初始化的)。除此之外,所有引用类的方式都不会触发类的初始化,被称为类的被动引用。下面是类的三个类的被动引用的例子:
代码:
public class Main { public static void main(String[] args) { //调用父类的静态成员,并不会导致子类的初始化,只会进行父类的初始化 int b = subClass.a; } } class superClass { static { System.out.println("superClass进行初始化"); } public static int a = 3; } class subClass extends superClass { static { System.out.println("subClass进行初始化"); } }运行结果:
上图显示了,没有对子类进行初始化。
代码:
public class Main { public static void main(String[] args) { //通过数组定义引入类,不会触发类的初始化 subClass[] subArray=new subClass[10]; } } class superClass { static { System.out.println("superClass进行初始化"); } public static int a = 3; } class subClass extends superClass { static { System.out.println("subClass进行初始化"); } }运行结果: 运行结果就是没输出。
代码:
public class Main { public static void main(String[] args) { //访问用final修饰的静态字段,不会触发类的初始化 int b = superClass.a; } } class superClass { static { System.out.println("superClass进行初始化"); } public final static int a = 3; } class subClass extends superClass { static { System.out.println("subClass进行初始化"); } }运行结果: 运行,没有输出。常量在编译阶段会存入调用类(Main)的常量池中,本质上并没有直接引用到定义常量的类(superClass),因此就不会触发定义常量的类的初始化。
类加载的时机中,类和接口的区别在于上面类初始化的时机中的第三点。当一个类初始化时,需要其全部父类都被进行初始。然而接口并不需要父接口全部完成初始化,只有在真正是要到父接口时(如引用接口中定义的常量)才会初始化父接口。
下面代码意义不大。
public class Main { public static void main(String[] args) { //初始化子類時,需要先初始化其父類接口 int b = subClass.a; new subInterface(){ @Override public void subMethod() { System.out.println(subInterface.b); } @Override public void superMethod() { System.out.println(subInterface.a); } }.subMethod(); } } interface superInterface { public final static int a = 3; void superMethod(); } interface subInterface extends superInterface { public final static int b = 3; void subMethod(); } class superClass { static { System.out.println("初始化父類"); } } class subClass extends superClass{ static { System.out.println("初始化子類"); } public static int a=3; }运行结果(怎样看接口是否进行初始化,不知道):