原创

Java类加载器

前一章 Java 类加载机制讲了 java 类加载的理论基础,class 文件到底怎么加载到内存的,就是使用 jvm 类加载器,将二进制 class 文件解析成 Class<?>对象。本篇讲 jvm 团队具体怎么设计 java 类加载器

1.类加载器

java 类加载器作用:从磁盘、网络或其他来源加载 class 文件(字节码文件)加载到 jvm 内存中,并对字节码进行校验,解析和初始化,生成对应的 Class 对象。

类加载器加载过程 类加载器加载过程

class 文件(字节码文件)的来源有多种

  • jdk 编译的 class 文件,先是用编辑工具(如 eclipse)编写 java 代码;
  • Jar 文件,原始 jar 包,包含了很多 class 文件;
  • 网络,可以从网络中加载 class 文件;
  • jsp,jsp 编译后也是生成 class 文件;
  • 数据库中,保存在数据库中,可加密,安全性高;
  • 运行时计算生成,java 动态代理技术;

系统自带的类加载器分为三种:

启动类加载器(Bootstrap ClassLoader)

C++实现,负责加载 JAVA_HOME\lib 目录中的,或通过-Xbootclasspath 参数指定路径中的,且被虚拟机认可(按文件名识别,如 rt.jar)的类

扩展类加载器(Extension ClassLoader) 负责加载 JAVA_HOME\lib\ext 目录中的,或通过 java.ext.dirs 系统变量指定路径中的类库。

应用程序类加载器(Application ClassLoader) 负责加载用户路径(classpath)上的类库。JVM 通过双亲委派模型进行类的加载,当然我们也可以通过继承 java.lang.ClassLoader 实现自定义的类加载器

通过代码可以打印出来:

Main.java

public class Main {
    public static void main(String[] args) {
        Main main = new Main();
        System.out.println(main.getClass().getClassLoader());
        System.out.println(main.getClass().getClassLoader().getParent());
        System.out.println(main.getClass().getClassLoader().getParent().getParent());
    }
}

2.双亲委派机制

双亲委派机制类加载器模型
双亲委派机制类加载器模型

2.1 双亲委派机制工作过程:

如果一个类加载器收到了类加载器的请求.它首先不会自己去尝试加载这个类.而是把这个请求委派给父加载器去完成.每个层次的类加载器都是如此.因此所有的加载请求最终都会传送到 Bootstrap 类加载器(启动类加载器)中.只有父类加载反馈自己无法加载这个请求(它的搜索范围中没有找到所需的类)时.子加载器才会尝试自己去加载。

2.2 程序演示:

package java.lang;   // 自定义的包
public class String {
    public static void main(String[] args) {
        System.out.println("这是自定义的java.lang.String类");
    }
}

由于 jre\lib\ext 中存在 java.lang.String 类,当加载该类的时候,根据全限定名进行查找,找到后由启动类加载器加载,发现 String 类中不包含 main() 方法,因此程序出错。

2.3 双亲委派模型的优点:

java 类随着它的加载器一起具备了一种带有优先级的层次关系. 例如类 java.lang.Object,它存放在 rt.jart 之中.无论哪一个类加载器都要加载这个类.最终都是双亲委派模型最顶端的 Bootstrap 类加载器去加载.因此 Object 类在程序的各种类加载器环境中都是同一个类.相反.如果没有使用双亲委派模型.由各个类加载器自行去加载的话.如果用户编写了一个称为“java.lang.Object”的类.并存放在程序的 ClassPath 中.那系统中将会出现多个不同的 Object 类.java 类型体系中最基础的行为也就无法保证.应用程序也将会一片混乱.

3.自定义类加载器实例:

3.1Tomcat:正统的类加载器架构

Tomcat 自定义了多个类加载器,这些类加载器按照经典的双亲委派模型来实现,其架构如下图所示: 双亲委派模型

3.2OSGi:灵活的类加载器架构

OSGi(Open Service Gateway Initiative)是 OSGi 联盟制定的一个基于 java 语言的动态模块化规范。OSGi 在 java 程序员中最著名的应用案例就 Eclipse IDE,另外还有许多大型的软件平台和中间件服务器都基于或声明将会基于 OSGi 规范来实现。 OSGi 的 Bundle 类加载器之间只有规则,没有固定的委派关系。例如,某个 Bundle 声明了一个它依赖的 Package,如果有其他 Bundle 声明发布了这个 Package,那么所有对这个 Package 的类加载动作都会委派给发布它的 Bundle 类加载器去完成。不涉及某个具体的 Package 时,各个 Bundle 加载器都是平级关系,只有具体使用某个 Package 和 Class 的时候,才会根据 Package 导入导出定义来构造 Bundle 间的委派和依赖。 其架构如下图所示: osgi类加载器模型

OSGi 实现模块化热部署的关键则是它自定义的类加载器机制的实现。每一个程序模块都有一个自己的类加载器,当需要更换一个 Bundle 时,就把 Bundle 连同类加载器一起换掉以实现代码的热替换。在 OSGi 环境下,类加载器不再是双亲委派模型中的树状结构,而是复杂的网状结构。当收到类加载请求时,OSGi 将按照下面的顺序进行类搜索:

  1. 将以 java.*开头的类委派给父类加载器加载。
  2. 否则,将委派列表名单内的类委派给父类加载器加载。
  3. 否则,将 Import 列表中的类委派给 Export 这个类的 Bundle 的类加载器加载。
  4. 否则,查找当前 Bundle 的 ClassPath,使用自己的类加载器加载。
  5. 否则,查找类是否在自己的 Fragment Bundle 中,如果在,则委派给 Fragment Bundle 的类加载器加载。
  6. 否则,查找 Dynamic Import 列表的 Bundle,委派给对应的 Bundle 的类加载器加载。
  7. 否则,类查找失败。
正文到此结束