类生命周期
类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中验证(Verification)、准备(Preparation)、解析(Resolution)3个部分统称为连接(Linking)。这7个阶段的顺序如图:
加载、验证、准备、初始化和卸载这5个阶段的顺序是确定的,类加载过程必须按照这个顺序开始,但是解析就不一定,因为Java存在运行时绑定。
加载
加载阶段虚拟机需要完成三件事:
1. 通过一个类的全限定名类获取定义此类的二进制字节流。
2. 蒋这个字节流代表的静态存储结构转化成方法区的运行时数据结构。
3. 在内存中生成一个该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
验证
验证是连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证大致会完成以下4个校验动作:
1. 文件格式验证。该验证目的是确保输入的字节流能正确的解析并存储于方法区之内,格式上符合描述一个Java类型信息的要求。
2. 元数据验证。该验证目的是对类的元数据信息进行语义校验,保证不存在不符合Java语言规范的元数据信息。
3. 字节码验证。该验证目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。
4. 符合引用验证。该验证目的是确保解析动作能正常执行。
对应虚拟机的类加载机制来说,验证阶段是一个非常重要的,但不是一定必要的阶段。可以用-Xverify:none参数关闭大部分的类验证。
准备
准备阶段是正式为类变量(被static修饰)分配内存并设置初始化值的阶段,这些变量所使用的内存都在方法区中进行分配。实例变量将会在对象实例化时随着对象一起分配在Java堆中。这里的初始化值是数据类型的零值,假设一个类的变量的定义为:
public static int value = 123;
那变量的value在准备阶段过后的初始值为0而不是123。把value赋值为123的putstatic指令是在程序被编译后,存放一个类构造器
解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:以一组符号来描述引用的目标。
直接引用:直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
初始化
类初始化是类加载最后的一个阶段。初始化阶段是执行类构造器
public class ClassLoadTest {
static ClassLoadTest t = new ClassLoadTest();
static{
System.out.println("1");
}
{
System.out.println("2");
}
public ClassLoadTest(){
System.out.println("3");
System.out.println("a=" + a + ";b=" + b);
}
public static void f(){
System.out.println("4");
}
static int b = 150;
int a = 100;
public static void main(String[] args) {
f();
}
}
上面程序执行的结果:
2
3
a=100;b=0
1
4
对应的初始化顺序如下:
public class ClassLoadTest {
static ClassLoadTest t = new ClassLoadTest();
public ClassLoadTest(){
{
System.out.println("2");
}
int a = 100;
System.out.println("3");
System.out.println("a=" + a + ";b=" + b);
}
static int b;
static{
System.out.println("1");
b = 150;
}
public static void f(){
System.out.println("4");
}
public static void main(String[] args) {
f();
}
}