简述
类的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。
加载
①.通过一个类的全限定名来获取定义此类的二进制字节流
②.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
③.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
虚拟机设计团队将第一步操作放置Java虚拟机外部,以便让应用程序自己决定去获取所需要的类,实现这操作的代码模块称为类加载器(可以通过继承ClassLoader重写loadClass()方法),JVM提供了3中类加载器:
System.getProperty("sun.boot.class.path")
应用程序都是由这3种类加载器互相配合进行加载,我们自己也可以定义类加载器。JVM使用双亲委派模型来组织类加载器之间关系(组合关系)
双亲委派模型工作过程:如果一个类加载器收到类加载请求,它首先判断这个class是不是已经加载成功,如果没有委派父类加载器处理,逐层相同操作,最终加载请求都会传递到顶层的启动类加载器,只有当父类加载器无法完成加载请求时,子加载器才会尝试自己去加载。
双亲委派模型好处:因为不同类加载器加载同一个Class文件会是两个独立的类,所以双亲委派模型让Java类随着它的类加载器一起具备一种优先级的层次关系,例如类java.lang.Object,它存放在rt.jar包中,无论哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,确保了Object类在各种加载器环境中都是同一个类
验证
为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全,主要完成下面4个阶段:
准备
正式为类变量分配内存并在方法区设置类变量初始值
public static int value = 123;
通常情况下变量value在准备阶段过后var初始值为0而不是123,初始值为数据类型的默认值。到了初始化阶段,value复制123会在类构造器clinit()方法执行。
public static final int value = 123;
特殊情况在编译阶段会为value生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将value赋值为100 (如果同时使用final和static来修饰一个变量,并且这个变量的数据类型是基本类型或者java.lang.String,就会生成ConstantValue属性)。
解析
此阶段虚拟机将常量池内的符号引用替换为直接引用,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符。
符号引用:以一组符号来描述所引用的目标,可以是任何形式的字面量,符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中
直接引用:直接指向目标的指针、相对偏移量或一个能间接定位到目标的句柄
初始化
到了初始化阶段,类中的Java程序代码才开始执行,是执行类构造器clinit方法的过程,clinit方法由类变量赋值动作和静态语句块(static{})组成,顺序由语句在源文件出现的顺序决定。
注:
①.clinit方法与init方法不同,它不需要显式地调用父类clinit方法,虚拟机会保证父类的clinit优先执行
②.clinit方法对于类或接口不是必须的,如果一个类中没有静态代码块,也没有静态变量的赋值操作,那么编译器可以不为这个类生成clinit方法
③.行接口的clinit方法不需要先执行父接口的clinit方法,只有使用了父接口中定义的变量时,父接口才会初始化。接口的实现类在初始化时也一样不会执行接口的clinit方法
④.如果多个线程同时去初始化一个类,只会有一个线程去执行这个类的clinit方法,其余线程阻塞等待,当执行clinit方法的那条线程退出clinit方法后,其余线程唤醒后也不会再进入clinit方法。同一个类加载器下,一个类型只会初始化一次
不同类加载器初始化:
public class ClassLoaderTest {
static {
System.out.println("clinit");
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ClassLoader myLoader = new ClassLoader() {
@Override
public Class loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(is == null){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException();
}
}
};
myLoader.loadClass("load.ClassLoaderTest").newInstance();
// Object obj = Class.forName("load.ClassLoaderTest").newInstance();
}
}
输出:
clinit
clinit
感谢
《深入理解Java虚拟机》