1. 一些基本的 ClassLoader
Java 中基本的 ClassLoader 有 3 个: BootStrapClassLoader 、 ExtClassLoader 和 AppClassLoader 。
- BootStrapClassLoader: 用于加载 <JAVA_HOME>/lib 下的类。
- ExtClassLoader: 用于加载 <JAVA_HOME>/lib/ext 下的类。
- AppClassLoader: 用于加载当前应用程序中用到的类。
应用程序启动后,先由 BootStrapClassLoader 加载 <JAVA_HOME>/lib 下基本的 jar ,然后由 ExtClassLoader 加载 <JAVA_HOME>/lib/ext 下的 jar ,最后由 AppClassLoader 加载 CLASSPATH 目录下定义的类。
2. 类的加载 / 查找过程 – 使用 parent 委托模式
当需要加载一个新类型 A 的时候:
- 当前 ClassLoader 查找 A 是否已加载,如果已加载则直接返回。
- 如果 ClassLoader 的 parent 不为空,则使用 parent 去加载 / 查找该类型。
- 如果 parent 为空或 parent 加载失败,则使用 BootStrapClassLoader 去查找该类型是否已加载。
- 如果仍然加载不成功,才使用自己去加载该类。
- 如果还不成功,则抛出 ClassNotFoundException 。
使用 parent 委托模式的好处是:可以避免重复加载;可以保证核心类型(如 java.lang.String )不被用户定义覆盖。
3. 编写自己的 ClassLoader
- 直接继承抽象类 ClassLoader 。
-
覆盖方法
protected
Class<?> findClass(String name) throws
ClassNotFoundException
,一般的流程为:从资源中读取类定义到字节数组中,然后调用父类中的方法
defineClass()
来从字节数组获取
Class
对象,最后直接返回
Class
对象。
public class MyClassLoader extends ClassLoader { protected Class<?> findClass(String name) throws ClassNotFoundException { byte[] bytes = this.loadClassBytes(name); Class<?> c = super.defineClass(name, bytes, 0, bytes.length); if (c == null) throw new ClassNotFoundException("class definition is null"); return c; } private byte[] loadClassBytes(String name) { ... ... ... ... } }
4. 一点体会
不同的 ClassLoader 如果没有 parent-child 关系,则它们的 context 是相互独立的。同一个类在不同的 ClassLoader 中被看成是不同的且毫无关系的两个类型。
例如,执行下面的代码段,会发现输出均为false。
public void testLoadClassWithDifferentClassLoader() throws Exception {
URL url = new URL("file:d:/temp/");
URLClassLoader cl1 = new URLClassLoader(new URL[] { url });
URLClassLoader cl2 = new URLClassLoader(new URL[] { url });
Class<?> c1 = cl1.loadClass("Sample1");
Class<?> c2 = cl2.loadClass("Sample1");
System.out.println("c1==c2? " + (c1 == c2));
System.out.println("c1.equals(c2)? " + c1.equals(c2));
}