类加载器
本文部分参考《深入理解Java虚拟机》 -- 周志明 著
类和类加载器的关系
类加载是指将class文件(准确说是符合规范的字节码,不一定非要以class文件的形式存在)加载到内存这个动作,而这个动作是由类加载器classloader完成的。
在Java中,要比较两个类是否相等,除了这两个类必须来自同一个class文件,还必须是由同一个类加载器加载的。这里所说的“相等”,包括了使用Class对象的equals()方法来判断,或instanceof等关键字的判断结果。
双亲委派模型
Java中的类加载器
启动类加载器 Bootstrap Classloader
启动类加载器是JVM的一部分,对用户不可见,应用程序无法直接引用。启动类加载器负责加载<JAVA_HOME>/lib目录或被-Xbootclasspath参数所指定的目录中的类库。
扩展类加载器 Extension Classloader
扩展类加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>/lib/ext目录,或java.ext.dirs系统变量指定的目录中的类库。
应用程序类加载器 Application Classloader
也叫系统类加载器,由sun.misc.Launcher$AppClassLoader实现,负责加载用户classpath,或
java.class.path系统变量指定的目录中的类库。一般如果程序没有自定义类加载器,这就是应用的默认类加载器。
提示
可以用静态方法java.lang.ClassLoader#getSystemClassLoader获取到系统类加载器的引用。
几个类加载器的关系如下:
当然应用也可以根据需要自定义类加载器,所有类加载器(除了Bootstrap)都直接或间接继承自抽象类java.lang.ClassLoader。
双亲委派模型 Parents Delegation Model
实现双亲委派模型的代码都在java.lang.ClassLoader#loadClass方法中,简单来说,双亲委派模型就是在进行类加载时:
- 检查类是否已被加载过
- 如果类未被加载,则调用父类加载器来加载
- 如果父类加载器为null,则表明使用启动类加载器来加载
- 如果父类加载器抛出ClassNotFoundException,则调用自定义的findClass()方法进行加载。
提示
上面所说的父类加载器并不是指继承关系,而是通过组合方式来指定的,双亲委派模型要求除了Bootstrap类加载器外,其他类加载器都要指定父类加载器。可以在构造器中指定,如果不指定则父类加载器就是Application ClassLoader。
提示
用户如果自定义类加载器,应重写findClass()方法,而不是重写loadClass()方法,这样就不会破坏双亲委派规则。
双亲委派模型的作用
双亲委派模型的存在是有其意义的,它让类加载器有了一种层级关系,同一个类只能由一个类加载器进行加载。假如你自己编写了一个java.lang.String类,由于双亲委派机制,会一直向上委派到Bootstrap类加载器进行加载,而不会导致系统内出现两个java.lang.String类。
打破双亲委派
双亲委派模型并不是一种强制约束,只是一种推荐的类加载器实现,也是Java的默认实现。在双亲委派模型下,越是通用的类或越“底层”的类会交给越上层的类加载器去加载,用户代码就可以访问这些通用的类。但反过来说,如果需要让通用的类去访问用户代码就不行了,例如Java中定义的一些SPI(JNDI、JDBC等),就需要父类加载器委托子类加载器去加载。另外在需要用到动态加载和卸载的地方往往也需要打破双亲委派规则。
Tomcat的类加载机制
TODO