Java类加载器

类加载器作用

当JVM开始运作时需要使用当某个类时,就需要将对应类的字段吗加载到内存中,而类加载器正式负责加载这些类的工具。另外若果多次重复使用一个类的字节码时加载器不会多次加载,而是使用内存中的字节码。

主要的类加载器

我们首先看一下JVM预定义的三种类型类加载器,当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:

  • 引导(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将JRE/lib/tr.jar加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
  • 标准扩展(Extension)类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将JRE/lib/ext/*.jar或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • 系统(System)类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。

三个类加载器之间的继承关系

BootStrap——负责加载JRE/lib/tr.jar

/|\

ExtClassLoader——负责加载JRE/lib/ext/*.jar

/|\

AppClassLoader——负责加载CLASSPATH所指定的所有jar或目录(SystemClassLoader)

说明:每个加载器也是一个类,也需要被加载器加载,显然需要有一个最顶级加载器,而且它不需要被加载,这个加载器就是BootStrap加载器,它不是一个类,而是贮存在JVM内核中的一段C++代码,JVM启动会自动执行这段代码。BootStrap加载器负责JRE/lib/tr.jar中的类,当然包括其他加载器。

加载器委托加载机制

在加载类时,当前的类加载器(发起者)首先让其父类去加载,其父类加载器又让父类加载器加载,以此类推,直到提交到最顶级类加载器,即BootStrap加载器为止,这时BootStrap加载器开始加载,若找不到该类,则返回给要求它加载的加载器去加载,直到加载到,若发起者(最先请求加载的类加载器)也找不到该类,则抛出ClassNotFoundException。

类加载器中一些重要方法

  • 方法 loadClass

    ClassLoader.loadClass()是ClassLoader的入口点,程序通过调用该方法进行类的加载。方法签名如下:Class loadClass( String name, boolean resolve);
    参数name指定Java虚拟机需要的类的全名(含包名),比如Foo或者java.lang.Object。
    参数 resolve指定该类是否需要解析你可以把类的解析理解为完全为运行做好准备。解析一般都不需要。如果Java虚拟机只想知道这个类是否存在或者想知道它的父类的话,解析就完全没有必要了。 在Java1.1和它以前的版本,如果要自定义类加载器,loadClass方法是唯一需要在子类中覆盖的方法.(ClassLoader在Java1.2中有所改变,提供了方法findClass())。

  • 方法findClass()

    Java1.2之后自定义类加载器,除了要继承ClassLoader外,只需要覆写该方法即可。在调用loadClass()时,如果父类加载器找不到,会调用该类加载器的findClass()进行查找类。
    方法 defineClass
    defineClass 是ClassLoader中一个很神秘的方法。这个方法通过一个字节数组来构建类实例。这个包含数据的原始字节数组可能来自文件系统,也可能是来自网络。defineClass 表
    明了Java虚拟机的复杂性,神秘性和平台依赖性-它通过解释字节码把它转化为运行时数据
    结构,检查有效性等等。但是不用担心,这些都不用你去实现。其实,你根本不能覆盖它,
    因为该方法被关键字final修饰。
    方法 findSystemClass
    findSystemClass方法从本地系统加载文件。它在本地系统寻找类文件,如果找到了,调用
    defineClass把原始字节数组转化成类对象。这是运行Java应用时Java虚拟机加载类的默认机制。对于自定义类加载器,只有在我们无法加载之后才需要用findSystemClass。 原因很简单: 我们的类加载器负责执行类加载中的某些特定的步骤,但并不是对所有的类。比如,即使我们的类加载器从远程站点加载了某些类,仍然有很多基本的类要从本地系统加载。
    这些类不是我们关心的,所以我们让Java虚拟机以默认的方式加载他们:从本地系统。这就是findSystemClass做的事情。整个过程大致如下:

    1. java虚拟机请求我们自定义的类加载器加载类。
    2. 我们检查远程站点是否有这个需要加载的类。
    3. 如果有,我们获取这个类。
    4. 如果没有,我们认为这个是类在基本类库中,调用findSystemClass从文件系统中加载。

      在大多数自定义类加载器中,你应该先调用findSystemClass来节省从远程查找的时间。
      实际上,正如我们将在下一部分看到的,只有当我们确定我们已经自动编译完我们的代码后
      才允许Java虚拟机从本地文件系统加载类。

  • 方法resolveClass

    正如上面说的,类记载可以分为部分加载(不解析)和完全加载(包括解析)。我们创建自
    定义类加载器的时候,可能要调用resolveClass。

  • 方法 findLoadedClass

    findLoadedClass实现一个缓存:当要求loadClass来加载一个类的时候,可以先调用这个方法看看这个类是否已经被加载,防止重新加载一个已经被加载的类。这个方法必须先被调用,我们看一下这些方法是如何组织在一起的。

我们的例子实现loadClass执行以下的步骤。(我们不指定通过某种具体的技术获得类文件,-它可能从网络,从压缩包或者动态编译的。无论如何,我们获得的是原始字节码文件)

  1. 程序调用该类加载器的loadClass()方法。
  2. loadClass()内部调用findLoadedClass检查这个类是否已经加载。
  3. 如果没有加载,调用父类加载器(父类再调用父类)加载,加载到,返回这个类。
  4. 否则,调用findCLass()方法,加载类。具体代码在findClass()中,执行查找文件,读取,编译等操作,生成字节码,返回。
  5. 如果参数resolve为true,调用resolveClass来解析类对象。
  6. 如果还没有找到类,抛出一个ClassNotFoundException异常。

现在我们看一下ClassLoader中的loadClass()源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First, check if the class has alreadybeen loaded
Class c = findLoadedClass(name);
if (c == null) {
try {
if (parent !=null) {
c = parent.loadClass(name,false);
} else {
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);
}
}

大致这是这样。

用户自定义类加载器

通过前面的分析,我们可以看出,要想实现自定义类加载器,除需要继承ClassLoader之外,还有就是覆写findClass()方法,在这里面写自己的操作。
一般用户自定义类加载器的工作流程:

  1. 首先检查请求的类型是否已经被这个类装载器装载到命名空间中了,如果已经装载,直接返回;否则转入步骤2
  2. 委派类加载请求给父类加载器(更准确的说应该是双亲类加载器,整个虚拟机中各种类加载器最终会呈现树状结构),如果父类加载器能够完成,则返回父类加载器加载的Class实例;否则转入步骤3
  3. 调用本类加载器的findClass(…)方法,试图获取对应的字节码,如果获取的到,则调用defineClass(…)导入类型到方法区;如果获取不到对应的字节码或者其他原因失败,返回异常给loadClass(…), loadClass(…)转抛异常,终止加载过程(注意:这里的异常种类不止一种)。
    (说明:这里说的自定义类加载器是指JDK1.2以后版本的写法,即不覆写改变java.lang.loadClass(…)已有委派逻辑情况下)
    具体就不举例子了!