什么是类加载器
ClassLoader
类加载器,顾名思义是用来加载Class的,负责将Class文件的字节码信息加载进内存。
类文件
Java
诞生之时曾经提出一个非常著名的宣传口号:一次编写,多次运行。这是最初对突破平台界限的渴求。
Write Once, Run Anywhere.
Sun公司及其其他虚拟机提供商提供了许多可以运行在各种平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现程序的“一次编写,到处运行”,这个字节码就是构成平台无关性的关键基石。同时,在1997年发版第一版Java虚拟的时候曾经承诺:在未来,我们会对Java虚拟机进行适当的扩展,以便更好的支持其他语言运行在JVM之上。
In the future, we will consider bounded extensions to the Java virtual machine to provide better support for other languages.
在发布规范文档时,可以把Java的规范分成了Java的语言规范《The Java Language Specifiction》及Java虚拟机规范《The Java Virtual Machine Specification》。
基于以上背景,Java虚拟机不和包括Java在内的任何语言绑定,它只和Class文件这种特定的二进制格式所关联,就形成了一下的关系。
虚拟机不用关系Class的文件的来源。
类与类加载器
对于任意一个类,都需要有加载它的类加载器和这个类本身一同确认其在Java虚拟机内的唯一性,每个类加载器,都拥有一个独立的类名称空间。
双亲委派
“双亲委派”是一个典型翻译失败的例子,看上去让人很不好理解,英文原意:The parent-delegation model
,双亲其实就是parent的意识。
3中系统提供的类加载器:
ClassLoader类型 | 负责包路径 | 描述 |
---|---|---|
Bootstrap ClassLoader | 启动类加载器 | JAVA_HOME/lib | C++实现,虚拟机的一部分 |
Extension ClassLoader | 扩展类加载器 | JAVA_HOME/lib/ext | Java实现,独立于虚拟机之外 |
Application ClassLoader | 系统类加载器 | ClassPath | Java实现,独立于虚拟机之外,一般情况下默认加载器 |
双亲委派模型:如果一个类加载器收到类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每个层次的类加载器都是如此,因此素有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己在自己的搜索范围内没有找到所需要的类时,子加载器才会尝试自己去加载。
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;
}
}
双亲委派的好处是Java类随这它的类加载器一起具备了一种带有优先级的层次关系。例如类java.lang.Object,它存放在rt.jar之中,无论哪个类加载器要加载这个类,都最终委派给处于模型最顶层的启动类加载器进行加载,因此Object类在程序的各类加载器环境中加载都是同一个类,不会导致系统的混乱,一定程度上保证了系统的稳定运行。
双亲委派模型不是一个强制性的规范约束限制,所以可以存在破坏双亲委派机制。
破坏双亲委派机制:
- 继承重写loadClass方法
- 线程上下文类加载器(Thread Context ClassLoader)
- OSGi模块化热部署
自定义加载器的使用场景
1)类不一定放在设置好的classPath下,对于自定义路径需要自己的类加载器。(比如Tomcat,每个WebApp有自己的ClassLoader进行加载)
2)类文件的来源可以从网络直接读取,可能需要一些加密、解密的操作,可以在自定义的类加载器中进行。
3)类的热部署,OSGi使用中需要通过实现自己的ClassLoader。
参考:
《深入理解Java虚拟机-第二版》