什么是类加载器?
类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作在Java虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为“类加载器”。
类与类加载器之间的关系
对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。也就是说:比较两个类是否“相等”,只有在这个两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那么这两个类就必定不想等。
package cn.hao.test; import java.io.IOException; import java.io.InputStream; public class ClassLoaderTest { public static void main(String[] args) throws Exception { ClassLoader myLoader = new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class"; InputStream is = getClass().getResourceAsStream(fileName); if (is == null) { return super.loadClass(name); } byte[] b = new byte[is.available()]; is.read(b); return defineClass(name, b, 0, b.length); } catch (IOException e) { throw new ClassNotFoundException(name); } } }; Object obj = myLoader.loadClass("cn.hao.test.ClassLoaderTest").newInstance(); System.out.println(obj.getClass()); System.out.println(obj instanceof cn.hao.test.ClassLoaderTest); } }
运行结果:
class cn.hao.test.ClassLoaderTest
false
使用自定义加载器加载了一个名为“cn.hao.test.ClassLoaderTest”的类,并且实例化了这个类的一个对象。输出结果表明,这个对象确实是类cn.hao.test.ClassLoaderTest实例化出的一个对象,但是这个对象与类cn.hao.test.ClassLoaderTest做所属类型检查的时候返回了false,因为虚拟机中存在了两个ClassLoaderTest类,一个由系统应用程序类加载器加载的,另一个是由自定义的类加载器加载的,虽然都来自同一个Class文件,但是它们依然是两个独立的类,因此做对象所属类型检查的结果是false。
Java虚拟机中类加载器
从Java虚拟机的角度来看,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一个部分;另一种是所有其他类加载器,这些类加载器独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
从Java开发人员的角度看,绝大多数Java程序都会使用到以下3种系统提供的类加载器:
启动类加载器(Bootstrap ClassLoader):负责将存在<JAVA_HOME>\lib目录中,或者被-Xbootclasspath参数所指定的路径中的,并且是虚拟机识别的(如rt.jar)类库加载到虚拟机内存中。
扩展类加载器(Extension ClassLoader):复负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。
应用程序类加载器(Application ClassLoader):一般也被称为系统类加载器。负责加载用户类路径(ClassPath)上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
双亲委派模型
下图显示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。
双亲委派模型的工作过程:
如果一个类加载器收到了类加载的契求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传递到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。
使用双亲委派模型的好处:
最大和好处就是保证Java中最基础的行为不会被破坏。例如类java.lang.Object,它存在rt.jar之中,无论哪个类加载器要加载这个类,最终都会委派给处于模型最顶层的启动类加载器进行加载,因此Object类在程序的各种类加载器环境中都是同一类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户自己编写了一个名称为java.lang.Object的类,并放在CLASSPATH中,那系统中会将出现多个不同的Object类,Java类型体系中最基础的行为也就无法保证,应用程序也将会变得一片混乱。
参考
1、周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社