类从加载到虚拟机内存,到卸载出内存,分为:加载、验证、准备、解析、初始化、使用、卸载。
一般编程人员只用关注:加载、连接(分为验证、准备、解析)、初始化即可。
加载、验证、准备、解析、初始化、卸载这5个阶段顺序是确定的。而解析阶段不一定:可以在初始化之后再开始,这是为了支持Java的动态绑定。
以上四条为主动引用,而被动引用,如下所示,不会引发类初始化
1. 子类引用父类静态变量,子类不会初始化
public class ClassInitTest {
public static void main(String[] args) {
System.out.println(SubClass.value);
}
}
class SuperClass{
static{
System.out.println("SuperClass Init");
}
public static int value = 123;
}
class SubClass extends SuperClass{
static{
System.out.println("SubClass Init");
}
}
输出:
SuperClass Init
123
子类没有初始化
2. 定义一个该类的数组,不会引发该类初始化
//SuperClass不会进行初始化
SuperClass[] sps = new SuperClass[];
3. 引用一个类的常量(final修饰),不会引发初始化
public class ClassInitTest2 {
public static void main(String[] args) {
System.out.println(ConstClass.H);
}
}
class ConstClass{
static{
System.out.println("ConstClass Init");
}
public static final String H = "hi";
}
输出:
hi
ConstClass没有进行初始化
保证Class文件字节流包含的信息符合虚拟机要求,且不会破坏虚拟机
1. 文件格式验证
2. 元数据验证:对Java语言语义的验证
3. 字节码验证:方法体的验证
4. 符号引用的验证:为解析阶段作些预处理
为类变量分配内存,全初始化为0值。
准备与初始化的区别?
public static int value = 123;
对于以上语句,在准备阶段时,会为value分配内存空间,并且二进制全置为0,准备阶段完成后,value值为0。只有在类被引发初始化时,value才会被赋值为123。
注:对于以下语句
public static final int value = 123;
此值为常量,那么在准备阶段就会被初始化为123。
把符号引用替换为直接引用
分为四种解析:
1. 类或接口的解析
2. 字段解析
3. 类方法解析
4. 接口方法解析
初始化阶段是执行类构造器方法< clint >()的过程。< clint >方法执行过程中的特点(以下统称为clint方法):
clint方法由编译器自动收集类中的所有类变量的赋值动作和静态代码块中语句合并产生的,编译器收集的顺序是按照程序的顺序,所以,静态代码块只能访问到定义在静态代码块之前的变量,定义在之后的变量,只能赋值,不能访问。
public class Test{
static{
i = 0;//给变量赋值可以编译通过
System.out.print(i);//编译器提示“非法向前引用”
}
static int i = 0;
}
对于任意一个类,由 加载它的类加载器和类本身一同确定在JVM中的唯一性。
比较两个类相等(equals、isAssignableFrom、isInstance、instanceof),只有他们由同一类加载器加载才有意义,否则,即使两个类来自同一个Class文件,他们也是不相等的。
绝大部分Java程序都会使用到以下三中系统提供的类加载器:
启动类加载器
扩展类加载器
应用程序类加载器
双亲委派模型的工作过程:如果一个类加载器收到一个类加载请求,他不会首先自己加载这个类,而是把请求委派给父类加载器去执行,每一层的类加载器都是如此。只有当父类加载器反馈自己无法完成加载请求时,子类加载器才会尝试自己去加载。保证了类的唯一性,因为不同类加载器加载相同的类会在虚拟机产生多个不同的类。