上一篇,我们详细讲解了java类的加载机制,本篇我们继续讲解java类加载器的内容。
本文学习目录:
什么是类加载器?
类加载器的树状层次结构
类加载器的工作原理
双亲委派模型
类加载的过程
一、 什么是类加载器?
类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 类的虚拟机使用 Java 方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成 java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的 newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java 字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
二、类加载器的树状层次结构
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。
类加载器的树状层次结构图
1.引导类加载器(bootstrap class loader):
它用来加载 Java 的核心库(jre/lib/rt.jar),是用原生C++代码来实现的,并不继承自java.lang.ClassLoader。加载扩展类和应用程序类加载器,并指定他们的父类加载器,在java中获取不到。
2.扩展类加载器(extensions class loader):
它用来加载 Java 的扩展库(jre/ext/*.jar)。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
3.系统类加载器(system class loader):
它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()来获取它。
4.自定义类加载器(custom class loader):
除了系统提供的类加载器以外,开发人员可以通过继承 java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
测试代码案例:
输出结果:
测试案例可以看出ClassLoader类是由AppClassLoader加载的。他的父亲是ExtClassLoader,ExtClassLoader的父亲无法获取是因为它是用C++实现的。
三、类加载器的工作机制
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类加载器就是寻找类的字节码文件并构造出类在JVM内部表示的对象组件。在java中,类加载器把一个类装入JVM中,要经过以下步骤:
1.装载:查找和导入Class文件;
2.链接:执行校验、准备和解析步骤,其中解析步骤是可以选择的;
(1)校验:检查载入Class文件数据的正确性;
(2)准备:给累的静态变量分配存储空间;
(3)解析:将符号引用转成直接引用;
3.初始化:对类的静态变量、静态代码块执行初始化工作。
类加载工作由ClassLoader及其子类负责,ClassLoader是一个重要的java运行时系统组件,他负责在运行时查找和装入Class字节码文件。JVM在运行时会产生三个ClassLoader:根装载器、扩展类加载器和应用程序类加载器。其中根装载器不是ClassLoader的子类,它是由C++语言编写,因此我们在java中看不到它。根装载器负责装载JRE的核心类库,如JRE目标下的大rt.jar、charsets.jar等。扩展类加载器和应用程序类加载器都是ClassLoader的子类,其中扩展类加载器负责装载JRE扩展目录ext中的JAR类包;应用程序类加载器负责加载Classpath路径下的类包。
四、双亲委派模型
双亲委派模型过程
某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
使用双亲委派模型的好处在于Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存在在rt.jar中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的Bootstrap ClassLoader进行加载,因此Object类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个java.lang.Object的同名类并放在ClassPath中,那系统中将会出现多个不同的Object类,程序将混乱。因此,如果开发者尝试编写一个与rt.jar类库中重名的Java类,可以正常编译,但是永远无法被加载运行。
双亲委派模型的系统实现
在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。
五、类加载器的过程
首先我们先看一下类加载器过程图,如下:
java类加载器过程图
类加载器的过程包括了三个大步骤,他们分别是:
加载
1.加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象。
2.通过类的全限定名获取此类的二进制字节流。
3.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
4.在内存中生成一个Class对象,作为方法区这个类的各种数据的访问入口。
加载通常由类加载器完成,加载类的方式具体有以下几项:
1.本地资源加载。
2.网络加载。Web Applet。
3.zip压缩包加载。jar,war。
4.运行时计算生成。动态代理技术。
5.其他文件生成。JSP应用。
6.从加密文件中读取,主要是为了防止class文件被反编译。
链接
链接分为三个步骤:
1.验证
确保class文件的字节流中包含信息符合虚拟机要求。
主要包括,文件格式验证,元数据验证,字节码验证,符号引用验证。
2.准备
为类变量分配内存,并为变量赋零值。
这里不包括用final修饰的static,因为final在变异的时候就会分配了,准备阶段会显式初始化。不会为实例变量分配初始化。
3.解析
将常量池内的符号引用变为直接引用。
初始化
初始化阶段就是执行类构造器方法()方法。
()方法是由javac将类变量赋值动作和静态代码块中的语句合并自动产生的。当类不存在类变量和静态代码块时不会自动生成此方法。一个类只会被加载一次,()方法是同步加锁的。
总结了这么多,你学会了吗?