jvm运行时通过将需要的class文件加载到方法区中,并对其进行验证、准备、解析和初始化,最终将二进制字节流转换成jvm方法区中的java类型。jvm加载class文件与其他语言加载二进制文件不一样,jvm加载是懒加载,只有在需要使用到该class文件时才会进行加载。既然如此,class文件该何时加载,加载过程中需要完成的工作有哪些?
1.类生命周期
以上步骤并非严格按照顺序执行,加载,验证,准备,初始化是严格按照顺序,但是为了支持动态绑定,解析动作会在初始化动作之后进行。
动态绑定知识补充:
动态绑定又称运行时绑定,根据具体对象类型决定调用哪个方法。
Son s = new Son();
s.method();
方法执行过程:
- 编译器根据对象的声明类型和方法名,搜索子类及其父类的方法表,找出所有访问属性为public的method方法。可能存在多个方法名为methd的方法,只是参数不同。
- 根据方法签名(方法名称+参数列表)找出完全匹配的方法。
- 如果方法为private、static、final或者构造器方法,则为静态调用
如果son定义了method方法,则直接调用,否则,搜索父类中是否存在method方法。
动态绑定只是针对方法,对属性无效,因为属性无法被重写。
public class Father{ public int a = 1; } public class Son extends Father{ public int a = 2; } public class Main{ public static void main(String[] args){ Father f = new Son(); System.out.println(f.a); } }
什么时候加载class文件,虚拟机规范并没有明确要求,只对何时需要初始化进行了要求,只有在一下5种情况下,类必须进行初始化,而加载,验证,准备必须在初始化之前就应该完成。
new实例化对象、读取/设置类的静态字段(静态常量除外,静态常量在完成准备阶段就可以获取)、执行静态方法。(静态字段的赋值操作是在初始化阶段执行clinit方法进行赋值)
使用java.lang.reflect包的方法对类进行反射调用时,如果类没有初始化,需要对其进行初始化
如果父类没有初始化,在初始化子类之前需要对父类进行初始化。
main方法所在的类
动态语言相关
以上5种情况称为主动引用。引用类不会触发初始化的情况称为被动引用。
1. 子类引用父类静态字段,不会导致子类初始化。
public class Father{
static{
System.out.println("father");
}
public static int value = 2;
}
public class Son extends Father{
static{
System.out.println("son");
}
}
public class Main{
public static void main(String[] args){
System.out.println(Son.a);
}
}
//输出结果
father
2
//说明:class文件加载到jvm内存中,转换为方法区的数据结构,在解析阶段,
//对字段进行解析时,如果在类的方法区的字段信息中找不到对应的字段,那么
//通过接口、父类查找字段。在解析阶段可以完成的事,不用等到初始化阶段
2.通过数组定义引用类,不会触发此类的初始化
public class Main{
public static void main(String[] args){
Father[] f = new Father[10];
}
}
//说明:没有输出Father类的初始化信息,会触发[Father的初始化阶段,创建
//动作由jvm完成
3.调用静态常量
public class Test{
public static final int a = 1;
static {
System.out.println("test");
}
}
public class Main{
public static void main(String[] args){
System.out.println(Test.a);
}
}
//输出结果
1
//说明:没有输出Test的初始化消息,因为常量在准备阶段就已经放入到方法
//区的字段信息中
总结:对类的生命周期做了一个简单的描述,以及何时对类进行初始化,主动引用、被动引用做了一个简单总结。