JAVA类加载机制以及如何自定义类加载器
从双亲委派说起
- 启动(Bootstrap)类加载器:是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的类库加载到内存中(比如rt.jar)。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。
- 标准扩展(Extension)类加载器:是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量 java.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
- 系统(System)类加载器:是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。 除了以上列举的三种类加载器,还有一种比较特殊的类型 — 线程上下文类加载器。
双亲委派机制描述
- 某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
Tomcat的类加载机制
本文主要以tomcat7为例说明类加载机制,大家也可以参考tomcat7的类加载机制的官方文档。Tomcat7总的ClassLoader结构如下图:
各个类加载器加载类的范围:
- Bootstrap:包括java虚机机的基本类,以及$JAVA_HOME/jre/lib/ext下的类。
- System:包括CLASSPATH环境变量的类,主要是$CATALINA_HOME/bin/bootstrap.jar和$CATALINA_BASE/bin/tomcat-juli.jar两个jar包
- Common:包括tomcat的基本类,主要是$CATALINA_BASE/lib下的所有jar包
- WebappX:应用相关类,保证应用之间的类隔离。先加载/WEB-INF/classes,再加载/WEB-INF/lib/*.jar
加载顺序默认如下
- Bootstrap classes of your JVM
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
- System class loader classes (described above)
- Common class loader classes (described above)
如果设置了<Loader delegate="true"/>,加载顺序如下:
- Bootstrap classes of your JVM
- System class loader classes (described above)
- Common class loader classes (described above)
- /WEB-INF/classes of your web application
- /WEB-INF/lib/*.jar of your web application
setContextClassLoader的理解
JDK的解释是这样的:
Sets the context ClassLoader for this Thread. The context ClassLoader can be set when a thread is created, and allows the creator of the thread to provide the appropriate class loader, through , to code running in the thread when loading classes and resources.
并不是给线程设置了ContextClassLoader,这个线程下加载的类就都使用该ContextClassLoader。ContextClassLoader的用途是提供一个途径,使得线程运行时可以随时获得指定的ContextClassLoader进行类的加载,通过这个这种方式可以避开双亲委派模型,最典型的应用是JDBC、JNDI。
自定义的ClassLoader
自定义的ClassLoader通过继承ClassLoader来实现,也可以使用URLClassLoader更简单。如果需要改写类的加载过程最好覆盖findClass()而不是loadClass(),loadClass()是为了保持jdk1.2之前的兼容。使用findClass()能保证不会违背双亲委派模式。
如何使用自定义的ClassLoader new出对象呢?前面说过的setContextClassLoader是不对的,必须用自定义的ClassLoader,通过反射实例化一个初始类,由该初始类加载的其他类就都会使用自定义的ClassLoader了。可以分析一下tomcat的代码:
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
通过反射机制调用startupInstance的方法,之后的类就会都有自定义的ClassLoader加载,当然有个前提就是不能违背双亲委派模型——自定义的ClassLoader加载的类在父ClassLoader加载的类中不存在。为什么要这么做?类加载的时候有一个规律,被加载类使用调用者所用的ClassLoader进行类的加载。可以通过Class.forName()的代码得到这个结论:
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}