概要
一个类从出生到死亡会经历五个阶段:加载、连接、初始化、使用、卸载,其中连接又分为验证、准备和解析。加载、连接、初始化这三个阶段(类加载过程)是必定会执行,而使用和卸载不一定执行。
加载(Loading)
加载顾名思义就是将类加载到jvm中,在堆区中实例化一个java.lang.Class对象,作为方法区中类入口。
一般会有一下几种方式将类加载到内存中:
1.通过类的全限定名来获取类的class文件,再读取二进制流信息
2.从jar文件中读取
3.通过一定规则实时生成,例如设计模式中的动态代理
连接(Linking)
连接分为验证,准备和解析。
- 验证中的工作主要是验证类的合法性。如字节码格式,变量与方法的合法性、数据类型的有效性、继承与实现的规范性等,确保加载到内存中能够正常运行
- 准备主要是为类的静态变量分配内存并赋默认值,而非静态变量不会分配内存。值得注意的是,静态变量的赋值是默认值,而非程序设定的值,例如静态int类型的age字段,在准备阶段会被赋值为0,而真正的赋值程序设定的值是在初始化阶段
- 解析则是将常量池中的符号引用、方法区中的字符引用解析为直接引用(即将字符解析成计算机认识的二进制)
初始化(Initialization)
类初始化是类加载的最后一步,该阶段才开始真正执行程序代码,之前的动作都由虚拟机主导。
jvm对类加载时机没有明确规定,但对类的初始化有着明确规范:只有当类被直接引用的时候,才会触发类的初始化。类的直接引用有以下几种:
- new关键字创建对象
- 读取或设置类的静态变量
- 调用类的静态方法
- 通过反射动态获取类的行为
- 初始化子类会初始化父类
- 作为程序入口(mian方法)直接运行时
- 接口实现类初始化时,会触发直接或间接实现所有接口的初始化
类的初始化,会自上而下运行静态代码块或静态赋值语句,非静态与非赋值的静态语句不会执行 ;
如果存在父类,会优先初始化父类,这是一个典型的递归模型。
区别于对象的初始化,类的初始化所做的一起都是基于类变量或类语句的,也就是说执行的都是共性的抽象信息。而我们知道,类就是对象实例的抽象。
使用(Using)
类的使用分为直接引用和间接引用。
直接引用与间接引用等判别条件,是看对该类的引用是否会引起类的初始化
直接引用已经在类的初始化中的有过阐述,不再赘述。而类的间接引用,主要有下面几种情况:
- 当引用了一个类的静态变量,而该静态变量继承自父类的话,不引起初始化
- 定义一个类的数组,不会引起该类的初始化;
- 当引用一个类的的常量时,不会引起该类的初始化
卸载(Unloading)
当类使用完了之后,类就要进入卸载阶段了。那何为衡量类使用完的标准呢?
5. 该类所有的实例都已经被回收,也就是java堆中不存在该类的任何实例。
6. 加载该类的ClassLoader已经被回收。
7. 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法。
如果以上三个条件全部满足,jvm就会在方法区垃圾回收的时候对类进行卸载,类的卸载过程其实就是在方法区中清空类信息,java类的整个生命周期就结束了。