前言
虚拟机类加载阶段“通过一个类的全限定名来获取此类的二进制字节流”这个动作是Java虚拟机之外实现的,所以程序可以自定义如何去获取所需要的加载类。
类加载器
对于任意一个类来说,此类的类加载器和类本身共同确定了类的唯一性。即,一个类的Class文件相同,类加载器相同,才能是同一个类。
类加载
Java 虚拟机角度,只有2种类加载器:启动类加载器(Bootstrap ClassLoader);其他类加载器。
对于Java开发来说,可以再次细致划分下,分为3种:
- 启动类加载器(Bootstrap ClassLoader):加载JAVAHOME/lib目录中的class,或者指定。
- 扩展类加载器(Extension ClassLoader):加载JAVAHOME/lib/ext目录中的class,或者指定。
- 应用程序类加载器(Application ClassLoader):加载用户类路径上(ClassPath)的类库。
注:应用类加载器可以直接使用,如果没有定义过类加载器,这就是默认的类加载器。
双亲委派模型
要求:除了顶层的启动类加载器歪,其他的类加载都要有自己的父类加载器。
工作过程:类加载器收到类加载请求时,先将请求委派给父类去加载,每一层都是如此,一直传递到顶层的启动类加载器,父类加载好就直接返回,无法加载时(自己加载的类范围内找不到这个Class),子加载器才会尝试加载。
源码分析
源码都在ClassLoader当中:
//系统类加载器,即应用程序加载类 Application ClassLoader
public static ClassLoader getSystemClassLoader() {
initSystemClassLoader();
if (scl == null) {
return null;
}
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkClassLoaderPermission(scl, Reflection.getCallerClass());
}
return scl;
}
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();
//父加载器无法加载的情况下,调用本身的findClass方法加载
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;
}
}
//实现自己类加载
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
如果有自己的类加载器,那么实现findClass()方法。
破坏双亲委派模型
大部分情况的类加载器都遵循这个模型,也有特殊情况。
- 在Java 1.2之前未发明双亲委派时,发生的破坏。
- 当基础类加载用户代码时,无法实现,如:JNDI、JDBC等。此时,采取线程上下文类加载器(Thread Context ClassLoader),自己设置类加载器,进行类的加载。
- 程序动态性,导致需要热加载等等。OSGi类加载机制,开发出来网状的类加载机制。
Tomcat类加载器架构
Web服务器需要解决问题:
- 一个服务器上多个Web使用的Java类库互相隔离。解决同时依赖不同的第三方版本时,必须都加载。
- 一个服务器上多个Web共享使用的Java类库。节约资源。
- 服务器的类加载不受到Web应用程序的影响。
- 支持JSP应用的Web服务器,需要热部署机制。
Tomcat为此规划了很多类加载器,目录也进行了区分。
- common目录:tomcat和web都可以加载访问。
- server目录:仅仅tomcat加载访问。
- shared目录:仅仅web可以加载访问。
- /WebApp/WEB-INF目录:仅web应用程序加载访问。
应用程序类加载器 <— Common类加载器 <— Catalina类加载器
应用程序类加载器 <— Common类加载器 <— Shared类加载器 <— WebApp类加载器 <— Jsp类加载器
技巧
当发生Jar冲突时,即可采取类加载器区分加载。