Java作为一种面向对象语言,类是其非常重要的组成部分。Java程序经过编译之后,每个类或接口都会对应一个class文件。Java类型的生命周期就是指一个类型的class文件从被JVM加载到被JVM卸载的全过程。类型的生命周期一般包括装载、连接、初始化、使用和卸载五个阶段。
1.装载
在装载阶段,Java虚拟机类加载器把类信息从二进制class文件读入JVM的方法区中,并在堆中创建该类型的java.lang.Class类的实例。
在class文件中,类型的信息是以一定格式组织的二进制数据流。而在虚拟机中,类型的信息是以一定内部数据结构形式存储在方法区中,同时每个类在堆中都有一个java.lang.Class类的实例,这个类的实例可以找到该类型在方法区中的类型数据。
类型需要通过类装载器进行装载,有两种类装载器:启动类装载器和自定义类装载器。启动类装载器是Java虚拟机的一部分,而自定义类装载器是用户自己定义的java.lang.ClassLoader的子类。每个类装载器都有一个命名空间,用来管理自己装载的类。不同的类装载器之间不会相互干扰,从而有助于安全的实现。
2.连接
装载阶段只是将类型的信息读入到虚拟机中,但是这些类型数据之间都是独立的,还不能真正被使用。连接阶段将方法区中的类型数据转化为虚拟机运行时的状态,即将这些类型数据关联起来,并为它们准备运行时所需要的资源。
连接阶段可以细分为三个步骤:验证、准备和解析。
-
验证:确认类型信息符合Java语言规范,保证它不会危及虚拟机的完整性。
验证内容主要包括:
-
类型与超类型之间不存在不兼容
-
final类不能有子类
-
final方法不能被覆盖
-
除Object类外的所有类都必须有一个超类
-
-
准备:为类变量非常内存,并设置默认初始值。
-
解析:将类型的常量池中符号引用,替换成直接引用。
3.初始化
如果类被首次主动使用,就需要对该类进行初始化,即为类变量赋予正确的初始值。
类变量的初始化通过初始化语句或者静态初始化程序块,编译器会将它们收集起来,并放到<clinit>方法中,只能被虚拟机调用进行初始化。
并非所有的类都会拥<clinit>() 方法,在以下条件中该类不会拥有 <clinit>() 方法:
- 该类既没有声明任何类变量,也没有静态初始化语句;
- 该类声明了类变量,但没有明确使用类变量初始化语句或静态初始化语句初始化;
- 该类仅包含静态 final 变量的类变量初始化语句,并且类变量初始化语句是编译时常量表达式。
4.使用
类的使用分为主动使用和被动使用两类。
被认为是主动使用的6种情况:
-
通过new关键字创建类的实例化对象
-
调用类的静态方法
-
操作类中静态变量
-
调用Java API中特定的反射方法
-
初始化一个类的子类
-
Java虚拟机的启动类,即拥有main方法且是启动方法的类
除了上面6种情况以外的使用,都是被动使用,例如:
-
引用父类的静态字段,只会引起父类的初始化,而不会引起子类的初始化
-
定义类数组
-
使用类的常量
注意:只有主动使用才会触发类的初始化,而被动使用不会触发类进行初始化。
5.卸载
被装载的类型会在方法区中占据内存空间。如果程序不再引用某类型,该类型就会变成不可触及,可以被卸载。
判断类型的Class实例是可触及的两种方式:
-
程序保持对该Class实例的明确引用
-
堆中可触及的对象在方法区中对应的类型数据或超类型指向该Class实例
注意:启动类装载器装载的类型永远是可以触及的,所以永远不会被卸载。只有使用自定义类装载器装载的类型才会变成不可触及,从而被虚拟机卸载。