类加载过程
类从被加载到虚拟机内存中开始到卸载出内存为止,它的整个生命周期包括七个阶段:
加载、验证、准备、解析、初始化、使用和卸载。
类的加载过程包括:加载、验证、准备、解析、初始化五个阶段。
1、加载、验证、准备、初始化四个阶段发生的顺序数确定的。
2、解析阶段在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也称为动态绑定或晚期绑定)
3、这几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉的混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。
Java绑定
绑定指把一个方法的调用与方法所在的类(方法主体)关联起来,对Java来说,绑定分为:静态绑定和动态绑定。
静态绑定:即前期绑定,在程序执行前方法以及被绑定,此时由编译器或其它连接程序实现。针对Java,可以理解为程序编译期的绑定。Java中的方法只有final、static、private和构造方法是前期绑定的。
动态绑定:即晚期绑定,也叫运行时绑定。在运行时根据具体对象的类型进行绑定。在Java中,几乎所有的方法都是后期绑定的。
下面详细讲述类加载过程中每个阶段所做的工作。
加载
加载是类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情:
1、通过一个类的全限定名来获取其定义的二进制字节流。
2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。
验证
验证的目的是为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,而且不会危害虚拟机自身的安全。不同的虚拟机对类验证的实现可能会有所不同,但大致都会完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。
文件格式验证:验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理,验证的目的是保证输入的字节流能正确的解析并存储于方法区内。经过该阶段的验证后,字节流才会进入内存的方法区中进行存储,后面的三个验证都基于方法区的存储结构进行。
元数据验证:对类的元数据信息进行语义校验(即对类中的各数据类型进行语法校验),保证不存在不符合Java语法规范的元数据信息。
字节码验证:主要工作是进行数据流和控制流分析,对类的方法体进行校验分析,以保证被校验的类的方法在运行时不会做出危害虚拟机安全的行为。
符号引用验证:这是最后一个阶段的验证,它发生在虚拟机将符号引用转化为直接引用的时候,主要是对类自身以外的信息(常亮池中的各种符号引用)进行匹配性的校验。
准备
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
1、这时候进行内存分配的仅包括类变量(static),而不包括实例变量,实例变量会在对象实例化时随着对象一块分配在Java堆中。
2、这里所设置的初始值通常情况下是数据类型默认的零值(如0、0L、null、false等),而不是被在Java代码中被显式的赋予的值。
例如:
public static int value = 3;
变量value在准备阶段过后初始值为0,而不是3,因为这时候尚未开始执行任何Java方法,而把value赋值为3的public static指令是在程序编译后,存放于类构造器
下面列出Java中所有基本数据类型以及reference类型的默认零值:
数据类型 | 默认零值 |
---|---|
int | 0 |
long | 0L |
short | 0 |
char | ‘\u0000’ |
byte | 0 |
boolean | false |
float | 0.0f |
double | 0.0d |
reference | null |
还需要注意以下几点:
1、对基本数据类型来说,对于类变量(static)和全局变量,如果不显式地对其赋值而直接使用,则系统会为其赋予默认的零值,而对于局部变量来说,在使用前必须显式地为其赋值,否则编译时不通过。
2、对于同时被static和final修饰的常量,必须在声明的时候就为其显式地赋值,否则编译时不通过;而只被final修饰的常量则既可以在声明时显式地为其赋值,也可以在类初始化时显式地为其赋值,总之,在使用前必须为其显式地赋值,系统不会为其赋予默认零值。
3、对于引用数据类型reference来说,如数组引用、对象引用等,如果没有对其进行显式地赋值而直接使用,系统都会为其赋予默认的零值,即null。
4、如果在数组初始化时没有对数组中的各元素赋值,那么其中的元素将根据对应的数据类型而被赋予默认的零值。