类在JVM的生命周期分别经历:加载、验证、准备、解析、初始化、使用和卸载7个阶段,其中验证、准备和解析也叫连接阶段。
1、加载
由类加载器进行加载class文件,加载的途径可以是本地文件、网络数据流、数据库等能获取到class文件的地方。
java采用双亲委派机制进行类加载,也就是对某个类进行加载的时候,先尝试让父类加载,父类加载不了时再由自己加载,java这样设计主要是为了不让系统类被覆盖。
2、验证
验证是为了确保class文件的合法性,验证包括如下方式:
(1)文件格式验证
是否以魔数0xCAFEBABE开头、主次版本号是否在当前虚拟机处理范围之内、常量池的常量是否有不被支持的类型等。
(2)元数据验证
该类是否有父类(只有Object对象没有父类,其余都有)、该类是否继承了不允许被继承的类(被final修饰的类)、如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法等。
(3)字节码验证
保证跳转不会跳到方法体以外的字节码指令上、保证方法体内的类型转换是合法的等。
(4)符号引用验证
符号引用中通过字符串描述的全限定名是否找到对应的类、在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段等。
可以通过虚拟机参数**-Xverify: none**来关闭验证,加速类加载时间。
3、准备
准备阶段的任务是为类或者接口的静态字段分配空间,并且默认初始化这些字段,例如下面代码:
private static int a = 20;
在准备阶段的初始化值为0,到了初始化阶段才会被赋值为20。
而对于常量,则在准备阶段就被初始化为字面定义的值,比如下面代码:
private static final int a = 20;
则在准备阶段就被初始化为20。
4、解析
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:常量池中存储的那些描述类、方法、接口的字面量。
直接引用:直接指向目标的指针、相对偏移量或能间接定位到目标的句柄。
虚拟机将运行时常量池中那些仅代表其他信息的符号引用解析为直接指向所需信息所在地址的指针。
5、初始化
在准备阶段,变量都被赋予了初始值,但是到了初始化阶段,所有变量才根据用户编写的代码重新赋值。
初始化阶段是执行类构造器
public class Test {
static {
i=0; //可以赋值
System.out.print(i); //编译器会提示“非法向前引用”
}
static int i=1;
}
思考题:下面程序输出的结果是什么?
public class Test {
public static void main(String[] args) {
Singleton s = Singleton.getInstance();
System.out.println(s.count1);
System.out.println(s.count2);
}
}
class Singleton {
private static Singleton instance = new Singleton();
public static int count1 ;
public static int count2 = 0;
private Singleton (){
count1++;
count2++;
}
public static Singleton getInstance(){
return instance;
}
}
答案是:1 和 0。
为什么呢?因为在准备阶段,count1和count2的值为0,而在初始化阶段,instance执行时,count1和count2值都为1,但由于count1没有赋值,所以程序往下执行时,count1的值还是为1,而count2赋值0,因此count2原来的1被覆盖为0。
6、使用
类在JVM正常使用,比如创建了对象实例等等。
7、卸载
通常系统自带的类加载器不会卸载类,而自定义的类加载器有可能会卸载类,当类不再使用,执行垃圾回收时,类可能被卸载。
8、测试
package com.xuanwu.base.jvm;
public class ClassLife {
public static void main(String[] args) throws Exception {
MyClassLoader myClassLoader = new MyClassLoader("D:/01-Personal/03-Study/01-IdeaWorkSpace/java-learning/target/classes");
Class<?> aClass = myClassLoader.findClass(ClassLife.class.getName());
Object object = aClass.newInstance();
System.out.println(aClass);
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
object = null;
aClass = null;
myClassLoader = null;
System.gc();
Thread.sleep(5000);
}
}
运行前加入JVM配置:-verbose:class -XX:+TraceClassLoading -XX:+TraceClassUnloading
从控制台打印的日志中,可以看到加载和卸载的日志信息。