类加载器负责加载Java类的字节代码到Java虚拟机中,可以根据指定的类名(如java.lang.Object
)来装载class文件的内容到Runtime Data Area中的Method Area(方法区域)。Java程序员可以extends java.lang.ClassLoader
类来写自己的Class loader
。
类加载器的模型
类加载器双亲委派模型
如果一个类加载器接收到了类加载的请求,它首先把这个请求委托给他的父类加载器去完成,每个层次的类加载器都是如此,因此所有的加载请求都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它在搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
双亲委派模型的优点
Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object
,它存放在tools.jar
中,无论哪个类加载器要加载这个类,最终都会委派给启动类加载器进行加载,因此Object
类在程序的各种类加载器环境中都是同一个类。相反,如果用户自己写了一个名为java.lang.Object
的类,并放在程序的Classpath
中,那系统中将会出现多个不同的Object
类,Java类型体系中最基础的行为也无法保证,应用程序也会变得一片混乱。
类加载器的类型
站在JVM的角度讲,主要有两种类型加载器:启动类加载器和其它的类加载器。启动类加载器是JVM实现的一部分,使用C++语言实现,其它类加载器都由Java语言实现 ,独立于虚拟机外部,并且全部继承抽象类java.lang.ClassLoader
。
Bootstrap ClassLoader 启动类加载器
这是JVM的根ClassLoader
,它是用C++实现的,JVM启动时初始化此ClassLoader
,并由此ClassLoader
完成$JAVA_HOME$
中jre\lib\rt.jar
(Sun JDK的实现)中所有class
文件的加载,这个jar中包含了Java规范定义的所有接口以及实现(加载可信的类,包括Java的API)。启动类加载器无法被Java程序直接引用,Java程序在它开始运行之前并非被完全加载,其各个部分是在必需时才加载。
Extension ClassLoader 扩展类加载器
扩展类加载器负责加载<JAVA_HOME>\lib\ext
目录中或者java.ext.dirs
系统变量所指定的所有类库,开发者可以直接使用扩展类加载器。
Application ClassLoader 应用程序类加载器
JVM用此classloader
来加载用户类路径 (Classpath
)上所指定的类库,包含指定的jar包以及目录,该加载器有时也称为系统类加载器。开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
User-Defined ClassLoader 用户自定义类加载器
User-DefinedClassLoader
是Java开发人员继承ClassLoader
抽象类自行实现的ClassLoader
,基于自定义的ClassLoader
可用于加载非Classpath
中的jar以及目录。
类的加载的过程
类的加载过程:加载,链接,初始化。
加载
加载过程负责找到二进制字节码并加载至JVM中,JVM通过类名、类所在的包名通过ClassLoader
来完成类的加载。
连接
链接过程负责对二进制字节码的格式进行校验、初始化装载类中的静态变量以及解析类中调用的接口、类。
验证
确保被导入类的正确性。
准备
为类变量分配内存,并将其初始化为默认值。
解析
解析这个类创建的对其他类的所有引用。
初始化
初始化过程即为有父类的情况,对父类初始化,或者执行类中的静态初始化代码、构造器代码以及静态属性的初始化,在四种情况下初始化过程会被触发执行:调用new
;反射调用类中的方法;子类调用了初始化;JVM启动过程中指定的初始化类。
所有的类都是在对其第一次使用时候,动态加载到JVM中。当程序创建第一个对类的静态成员引用时,就会加载这个类。这个证明构造方法也是静态方法,即使构造方法没有static
关键字。因此new
操作符创建类的新对象也会被当做对类的静态成员的引用。