Java 类加载器( ClassLoader)浅析

背景知识:

java平台无关的特性是由JVM支撑的。不同平台有不同的JVM支持。这其实就是在运行的java源代码和实际的硬件之间抽象出了一个新的层次结构。(据说“计算机中的大多数问题,都可以通过曾加层次结构来解决”)

所有.java文件中的java源代码,首先通过编译器产生出后缀为.class的字节码(bytecode)文件。然后由JVM运行那些.class文件。

JVM工作原理如下图

Java 类加载器( ClassLoader)浅析

所有的class文件都需要由class loader装载。可以简单的将JVM理解为一个工厂,class 文件就是等待加工的原料,class loader就是装载货物的工人。

装载之后,则进入到JVM真正的runtime机制中,开始运行。那是后话啦。


class loader作用

从class loader的名字里,大家都能理解到它能够完成class文件的装载。

这种动态装载的技术,是java的一种创新,让类能够动态加载到JVM中执行。

Java programming dynamics, Part 1: Java classes and class loading


但他的意义远非这么简单。详细了解装载的过程机制后,会发现,class loader对于java的sandybox模型具有重大意义,他和security manager(负责对class文件中的字节码进行校验verification,防止恶意代码的攻击)一起保证了JVM运行的安全性。


class loader机制

大体上,每个java应用使用了如下几种类型的类加载器(class loader):

1,bootstrap class loader ( 从Java1.2开始,它只加载java 核心API部分)。

它由C++编写(注意,它非常特殊,并非ClassLoader的子类),当JVM启动时,bootstrap class loader也随之启动,加载java 核心类,JRE目标下的rt.jar、charsets.jar等。

因为这是系统信任的类,所以这里的装载,跳过了一些对字节码的验证过程。“the bootstrap loader skips much of the validation that gets done for normal (untrusted) classes.”

2,extension class loader-扩展类加载器。顾名思义,它负责加载/lib/ext中的java扩展类。

3,System Class Loader,这是很重要的一个加载器,加载classpath下的类。应用程序的装载默认由它负责。

4,由System Class Loader继承出的 用户自定义加载器。它的存在让我们能定制出各种不同功能的加载器,大大的增加了java的扩展性。自定义的class laoder如果没有显示的继承关系,则parent默认位system class loader.


一个JVM,只拥有一个bootstrap class loader,却可以拥有很多user-defined class loader,方便不同应用环境的用户定制:比如,自定义的class loader能够动态的修改字节码,能够接收并加载从网上传来的.class文件。当然,不仅限于.class文件,也可以是.jar包,甚至是任何编码方式的压缩包,只要定制的class loader能够正确识别并调用相应method实现类的加载和解析,一切都有可能。

Java 类加载器( ClassLoader)浅析

四种加载器当然不是四个独立的部分,他们之间具有一种特殊的 parent-child relationship ,共同组成了一条 父亲、子代关系链,被称作parent-delegation 模式。如上图

"Each class loader also keeps a reference to a parent class loader"

类加载器按照如此树形排列。通常的,类加载时的查找顺序是:

cache --> parent --> self

子类加载器需要加载某个类时,并不是直接加载,而是首先查看cache(cache可以理解为对已经在此加载过的类的记录),如果没有,则向parent提出请求,查看是否存在于parent的cache中。如此往上,直到根部的bootstrap class loader. 如果bootstrap class loader的cache没有这个类,那么他开始尝试加载它,若成功加载,则传递这个成功加载的类到最底层的user-defined class loader(表现形式为一个对应的Class类的instance实例),若无法加载这个类,那么它又会委派他的child加载器去尝试加载。

getParent() 方法可以获得class loader的父亲。Initiating class loader是指那些直接被程序要求加载某类的加载器,而defining class loader就是真正加载了某类的加载器。

public class ClassLoaderTest { public static void main(String[] args) { ClassLoader loader = Thread.currentThread().getContextClassLoader(); System.out.println("current loader:"+loader); System.out.println("parent loader:"+loader.getParent()); System.out.println("grandparent loader:"+loader.getParent().getParent()); } }

显示的结果是

current loader:[email protected]
parent loader:[email protected]
grandparent loader:null

grandparent是 null,并不意味着他没有parent,而是这个parent就是由C++编写的bootstrap class loader,他并不是classloader的子类,也就无法使用getParent()方法获得返回了。


如此,parent class loader总是拥有更高的加载优先级,这让想利用自定义加载器伪装加载某些重要类的恶意代码无法得逞。如果好奇,你可以尝试自己写packagejava.lang里的String类,加载执行试试~

另外,当类A调用另类B时,B会由加载A的class loader加载,从而实现。




class loader 流程

Java 类加载器( ClassLoader)浅析

类的装载大致可以分为三个步骤(如上图所示):

1.装载 loading

2.链接 linking

3.初始化 initialising

跟C++ 或者C程序有很大的不同,编译过后的.class文件中的字节码并没有设计好内存布局,这些需要等到加载之后的链接阶段,才会完成。这也是java可移植性中精彩的一笔!

详情参加我另外一篇博文《java类的装载(Loading)、链接(Linking)和初始化(Initialization)


关于ClassLoader具体的编程实践,请参照另一篇博文:

ClassLoader编程实践