类加载器
-
类加载分类:
- 启动类加载器(BootstrapClassLoader):最顶层的加载类,由C++实现,负责加载
<JAVA_HOME>\lib
目录下以及被-Xbootclasspath
参数所指定的路径中存放的,并且能够被Java虚拟机识别的类(jar等等)。 - 扩展类加载器(ExtensionClassLoader):负责加载 <JAVA H O ME>\lib\ext 目录中,或者被 java.ext.dirs 系统变量所指定的路径中所有的类库。
- 应用程序类加载器(AppClassLoader):面向用户的加载器,负责加载用户类路径(ClassPath)上的所有类库,可以在代码中使用该类加载器。
- 启动类加载器(BootstrapClassLoader):最顶层的加载类,由C++实现,负责加载
除了BootstrapClassLoader加载器,其他的均由Java实现,且全部继承自Java.lang.ClassLoader
。
双亲委派模型
双亲委派的工作流程为:- 当一个类加载器收到了类加载的请求,首先会判断当前类是否被加载过,若加载过则直接返回该类。
- 若没有被加载过,会先将该请求委派给父类加载器去完成。
- 该请求会递归的传送到顶层的启动类加载器中。
- 当父类加载器无法完成该请求时(即搜索范围中没有找到请求所需的类),子加载器才会尝试去加载。
- 当父类加载器为null时,会使用启动类加载器
BootstrapClassLoader
作为父类加载器。
类加载器之间的父子关系一般不是以继承( Inheritance)的关系来实现的,而是通常使用组合(Composition)关系来复用父加载器的代码。
双亲委派的优点:Java中的类随着加载器一起具备了带有优先级的层次关系。若无层次关系,类都由各个加载器自行去加载,若用户此时编写了一个名为java.lang
的类,并放在程序的ClassPath中,那么系统会因多个加载器而产生多个不同的java.lang
类(JVM 区分不同类的方式不仅仅根据类名,相同的类文件被不同的类加载器加载产生的是两个不同的类), Java 类型体系中最基础的行为也就无从保证,程序将变得混乱。
双亲委派模型的实现源码:
private final ClassLoader parent;
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) {//父加载器不为空,调用父加载器loadClass()方法处理
c = parent.loadClass(name, false);
} else {//父加载器为空,使用启动类加载器 BootstrapClassLoader 加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
//抛出异常说明父类加载器无法完成加载请求
}
if (c == null) {
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;
}
}