什么是类加载器
类加载器本身作用就是用于加载类的;类加载器是通过类的全限定名来获取类的二进制字节流进而实现加载类。注意:类加载器不是只有一个,而是多个并且也可以自定义类加载器。jvm中的多个加载器如何加载类的就是双亲委派模型的话题了。
类和类加载器
当我们比较两个类是否相等的时候,首先必须要确认是否由同一个类加载器加载,否则就算是使用相同class文件,被不同加载器加载比较结果还是不相等的。(相等可以指equals方法、isAssignableFrom方法、isInstance方法、instanceof方法等)。
系统提供类加载器
- 启动类加载器 Bootstrap ClassLoader
启动类加载器是专门加载<JAVA_HOME>\lib下或者-Xbootclasspath参数指定的路径下的类库,加载到jvm的内存中去。该类加载器使用c++语言实现,是虚拟机自身实现的一部分。 - 扩展类加载器 Extension ClassLoader
扩展类加载器是专门加载<JAVA_HOME>\lib\ext或者java.ext.dirs系统变量指定路径的类库;由sun.misc.Launcher$ExtClassLoader类加载器实现。 - 应用程序类加载器 Application ClassLoader
应用程序类加载器专门加载用户类路径Classpath上指定的类库。(就是项目bin目录下的class文件)如果我们不指定自定义的类加载器,那么程序中默认使用这个类加载器;由sun.misc.Launcher$AppClassLoader类加载器实现。
双亲委派模型

解释:
双亲委派模型除了顶层的启动类加载器之外,剩下的类加载器都必须要有自己的父类加载器。(此处的父子关系不是继承而是组合)
- 工作过程:
首先类加载器收到了加载类的请求时,自己不会马上去尝试加载,而是把这个请求委派给父类加载器去完成,所以每层的类加载器都是这样的方式。最终加载的请求就都传到了顶层的启动类加载器。但是发现父类无法完成类加载请求,这个时候子类才开始尝试加载。
- 双亲委派模型的加载器顺序:
public class MyTest {
public static void main(String[] args) {
ClassLoader classLoader = MyTest.class.getClassLoader();
while (classLoader != null) {
System.out.println(classLoader);
classLoader = classLoader.getParent();
}
}
}

说明:因为最后请求委派给Bootstrap ClassLoader去完成的,但是Bootstrap ClassLoader是使用c/c++实现的,不在java堆中,所以ExtClassLoader的父加载器获取到是null。
- 双亲委派模型向上委派
public class MyLoader {
public void printLoader() {
System.out.println("My App ClassLoader");
}
}
public class CaseTest {
public static void main(String[] args) {
MyLoader myLoader = new MyLoader();
myLoader.printLoader();
}
}
运行结果:

追加到classpath的后面:-Xbootclasspath/a:E:\MyTemp\MyClass.jar;

运行结果:

反编译看下E:\MyTemp\MyClass.jar;

说明:从上面例子代码就可以看出,虽然应用程序类加载器加载了MyLoader.class文件,但是根据双亲委派模型,一个类加载器收到类加载的请求,不会自己去尝试加载,而是将这个请求委派给父类加载器,因为双亲委派模型是单向的,除非父类加载器加载不了,子加载器才会尝试自己去加载,而我们将MyLoader.class文件在启动类加载器中加载了一份,由于启动类加载器(父类加载器可以加载),那么就无需应用程序类加载器去实现了,所以我们看到的结果是 “ My Bootstrap ClassLoader ” 。
双亲委派模型实现:
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) {
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;
}
}
过程:
- 首先先检查类是否已经被加载了。如果被加载了,那么解析加载的类。
- 如果没有被加载,那么使用父类加载器加载,如果父类加载器为空,那么直接使用启动类加载器来加载类。
- 如果最终父类加载失败,那么直接使用当前的findClass方法来加载。
网友评论