之前在介绍JVM内存模型的时候(参看:JVM内存模型),提到了在运行时数据区之前,有个Class Loader,这个就是类加载器。用以把Class文件中的描述信息加载到内存中运行和使用。以下是《深入理解Java虚拟机第二版》对类加载器机制的定义原文:
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
一般我们把类从加载到内存到卸载出内存的整个过程分为七个阶段:加载,验证,准备,解析,初始化,使用和卸载。其中,验证、准备和解析统称为连接。
在这几个阶段中,加载、验证、准备、初始化和卸载这五个阶段的顺序是固定的,而解析阶段则不一定,它有时候可能会在初始化之后开始,这是为了支持Java的运行时绑定。需要特别注意的是,这里边的顺序指的是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常会互相交叉的混合进行。
了解类的加载机制非常有必要,下面将逐个解释说明类加载的全过程(即加载,验证,准备,解析,初始化五个阶段)。相信看完之后,你会对Java类某些问题有更深刻的理解(例如,为什么子类可以覆盖父类的字段和方法?饿汉式单例为什么天生是线程安全的?)
加载
加载过程分为三步:
1)通过一个类的全限定名来获取定义此类的二进制字节流。
2)将这个字节流所代表的的静态存储结构转化为方法区的运行时数据结构。
3)在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
上面的第一步获取二进制字节流,并没有限定只能从编译好的.class文件中获取,也可以是zip包,jar,war,网络流(Applet),运行时计算生成(如动态代理,通过反射在运行时动态生成代理类),其他文件(如jsp,因jsp最终会编译成class),数据库(用的场景较少)。
对于数组类的加载,和普通类的加载有所不同。数组类本身不通过类加载器加载,而是由虚拟机直接完成。但是数组类的元素类型(指数组类去除维度之后的类型,如String[] 数组的元素类型就是 String)是靠类加载器加载的。
加载阶段完成之后,虚拟机就会把外部的二进制字节流(不论从何处获取的)按照一定的数据格式存储在运行时数据区中的方法区。然后在内存中实例化一个java.lang.Class对象(Class这个对象比较特殊,它存放在方法区中而不是堆中),这个对象将作为程序访问方法区中的这些数据的外部接口。
验证
验证是连接阶段的第一步,这一阶段的主要目的就是确保Class文件流中的信息符合虚拟机的规范,并且不会危害虚拟机的安全。验证阶段一般分为四个阶段:文件格式