java类加载器-Tomcat类加载器
在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式。接下来就以tomcat6为例看看tomat是如何使用自定制类加载器的。(本介绍是基于tomcat6.0.41,不同版本可能存在差异!)
网上所描述的tomcat类加载器
在网上搜一下“tomcat类加载器”会发现有大量的文章,在此我偷个懒,^_^把网上对tomcat类加载器的描述重说一下吧。
- CommonClassLoader:加载的类目录通过{tomcat}/conf/catalina.properties中的common.loader指定,以SystemClassLoader为parent(目前默认定义是common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar)
- CatalinaClassLoader :加载的类目录通过{tomcat}/conf/catalina.properties中server.loader指定,以CommonClassLoader为parent,如果server.loader配置为空,则ServerClassLoader 与CommonClassLoader是同一个(默认server.loader配置为空)
- SharedClassLoader:加载的类目录通过{tomcat}/conf/catalina.properties中share.loader指定,以CommonClassLoader为parent,如果server.loader配置为空,则CatalinaClassLoader 与CommonClassLoader是同一个(默认share.loader配置为空)
- WebappClassLoader:每个Context一个WebappClassLoader实例,负责加载context的/WEB-INF/lib和/WEB-INF/classes目录,context间的隔离就是通过不同的WebappClassLoader来做到的。由于类定义一旦加载就不可改变,因此要实现tomcat的context的reload功能,实际上是通过新建一个新的WebappClassLoader来做的,因此reload的做法实际上代价是很高昂的,需要注意的是,JVM内存的Perm区是只吃不拉的,因此抛弃掉的WebappClassLoader加载的类并不会被JVM释放,因此tomcat的reload功能如果应用定义的类比较多的话,reload几次就OutOfPermSpace异常了。
- JasperLoader:每个JSP一个JasperLoader实例,与WebappClassLoader做法类似,JSP支持修改生效是通过丢弃旧的JasperLoader,建一个新的JasperLoader来做到的,同样的,存在轻微的PermSpace的内存泄露的情况
以上对个个classloader的作用做了介绍,但请读者不要搞混淆了,上边说的个个类加载器只是类加载器的名字,不是类加载类的名字。上边的图是看到网上资料的说明绘制的,但是与实际源码中的结构还是差异挺大的。(没有研究是不是因为tomcat的版本所致)。下面就详细介绍下tomcat源码中类加载器的组织结构。
tomcat源码中类加载器的结构分析
首先要说明是tomcat默认配置下的情况。那接下来看看tomcat启动时的类初始化情况,这是BootStrap类的类初始化方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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) { log.error( "Class loader creation threw exception" , t); System.exit( 1 ); } } |
可以看到,在创建commonLoader时传的父类加载器是null。跟踪下去会发现commonLoader的父类加载器确实是null。有朋友可能想,tomcat在启动时肯定也要依赖jdk核心库,parent是null那怎么委托给parent去加载核心库的类了啊。这里大家不要忘了ClassLoader类的loadClass方法:
1 2 3 4 5 6 7 8 9 10 | try { if (parent != null ) { c = parent.loadClass(name, false ); } else { c = findBootstrapClassOrNull(name); //没有父类加载器时使用bootstrap类加载器 } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } |
通过以上initClassLoaders方法我们也能看到catalinaLoader和sharedLoader的父类加载器都是commonLoader,跟上边图的类加载器结构符合。但是commonLoader、catalinaLoader和sharedLoader的创建都是依赖tomcat安装目录下conf/catalina.properties的配置。默认情况配置是:
- common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
- server.loader=
- shared.loader=
由于server.loader和shared.loader的配置为空,所以其实commonLoader、catalinaLoader和sharedLoader都是指向同一个类加载器实例,看代码如下:(限于篇幅只贴部分代码)
1 2 3 4 5 6 | private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader" ); if ((value == null ) || (value.equals( "" ))) return parent; |
而他们指向那个类加载器类的实例呢?跟踪到最后我们发现如下代码:
1 2 3 4 5 6 | StandardClassLoader classLoader = null ; if (parent == null ) classLoader = new StandardClassLoader(array); else classLoader = new StandardClassLoader(array, parent); return (classLoader); |
就是StandardClassLoader的实例,下文再对StandardClassLoader进行源码讲解。
接下来再看看webappclassloader的创建过程,webappclassLoader是在WebappLoader类中的createClassLoader方法中通过反射实例化的。下边是源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass); //loaderClass="org.apache.catalina.loader.WebappClassLoader" WebappClassLoader classLoader = null ; if (parentClassLoader == null ) { parentClassLoader = container.getParentClassLoader(); } Class[] argTypes = { ClassLoader. class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); return classLoader; } |
可以看到他的parent是通过调用container.getParentlassLoader()获得的(如果对tomcat的结构不熟悉,请看这篇文章)跟踪到最后我们发现它调用了ContainerBase的这个方法:
1 2 3 4 5 6 7 8 9 | public ClassLoader getParentClassLoader() { if (parentClassLoader != null ) return (parentClassLoader); if (parent != null ) { return (parent.getParentClassLoader()); } return (ClassLoader.getSystemClassLoader()); } |
通过默认配置下debug可以知道最后是返回的systemclassloader,也就是说WebappClassLoader的父类加载器是systemclassloader也就是上篇文章说的App ClassLoader。
(由于JasperLoader本人还没有做分析,先不进行讲解了)
tomcat类加载器的实现方式分析
上文说到了commonLoader、catalinaLoader和sharedLoader都是指向StandardClassLoader的实例,来先看一看StandardClassLoader的源码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 | public class StandardClassLoader extends URLClassLoader implements StandardClassLoaderMBean { public StandardClassLoader(URL repositories[]) { super (repositories); } public StandardClassLoader(URL repositories[], ClassLoader parent) { super (repositories, parent); } } |
有没有感到你的意外啊,对的就是这么简单,这跟我上篇文章说的最简单的实现方式一样。(上篇文章做了解读,这里不再做说明了)
我们再来看看webappclassLoader,他的实现类就是org.apache.catalina.loader.WebappClassLoader,此类加载器也是继承自URLClassLoader,但是它覆盖了loadClass方法和findClass方法。这个类有三千多行这里就不将代码全部贴出来了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { if (log.isDebugEnabled()) log.debug( "loadClass(" + name + ", " + resolve + ")" ); Class clazz = null ; // Log access to stopped classloader if (!started) { try { throw new IllegalStateException(); } catch (IllegalStateException e) { log.info(sm.getString( "webappClassLoader.stopped" , name), e); } } // (0) 检查WebappClassLoader之前是否已经load过这个资源 clazz = findLoadedClass0(name); if (clazz != null ) { if (log.isDebugEnabled()) log.debug( " Returning class from cache" ); if (resolve) resolveClass(clazz); return (clazz); } // (0.1) 检查ClassLoader之前是否已经load过 clazz = findLoadedClass(name); if (clazz != null ) { if (log.isDebugEnabled()) log.debug( " Returning class from cache" ); if (resolve) resolveClass(clazz); return (clazz); } // (0.2) 先检查系统ClassLoader,因此WEB-INF/lib和WEB-INF/classes或{tomcat}/libs下的类定义不能覆盖JVM 底层能够查找到的定义(譬如不能通过定义java.lang.Integer替代底层的实现 try { clazz = system.loadClass(name); if (clazz != null ) { if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { // Ignore } // (0.5) Permission to access this class when using a SecurityManager if (securityManager != null ) { int i = name.lastIndexOf( '.' ); if (i >= 0 ) { try { securityManager.checkPackageAccess(name.substring( 0 ,i)); } catch (SecurityException se) { String error = "Security Violation, attempt to use " + "Restricted Class: " + name; log.info(error, se); throw new ClassNotFoundException(error, se); } } } //这是一个很奇怪的定义,JVM的类加载机制建议先由parent去load,load不到自己再去load(见上篇文章),而Servelet规范的建议则恰好相反,Tomcat的实现则做个折中,由用户去决定(context的 delegate定义),默认使用Servlet规范的建议,即delegate=false boolean delegateLoad = delegate || filter(name); // (1) 先由parent去尝试加载,如上说明,除非设置了delegate,否则这里不执行 if (delegateLoad) { if (log.isDebugEnabled()) log.debug( " Delegating to parent classloader1 " + parent); ClassLoader loader = parent; if (loader == null ) loader = system; try { clazz = loader.loadClass(name); if (clazz != null ) { if (log.isDebugEnabled()) log.debug( " Loading class from parent" ); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } // (2) 到WEB-INF/lib和WEB-INF/classes目录去搜索,细节部分可以再看一下findClass,会发现默认是先搜索WEB-INF/classes后搜索WEB-INF/lib 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) { ; } // (3) 由parent再去尝试加载一下 if (!delegateLoad) { if (log.isDebugEnabled()) log.debug( " Delegating to parent classloader at end: " + parent); ClassLoader loader = parent; if (loader == null ) loader = system; try { clazz = loader.loadClass(name); if (clazz != null ) { if (log.isDebugEnabled()) log.debug( " Loading class from parent" ); if (resolve) resolveClass(clazz); return (clazz); } } catch (ClassNotFoundException e) { ; } } throw new ClassNotFoundException(name); } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?