ClassLoader(一)- 介绍

本文源代码在Githubhtml

本文仅为我的笔记,不该做为权威参考。java

原文git

什么是ClassLoader

javadoc ClassLoadergithub

A class loader is an object that is responsible for loading classes.
...
Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class.
A typical strategy is to transform the name into a file name and then read a "class file" of that name from a file system.

简单来讲:bootstrap

  1. ClassLoader是一个负责加载Class的对象。
  2. 给ClassLoader一个类名(需符合Java语言规范),那么它就应该尝试定位,或者生成包含该类定义的数据。
  3. 一个典型的定位策略是把类名转换成class文件名,而后从文件系统里读取这个class文件。

三种ClassLoader实现

讲到bootstrap class loader就不得不说三种常见的ClassLoader实现。api

执行下面代码会看到三种类型的ClassLoader实现:数组

import com.sun.javafx.util.Logging;
import java.util.ArrayList;
public class PrintClassLoader {
  public static void main(String[] args) {
    System.out.println("Classloader for ArrayList: " + ArrayList.class.getClassLoader());
    System.out.println("Classloader for Logging: " + Logging.class.getClassLoader());
    System.out.println("Classloader for this class: " + PrintClassLoader.class.getClassLoader());
  }
}

结果以下:安全

Classloader for ArrayList: null
Classloader for Logging: sun.misc.Launcher$ExtClassLoader@5e2de80c
Classloader for this class: sun.misc.Launcher$AppClassLoader@18b4aac2
  • Bootstrap class loader。bootstrap class loader是native code写的。它是全部ClassLoader的祖先,它是顶级ClassLoader。它负责加载JDK的内部类型,通常来讲就是位于$JAVA_HOME/jre/lib下的核心库和rt.jar
  • Extension class loader。即Extension class loader,负责加载Java核心类的扩展,加载$JAVA_HOME/lib/ext目录和System Property java.ext.dirs所指定目录下的类(见Java Extension Mechanism Architecture)。
  • System class loader,又称Application class loader。它的parent class loader是extension class loader(能够从sun.misc.Launcher的构造函数里看到),负责加载CLASSPATH环境变量、-classpath/-cp启动参数指定路径下的类。

类的ClassLoader

每一个Class对象引用了当初加载本身的ClassLoader(javadoc ClassLoader):oracle

Every Class object contains a reference to the ClassLoader that defined it.

其实Class对象的getClassLoader()方法就可以获得这个ClassLoader,而且说了若是该方法返回空,则说明此Class对象是被bootstrap class loader加载的,见getClassLoader() javadocjvm

Returns the class loader for the class. Some implementations may use null to represent the bootstrap class loader. This method will return null in such implementations if this class was loaded by the bootstrap class loader.

数组类的ClassLoader

Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.

简单来讲说了三点:

  1. 数组也是类,可是它的Class对象不是由ClassLoader建立的,而是由Java runtime根据须要自动建立的。
  2. 数组的getClassLoader()的结果同其元素类型的ClassLoader
  3. 若是元素是基础类型,则数组类没有ClassLoader

下面是一段实验代码:

import com.sun.javafx.util.Logging;
public class PrintArrayClassLoader {
  public static void main(String[] args) {
    System.out.println("ClassLoader for int[]: " + new int[0].getClass().getClassLoader());
    System.out.println("ClassLoader for string[]: " + new String[0].getClass().getClassLoader());
    System.out.println("ClassLoader for Logging[]: " + new Logging[0].getClass().getClassLoader());
    System.out.println("ClassLoader for this class[]: " + new PrintArrayClassLoader[0].getClass().getClassLoader());
  }
}

获得的结果以下,符合上面的说法:

ClassLoader for int[]: null
ClassLoader for string[]: null
ClassLoader for Logging[]: sun.misc.Launcher$ExtClassLoader@5e2de80c
ClassLoader for this class[]: sun.misc.Launcher$AppClassLoader@18b4aac2

那若是是二维数组会怎样呢?下面是实验代码:

import com.sun.javafx.util.Logging;
public class PrintArrayArrayClassLoader {
  public static void main(String[] args) {
    System.out.println("ClassLoader for int[][]: " + new int[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for string[][]: " + new String[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for Logging[][]: " + new Logging[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for this class[][]: " + new PrintArrayClassLoader[0][].getClass().getClassLoader());
    System.out.println("ClassLoader for this Object[][] of this class[]: " + new Object[][]{new PrintArrayArrayClassLoader[0]}.getClass().getClassLoader());
  }
}

结果是:

ClassLoader for int[][]: null
ClassLoader for string[][]: null
ClassLoader for Logging[][]: sun.misc.Launcher$ExtClassLoader@5e2de80c
ClassLoader for this class[][]: sun.misc.Launcher$AppClassLoader@18b4aac2
ClassLoader for this Object[][] of this class[]: null

注意第四行的结果,咱们构建了一个Object[][],里面放的是PrintArrayArrayClassLoader[],但结果依然是null。因此:

  1. 二维数组的ClassLoader和其定义的类型(元素类型)的ClassLoader相同。
  2. 与其实际内部存放的类型无关。

ClassLoader类的ClassLoader

ClassLoader自己也是类,那么是谁加载它们的呢?实际上ClassLoader类的ClassLoader就是bootstrap class loader。下面是实验代码:

import com.sun.javafx.util.Logging;
public class PrintClassLoaderClassLoader {
  public static void main(String[] args) {
    // Launcher$ExtClassLoader
    System.out.println("ClassLoader for Logging's ClassLoader: " + Logging.class.getClassLoader().getClass().getClassLoader());
    // Launcher$AppClassLoader
    System.out.println("ClassLoader for this class's ClassLoader: " + PrintClassLoaderClassLoader.class.getClassLoader().getClass().getClassLoader());
    // 自定义ClassLoader
    System.out.println("ClassLoader for custom ClassLoader: " + DummyClassLoader.class.getClassLoader().getClass().getClassLoader());
  }
  public static class DummyClassLoader extends ClassLoader {
  }
}

结果是:

ClassLoader for Logging's ClassLoader: null
ClassLoader for this class's ClassLoader: null
ClassLoader for custom ClassLoader: null

ClassLoader解决了什么问题

简单来讲ClassLoader就是解决类加载问题的,固然这是一句废话。JDK里的ClassLoader是一个抽象类,这样作的目的是可以让应用开发者定制本身的ClassLoader实现(好比添加解密/加密)、动态插入字节码等,我认为这才是ClassLoader存在的最大意义。

ClassLoader的工做原理

仍是看javadoc的说法

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

简单来讲:

  1. ClassLoader使用委托模型(国内广泛称之为双亲委派模型)查找Class或Resource。
  2. 每一个 ClassLoader 实例都有一个parent ClassLoader。
  3. 当要查找Class或者Resource的时候,递归委托给parent,若是parent找不到,才会本身找。举例说明:若是ClassLoader层级关系是这样A->B->C,若是被查找Class只能被A找到,那么过程是A-delegate->B-delegate->C(not found)->B(not found)->A(found)。
  4. JVM有一个内置的顶级ClassLoader,叫作bootstrap class loader,它没有parent,它是老祖宗。

ContextClassLoader

ClassLoader的委托模型存在这么一个问题:子ClassLoader可以看见父ClassLoader所加载的类,而父ClassLoader看不到子ClassLoader所加载的类。

这个问题出如今Java提供的SPI上,简单举例说明:

  1. Java核心库提供了SPI A
  2. 尝试提供了本身的实现 B
  3. SPI A尝试查找实现B,结果找不到

这是由于B通常都是在Classpath中的,它是被System class loader加载的,而SPI A是在核心库里的,它是被bootstrap class loader加载的,而bootstrap class loader是顶级ClassLoader,它不能向下委托给System class loader,因此SPI A是找不到实现B的。

这个时候能够经过java.lang.Thread#getContextClassLoader()java.lang.Thread#setContextClassLoader来让SPI A加载到B。

为什么SPI A不直接使用System class loader来加载呢?我想这是由于若是写死了System class loader那就缺乏灵活性的关系吧。

Class的惟一性

若是一个类被一个ClassLoader加载两次,那么两次的结果应该是一致的,而且这个加载过程是线程安全的,见ClassLoader.java源码:

protected Class<?> loadClass(String name, boolean resolve)
      throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
    // 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 {
            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);

        // ...
      }
    }
    // ...
    return c;
  }
}

若是一个类被两个不一样的ClassLoader加载会怎样呢?看下面代码:

// 把这个项目打包而后放到/tmp目录下
public class ClassUniqueness {

  public static void main(String[] args) throws Exception {
    Class<?> fooClass1 = Class.forName("me.chanjar.javarelearn.classloader.ClassUniqueness");
    System.out.println("1st ClassUniqueness's ClassLoader: " + fooClass1.getClassLoader());

    // 故意将parent class loader设置为null,不然就是SystemClassLoader(即ApplicationClassLoader)
    URLClassLoader ucl = new URLClassLoader(new URL[] { new URL("file:///tmp/classloader.jar") }, null);
    Class<?> fooClass2 = ucl.loadClass("me.chanjar.javarelearn.classloader.ClassUniqueness");
    System.out.println("2nd ClassUniqueness's ClassLoader: " + fooClass2.getClassLoader());

    System.out.println("Two ClassUniqueness class equals? " + fooClass1.equals(fooClass2));
  }

}

运行结果是:

1st ClassUniqueness's ClassLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
2nd ClassUniqueness's ClassLoader: java.net.URLClassLoader@66d3c617
Two ClassUniqueness class equals? false```

观察到两点:

  1. 虽然是同一个类,可是加载它们的ClassLoader不一样。
  2. 虽然是同一个类,可是它们并不相等。

由此能够得出结论:一个Class的惟一性不只仅是其全限定名(Fully-qualified-name),而是由【加载其的ClassLoader + 其全限定名】联合保证惟一。

这种机制对于解决诸如类冲突问题很是有用,类冲突问题就是在运行时存在同一个类的两个不一样版本,同时代码里又都须要使用这两个不一样版本的类。解决这个问题的思路就是使用不一样的ClassLoader加载这两个版本的类。事实上OSGi或者Web容器就是这样作的(它们不是严格遵守委托模型,而是先本身找,找不到了再委托给parent ClassLoader)。

参考文档

广告

clipboard.png

相关文章
相关标签/搜索