本文共 10899 字,大约阅读时间需要 36 分钟。
2.3 双亲委派模型
Java 提供三种类型的系统类加载器:启动类加载器(Bootstrap ClassLoader):由 C++ 语言实现,属于 JVM 的一部分,其作用是加载 <JAVA_HOME>\lib 目录中的文件,或者被 -Xbootclasspath 参数所指定的路径中的文件,并且该类加载器只加载特定名称的文件(如 rt.jar ),而不是该目录下所有的文件。启动类加载器无法被 Java 程序直接引用。
扩展类加载器( Extension ClassLoader ):由 sun.misc.Launcher.ExtClassLoader 实现,它负责加载 <JAVA_HOME>\lib\ext 目录中的,或者被 java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加载器。 应用程序类加载器( Application ClassLoader ):也称系统类加载器,由 sun.misc.Launcher.AppClassLoader 实现。负责加载用户类路径( Class Path )上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。双亲委派模型的工作机制:
如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。 为什么? 例如类 java.lang.Object ,它存放在 rt.jar 之中。无论哪一个类加载器都要加载这个类。最终都是双亲委派模型最顶端的 Bootstrap 类加载器去加载。因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有使用双亲委派模型,由各个类加载器自行去加载的话,如果用户编写了一个称为 「java.lang.Object」 的类,并存放在程序的 ClassPath 中,那系统中将会出现多个不同的 Object 类, java 类型体系中最基础的行为也就无法保证,应用程序也将会一片混乱。 3. Tomcat 类加载机制 先整体看下 Tomcat 类加载器:可以看到,在原来的 JVM 的类加载机制上面, Tomcat 新增了几个类加载器,包括 3 个基础类加载器和每个 Web 应用的类加载器。
3 个基础类加载器在 conf/catalina.properties 中进行配置:common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"server.loader=shared.loader=
Common: 以应用类加载器为父类,是 Tomcat 顶层的公用类加载器,其路径由 conf/catalina.properties 中的 common.loader 指定,默认指向 ${catalina.home}/lib 下的包。
Catalina: 以 Common 类加载器为父类,是用于加载 Tomcat 应用服务器的类加载器,其路径由 server.loader 指定,默认为空,此时 Tomcat 使用 Common 类加载器加载应用服务器。 Shared: 以 Common 类加载器为父类,是所有 Web 应用的父类加载器,其路径由 shared.loader 指定,默认为空,此时 Tomcat 使用 Common 类加载器作为 Web 应用的父加载器。 Web 应用: 以 Shared 类加载器为父类,加载 /WEB-INF/classes 目录下的未压缩的 Class 和资源文件以及 /WEB-INF/lib 目录下的 jar 包,该类加载器只对当前 Web 应用可见,对其他 Web 应用均不可见。先从 BootStrap 的 main 方法看起:
public static void main(String args[]) { synchronized (daemonLock) { if (daemon == null) { // Don't set daemon until init() has completed Bootstrap bootstrap = new Bootstrap(); try { bootstrap.init(); } catch (Throwable t) { handleThrowable(t); t.printStackTrace(); return; } daemon = bootstrap; } else { // When running as a service the call to stop will be on a new // thread so make sure the correct class loader is used to // prevent a range of class not found exceptions. Thread.currentThread().setContextClassLoader(daemon.catalinaLoader); } // 省略其余代码... }}
可以看到这里先判断了 bootstrap 是否为 null ,如果不为 null 直接把 Catalina ClassLoader 设置到了当前线程,如果为 null 下面是走到了 init() 方法。
public void init() throws Exception { // 初始化类加载器 initClassLoaders(); // 设置线程类加载器,将容器的加载器传入 Thread.currentThread().setContextClassLoader(catalinaLoader); // 设置区安全类加载器 SecurityClassLoad.securityClassLoad(catalinaLoader); // 省略其余代码...}
复制代码
接着这里看到了会调用 initClassLoaders() 方法进行类加载器的初始化,初始化完成后,同样会设置 Catalina ClassLoader 到当前线程。private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if (commonLoader == null) { // no config file, default to this loader - we might be in a 'single' env. commonLoader = this.getClass().getClassLoader(); } catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { handleThrowable(t); log.error("Class loader creation threw exception", t); System.exit(1); }}
看到这里应该就清楚了,会创建三个 ClassLoader : CommClassLoader , Catalina ClassLoader , SharedClassLoader ,正好对应前面介绍的三个基础类加载器。
接着进入 createClassLoader() 查看代码:private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent; value = replace(value); Listrepositories = new ArrayList<>(); String[] repositoryPaths = getPaths(value); for (String repository : repositoryPaths) { // Check for a JAR URL repository try { @SuppressWarnings("unused") URL url = new URL(repository); repositories.add(new Repository(repository, RepositoryType.URL)); continue; } catch (MalformedURLException e) { // Ignore } // Local repository if (repository.endsWith("*.jar")) { repository = repository.substring (0, repository.length() - "*.jar".length()); repositories.add(new Repository(repository, RepositoryType.GLOB)); } else if (repository.endsWith(".jar")) { repositories.add(new Repository(repository, RepositoryType.JAR)); } else { repositories.add(new Repository(repository, RepositoryType.DIR)); } } return ClassLoaderFactory.createClassLoader(repositories, parent);}
可以看到,这里加载的资源正好是我们刚才看到的配置文件 conf/catalina.properties 中的 common.loader , server.loader 和 shared.loader 。
4.2 ClassLoader 加载过程 直接打开 ParallelWebappClassLoader ,至于为啥不是看 WebappClassLoader ,从名字上就知道 ParallelWebappClassLoader 是一个并行的 WebappClassLoader 。 然后看下 ParallelWebappClassLoader 的 loadclass 方法是在它的父类 WebappClassLoaderBase 中实现的。 4.2.1 第一步:public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { if (log.isDebugEnabled()) log.debug("loadClass(" + name + ", " + resolve + ")"); Class clazz = null; // Log access to stopped class loader checkStateForClassLoading(name); // (0) Check our previously loaded local class cache clazz = findLoadedClass0(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return clazz; } // 省略其余...
首先调用 findLoaderClass0() 方法检查 WebappClassLoader 中是否加载过此类。
protected Class findLoadedClass0(String name) { String path = binaryNameToPath(name, true); ResourceEntry entry = resourceEntries.get(path); if (entry != null) { return entry.loadedClass; } return null;}
WebappClassLoader 加载过的类都存放在 resourceEntries 缓存中。
protected final Map<String, ResourceEntry> resourceEntries = new ConcurrentHashMap<>(); 复制代码 4.2.2 第二步:// 省略其余...clazz = findLoadedClass(name);if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Returning class from cache"); if (resolve) resolveClass(clazz); return clazz;}// 省略其余...
如果第一步没有找到,则继续检查 JVM 虚拟机中是否加载过该类。调用 ClassLoader 的 findLoadedClass() 方法检查。
4.2.3 第三步:ClassLoader javaseLoader = getJavaseClassLoader();boolean tryLoadingFromJavaseLoader;try { URL url; if (securityManager != null) { PrivilegedActiondp = new PrivilegedJavaseGetResource(resourceName); url = AccessController.doPrivileged(dp); } else { url = javaseLoader.getResource(resourceName); } tryLoadingFromJavaseLoader = (url != null);} catch (Throwable t) { ExceptionUtils.handleThrowable(t); tryLoadingFromJavaseLoader = true;}if (tryLoadingFromJavaseLoader) { try { clazz = javaseLoader.loadClass(name); if (clazz != null) { if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore }}
如果前两步都没有找到,则使用系统类加载该类(也就是当前 JVM 的 ClassPath )。为了防止覆盖基础类实现,这里会判断 class 是不是 JVMSE 中的基础类库中类。
4.2.4 第四步:boolean delegateLoad = delegate || filter(name, true);// (1) Delegate to our parent if requestedif (delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader1 " + parent); try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore }}
先判断是否设置了 delegate 属性,设置为 true ,那么就会完全按照 JVM 的"双亲委托"机制流程加载类。
若是默认的话,是先使用 WebappClassLoader 自己处理加载类的。当然,若是委托了,使用双亲委托亦没有加载到 class 实例,那还是最后使用 WebappClassLoader 加载。 4.2.5 第五步:if (log.isDebugEnabled()) log.debug(" Searching local repositories"); try { clazz = findClass(name); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from local repository"); if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore }
若是没有委托,则默认会首次使用 WebappClassLoader 来加载类。通过自定义 findClass() 定义处理类加载规则。
findClass() 会去 Web-INF/classes 目录下查找类。 4.2.6 第六步:if (!delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader at end: " + parent); try { clazz = Class.forName(name, false, parent); if (clazz != null) { if (log.isDebugEnabled()) log.debug(" Loading class from parent"); if (resolve) resolveClass(clazz); return clazz; } } catch (ClassNotFoundException e) { // Ignore }}
若是 WebappClassLoader 在 /WEB-INF/classes 、 /WEB-INF/lib 下还是查找不到 class ,那么无条件强制委托给 System 、 Common 类加载器去查找该类。
4.2.7 小结 Web 应用类加载器默认的加载顺序是:先从缓存中加载;
如果没有,则从 JVM 的 Bootstrap 类加载器加载; 如果没有,则从当前类加载器加载(按照 WEB-INF/classes 、 WEB-INF/lib 的顺序); 如果没有,则从父类加载器加载,由于父类加载器采用默认的委派模式,所以加载顺序是 AppClassLoader 、 Common 、 Shared 。转载地址:http://vebws.baihongyu.com/