类加载器和这个类本身一起共同确立其在Java虚拟机中的唯一性。即使这两个类来源于同一个Class文件,被同一个Java虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等
文章目录
启动类加载器(Bootstrap Class Loader)
这个类加载器负责加载存放在<JAVA_HOME>\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar。Bootstrap ClassLoader无法直接被java程序引用,如果需要把加载请求委派给它,直接用null代替即可。
扩展类加载器(Extension Class Loader)
它负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量所
指定的路径中所有的类库。由于扩展类加载器是由Java代码实现的,开发者可以直接在程序中使用扩展类加载器来加载Class文件。
应用程序类加载器/系统类加载器(Application Class Loader)
它负责加载用户类路径(ClassPath)上所有的类库,开发者同样可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
双亲委派模型
双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器(逻辑上的父类)去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
当一个类加载器(比如User Classloader,用UC代替)收到加载A类型的请求时,他会把加载请求向上委派到父类加载器(AC)。AC接到请求后抛给它的父类(EC)加载器,EC接到请求抛给父类(BC)。然后BC从它的扫描范围内开始寻找,如果找到了就完成加载工作,否则抛回给EC,同理依次向下。直到一个类加载器可以完成加载任务。
使用双亲委派模型可以确保一个类的加载是由同一个类加载器完成的。比如不同的类加载器接到加载java.lang.Object类的请求,最后都会向上委派给启动类加载器完成。而不会出现每个类加载器各自加载这个类的情况,从而导致出现名称相同但是类加载器不同的多个类。
JDK1.8中的双亲委派实现,跟书上有些出入(同步锁的获取放在了方法内部)
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 首先,检查类是不是已经加载了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else {
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// 如果非空父类加载器找不到该类则抛出ClassNotFoundException
}
if (c == null) {
// 如果还是找不到则调用findClass方法来找
long t1 = System.nanoTime();
c = findClass(name);
// 记录状态用的自定义类加载器
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}