文章目录
在阅读本文之前需要注意两点。第一,本文指的“类”包括类和接口,对于类和接口的不同之处,会特别指明。第二,本文指定“Class文件”并非存在磁盘上,这里说的“Class文件”是指一串二进制的字节流
一、类加载(初始化)的时机
类从被虚拟机加载到内存中到从内存中卸载,由以下几个过程(生命周期),如下图所示:
其实第一个类加载的时机,java虚拟机规范并没有进行强行约束,而是有虚拟机的具体实现来自动把握的。然而类的初始化,虚拟机规范就做出了规定了。
类的初始化有以下5个时机:
- 使用new实例化对象时、访问修改类的静态字段(用final修饰的静态字段除外,因为它在编译时已经放入了常量池了,不存在类的符号引用了)、调用一个类的静态方法时。在字节码层面就是上就是遇到new、puststatic、getstatic、invokesatic这几个字节码指令时,如果类没有被初始化,就进行类的初始化。
- 如果对类进行反射调用时,如果没有进行类的初始化,则需要先触发其初始化。
- 当初始化一个类时,发现其父类没有被初始化,就先触发其父类的初始化。
- 当虚拟机启动时,用户需要指定一个要执行的主类(包含main方法那个类),虚拟机先初始化这个类。
- 当使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例的最终的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有被进行过初始化,则需要先对其进行初始化。
二、主动引用、被动引用及被动引用的几个例子
上面的5个时机称为对类的主动引用(需要对类进行初始化的)。除此之外,所有引用类的方式都不会触发类的初始化,被称为类的被动引用。下面是类的三个类的被动引用的例子:
2.1 通过子类引用父类的静态字段,不会导致子类的初始化
代码:
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进行初始化");
}
}
运行结果:
上图显示了,没有对子类进行初始化。
2.2 通过数组定义引入类,不会触发类的初始化
代码:
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进行初始化");
}
}
运行结果:
运行结果就是没输出。
2.3 访问用final修饰的静态字段,不会触发类的初始化
代码:
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;
}
运行结果(怎样看接口是否进行初始化,不知道):