本篇文章主要是详细写一下个人对 Java ClassLoader 的理解
首先回顾一下, java 虚拟机载入 java 类的步骤: java 文件经过编译器编译后变成字节码文件 (.class 文件), 类加载器(ClassLoader) 读取. class 文件, 并且转换成 java.lang.Class 的一个实例, 最后通过 newInstance 方法创建该类的一个对象 ClassLoader 的作用就是根据一个类名, 找到对应的字节码, 根据这些字节码定义出对应的类, 该类就是 java.lang.Class 的一个实例
类加载器的组织结构
java 有三个初始类加载器, 当 java 虚拟机启动时, 它们会按照以下顺序启动: Bootstrap classloader -> extension classloader -> system classloader 三者的关系: bootstrap classloader 是 extension classloader 的 parent,extension classloader 是 system classloader 的 parent
bootstrap classloader
它是最原始的类加载器, 并不是由 java 代码写的, 是由原生代码编写的 Java 有一次编译所有平台运行的效果, 就是因为它写了一份功能相同, 但针对不同平台不同语言实现的底层代码它负责加载 java 核心库, 大家可运行以下代码, 看看自己本地的 java 核心库在哪里:
- URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
- for (int i = 0; i < urls.length; i++) {
- System.out.println(urls[i].toExternalForm());
- }
本人的运行结果:
- file:/home/eric/jdk1.6.0_35/jre/lib/resources.jar
- file:/home/eric/jdk1.6.0_35/jre/lib/rt.jar
- file:/home/eric/jdk1.6.0_35/jre/lib/sunrsasign.jar
- file:/home/eric/jdk1.6.0_35/jre/lib/jsse.jar
- file:/home/eric/jdk1.6.0_35/jre/lib/jce.jar
- file:/home/eric/jdk1.6.0_35/jre/lib/charsets.jar
- file:/home/eric/jdk1.6.0_35/jre/lib/modules/jdk.boot.jar
- file:/home/eric/jdk1.6.0_35/jre/classes
- extension classloader
它用来加载 JRE 的扩展目录(JAVA_HOME/jre/lib/ext 或 java.ext.dirs 系统属性指定的)JAR 的类包注意, 因为它是 bootstrap classloader 加载的, 所以当你运行:
- ClassLoader extensionClassloader = ClassLoader.getSystemClassLoader().getParent();
- System.out.println("the parent of extension classloader :" + extensionClassloader.getParent());
输出的是: the parent of extension classloader : null
system classloader
它用于加载 classpath 目录下的 jar 包, 我们写的 java 类, 一般都是由它加载, 除非你自己制定个人的类加载器
全盘负责委托机制
classloader 加载类时, 使用全盘负责委托机制, 可以分开两部分理解: 全盘负责, 委托
全盘负责机制: 若类 A 调用了类B, 则类 B 和类 B 所引入的所有 jar 包, 都由类 A 的类加载器统一加载
委托机制: 类加载器在加载类 A 时, 会优先让父加载器加载, 当父加载器加载不到, 再找父父加载器, 一直找到 bootstrap classloader 都找不到, 才自己去相关的路径去寻找加载以下是 ClassLoader 的源码:
- protected synchronized Class<?> loadClass(String name, boolean resolve)
- throws ClassNotFoundException
- {
- // First, check if the class has already been loaded
- Class c = findLoadedClass(name);
- if (c == null) {
- try {
- if (parent != null) {
- // 从父加载器加载
- c = parent.loadClass(name, false);
- } else {
- // 从 bootstrap loader 加载
- c = findBootstrapClassOrNull(name);
- }
- } catch (ClassNotFoundException e) {
- // ClassNotFoundException thrown if class not found
- // from the non-null parent class loader
- }
- if (c == null) {
- // If still not found, then invoke findClass in order
- // to find the class.
- c = findClass(name);
- }
- }
- if (resolve) {
- resolveClass(c);
- }
- return c;
- }
举个例子, 类加载器加载类 A 的过程:
1, 判断是否已经加载过, 在 cache 里面查找, 若有, 跳 7; 否则下一步
2, 判断当前加载器是否有父加载器, 若无, 则当前为 ext classloader, 跳去4; 否则下一步
3, 请求父加载器加载该类, 若加载成功, 跳 7; 若不成功, 即父加载器不能找到该类, 跳 2
4, 请求 jvm 的 bootstrap classloader 加载, 若加载成功, 跳 7; 若失败, 跳 5
5, 当前加载器自己加载, 若成功, 跳 7; 否则, 跳 6
6, 抛出 ClassNotFoundException
7, 返回 Class
编写自己的类加载器
Java 加载类的过程, 实质上是调用 loadClass()方法, loadClass 中调用 findLoadedClass()方法来检查该类是否已经被加载过, 如果没有就会调用父加载器的 loadClass(), 如果父加载器无法加载该类, 就调用 findClass()来查找该类
所以我们要做的就是新建 MyClassLoader 继承 java.lang.ClassLoader, 重写其中的 findClass()方法主要是重新设计查找字节码文件的方案, 然后调用 definedClass 来返回
本人写了一个 demo, 用自己的类加载器去加载指定 java 文件, 且带有热部署效果, 具体请查看以下 url
Demo 地址: http://git.oschina.net/ericquan8/hot-deploy
来源: http://www.codeceo.com/article/java-classloader-learn.html#0-tsina-1-20357-397232819ff9a47a7b7e80a40613cfe1