浅谈ClassLoader
本篇为学习JAVA虚拟机的第二篇文章,上一篇文章初步提到了class文件,以及一个最简单程序执行的指令含义,我们提到,是由JAVA虚拟机先加载这些编译好的class文件,然后再去根据解析出来的指令去转换为具体平台上的机器指令执行,但是加载这个class文件时如何加载的呢?其实就涉及比较重要的东西:ClassLoader
有一个基本认识,从编译到实例化对象的过程可以概括为以下三个阶段:
- 编译器将
xxx.java源文件编译为xxx.class字节码文件 ClassLoader将字节码转换为JVM种的Class<xxx>对象- JVM利用
Class<xxx>对象实例化为xxx对象
一、JVM系统结构

ClassLoader:依据特定格式,加载class文件到内存Execution Engine:对命令进行解析Native Interface:融合不同开发语言的原生库为Java所用Runtime Data Area:JVM内存空间结构模型
首先通过ClassLoader加载符合条件的字节码文件到内存中,然后通过Execution Engine解析字节码指令,交由操作系统去执行。
二、什么是ClassLoader
ClassLoader在java中有着非常重要的作用,它主要工作在Class装载的加载阶段,其主要作用是从系统外部获得Class二进制数据流。他是JAVA的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给JAVA虚拟机进行连接、初始化等操作。
简而言之,就是加载字节码文件。
我们翻开ClassLoader源码看看:
1 | public abstract class ClassLoader {...} |
它是一个抽象类,下面我们再来说具体的实现类。
里面比较重要的是loadClass()方法:
1 | public Class<?> loadClass(String name) throws ClassNotFoundException { |
就是根据name来加载字节码文件,返回Class实例,加载不到则抛出ClassNotFoundException异常。
三、ClassLoader的种类
- 启动类加载器(
Bootstrap ClassLoader):由C++语言实现(针对HotSpot),加载核心库java.*。
- 扩展类加载器(
Extension ClassLoader):Java编写,加载扩展库javax.*
它扫描的是哪个路径呢?

我们看到,它负责将 <JAVA_HOME >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 加载到内存中。
- 应用程序类加载器(
Application ClassLoader):Java编写,加载程序所在目录

它负责将 用户类路径(java -classpath或-Djava.class.path变量所指的目录,即当前类所在路径及其引用的第三方类库的路径,看截图的最后一行,显示的是当前项目路径。
- 自定义
ClassLoader:自定义
四、如何自定义ClassLoader
要自己实现一个ClassLoader,其核心涉及两个方法:
1 | protected Class<?> findClass(String name) throws ClassNotFoundException { |
首先想一下为什么是这两个类?
其实答案在loadClass()这个方法里面。如果已经熟悉双亲委派模型的同学,都会知道加载Class对象是先委派给父亲,看父亲是否已经加载,如果没有加载过,则从最顶层父亲开始逐层往下进行加载,这一块详细在下一篇文章中解释,我们先走马观花看看这个的核心方法长啥样:
1 | protected Class<?> loadClass(String name, boolean resolve) |
如果我们不去重写findClass(name)方法,默认是直接抛出找不到的异常,所以我们要对这个方法进行重写。
由于字节码文件是一堆二进制流,所以需要一个方法来根据这个二进制流来定义成一个类,即defineClass()这个方法来实现这个功能。
说的比较抽象,下面来真正实践一把!
五、实践自定义ClassLoader
首先写一个类:Robot.java
1 | public class Robot { |
在对Robot.java用javac编译之后形成Robot.class文件,就要删除本项目下的这个Robot.java文件,要不然就会被AppClassLoader类加载先加载了,而无法再被我们的自定义类加载器再去加载。这个Robot.class文件我就直接放到桌面去了。路径为C:/Users/swg/Desktop/.
然后定义一个自定义的ClassLoader,按照上面的理论,只要重写findClass就可以指定到某个地方获取class字节码文件,此时获取的是二进制流文件,转换为字节数组,最后借用defineClass获取真正的Class对象。
1 | public class MyClassLoader extends ClassLoader{ |
最后测试一下能不能用自定义类加载器去加载到Robot对应的Class对象:
1 | public class Test { |
打印结果:
1 | MyClassLoader@677327b6 |
好了,学习了关于ClassLoader的分类以及如何自定义ClassLoader,我们知道了类加载器的基本实现,上面谈到了一个重要方法是loadClass,这就涉及了类加载器的双亲委派模型。下一节从代码层面好好来说说这个,其实很简单。