1. 类加载器
两个类相等需要类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。
这里的相等,包括类的 Class 对象的 equals()
方法、isAssignableFrom()
方法、isInstance()
方法的返回结果为 true,也包括使用 instanceof 关键字做对象所属关系判定结果为 true。
以下代码自定义了一个类加载器,其中 obj 是 ClassLoaderTest 类的实例,但 obj instanceof ClassLoaderTest
为 false
,这是因为虚拟机中存在了两个ClassLoaderTest
类,一个是由系统应用程序类加载器加载的,另一个是由我们自定义的类加载器加载的,虽然来自同一Class文件,但依然是两个独立的类。
package com;
import java.io.InputStream;
import java.io.IOException;
public class ClassLoaderTest {
public static void main(String[] args) throws Exception {
ClassLoader ml = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
// 这里重写了 loadClass 函数,破坏了双亲委派模型
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
// name == "ClassLoaderTest.class" , 从该类所在包下获取文件。
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null) {
System.out.println("is null");
return super.loadClass(name);
}
byte[] b = new byte[is.available()]; // 申请byte空间
is.read(b); // 写入byte数组
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = ml.loadClass("com.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof ClassLoaderTest);
// (obj instanceof ClassLoaderTest) -> false
}
}
2. 类加载器的分类
从 Java 虚拟机的角度来讲,只存在以下两种不同的类加载器:
-
启动类加载器(
Bootstrap ClassLoader
),这个类加载器用 C++ 实现,是虚拟机自身的一部分。 -
所有其他类的加载器,这些类由 Java 实现,独立于虚拟机外部,并且全都继承自抽象类
java.lang.ClassLoader
。
从 Java 开发人员的角度看,类加载器可以划分得更细致一些:
-
启动类加载器(
Bootstrap ClassLoader
)此类加载器负责将存放在<JRE_HOME>\lib
目录中的,或者被-Xbootclasspath
参数所指定的路径中的,并且是虚拟机识别的(仅按照文件名识别,如 rt.jar,名字不符合的类库即使放在 lib 目录中也不会被加载)类库加载到虚拟机内存中。启动类加载器无法被 Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器,直接使用 null 代替即可。 -
扩展类加载器(
Extension ClassLoader
)这个类加载器是由ExtClassLoader
(sun.misc.Launcher$ExtClassLoader
)实现的。它负责将<JAVA_HOME>/lib/ext
或者被java.ext.dir
系统变量所指定路径中的所有类库加载到内存中,开发者可以直接使用扩展类加载器。 -
应用程序类加载器(
Application ClassLoader
)这个类加载器是由AppClassLoader
(sun.misc.Launcher$AppClassLoader
)实现的。由于这个类加载器是ClassLoader
中的getSystemClassLoader()
方法的返回值,因此一般称为系统类加载器。它负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
3. 双亲委派模型
应用程序都是由三种类加载器相互配合进行加载的,如果有必要,还可以加入自己定义的类加载器。
下图展示的类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。
该模型要求除了顶层的启动类加载器外,其余的类加载器都应有自己的父类加载器。这里类加载器之间的父子关系一般通过组合(Composition)关系来实现,而不是通过继承(Inheritance)的关系实现。
类加载的双亲委派模型在 JDK 1.2
之后才被引入,并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载器实现方式。
3.1 工作过程
如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器完成。每个类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException
),子加载器才会尝试自己去加载。
3.2 好处
使得 Java 类随着它的类加载器一起具有一种带有优先级的层次关系,从而使得基础类得到统一。
例如 java.lang.Object
存放在 rt.jar 中,如果编写另外一个 java.lang.Object
的类并放到 ClassPath 中,程序可以编译通过。由于双亲委派模型的存在,所以在 rt.jar 中的 Object 比在 ClassPath 中的 Object 优先级更高,这是因为 rt.jar 中的 Object 使用的是启动类加载器,而 ClassPath 中的 Object 使用的是应用程序类加载器。rt.jar 中的 Object 优先级更高,那么程序中所有的 Object 都是 rt.jar 中的 Object。
3.3 实现
双亲委派模型的代码都集中在 java.lang.ClassLoader
的 loadClass()
方法中,JDK 8 源码如下:
// 非 ClassLoader 全部代码
public abstract class ClassLoader {
// The parent class loader for delegation
private final ClassLoader parent;
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
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) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 若有父加载器,则调用父加载器的loadClass
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.
long t1 = System.nanoTime();
// 如果父加载器加载失败则自己加载
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
// 返回加载的类
return c;
}
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 现在不提倡用户去覆盖loadClass()方法,而应当把自己的类加载逻辑写到findClass()方法中。
throw new ClassNotFoundException(name);
}
}