Tomcat类加载器机制
Tomcat为什么需要定制自己的ClassLoader:
1、定制特定的规则:隔离webapp,安全考虑,reload热插拔
2、缓存类
3、事先加载
要说Tomcat的Classloader机制,我们还得从Bootstrap开始。在BootStrap初始化的时候,调用了org.apache.catalina.startup.Bootstrap#initClassLoaders
方法,这个方法里面创建了3个ClassLoader,它们分别是commonLoader,catalinaLoader,sharedLoader,其中catalinaLoader,sharedLoader的父亲加载器是commonLoader,initClassLoaders执行的过程中会执行createClassLoader,而createClassLoader是根据conf/catalina.properties文件中common.loader,server.loader,shared.loader的值来初始化,它的代码如下:
默认情况下,这3个ClassLoader是同一个实例变量。
private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {String value = CatalinaProperties.getProperty(name + ".loader");// 1 if ((value == null) || (value.equals("")))return parent;// 2value = replace(value);List<Repository> repositories = new ArrayList<Repository>();StringTokenizer tokenizer = new StringTokenizer(value, ",");while (tokenizer.hasMoreElements()) {String repository = tokenizer.nextToken().trim();if (repository.length() == 0) {continue;}// Check for a JAR URL repositorytry {@SuppressWarnings("unused")URL url = new URL(repository);repositories.add(new Repository(repository, RepositoryType.URL));continue;} catch (MalformedURLException e) {// Ignore }// Local repositoryif (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));}}// 3ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);//这里返回的是一个UrlClassLoader实例return classLoader;}
以上代码删除了与本篇无关的代码,下面我们分别来分析一下标注的地方:
- 标注1的代码(第5行)判断如果catalina.properties中没有配置对应的loader属性的话,直接返回父加载器,而默认情况下,server.loader,shared.loader为空,那么此时的catalinaLoader,sharedLoader其实是同一个ClassLoader.
- 标注2(第9行)的地方根据环境变量的配置替换字符串中的值.默认情况下,common.loader的值为common.loader=${catalina.base}/lib,${catalina.base}/lib/.jar,${catalina.home}/lib,${catalina.home}/lib/.jar,这里会将catalina.base和catalina.home用环境变量的值替换。
- 标注3(第46行)的代码最终调用
org.apache.catalina.startup.ClassLoaderFactory#createClassLoader
静态工厂方法创建了URLClassloader的实例,而具体的URL其实就是*.loader属性配置的内容,此外如果parent为null的话,则直接用系统类加载器。
上面分析了Tomcat在启动的时候,初始化的几个ClassLoader,接下来我们再来继续看看,这些ClassLoader具体都用在什么地方。
我们接着来看org.apache.catalina.startup.Bootstrap#init方法,在初始化完3个classLoader以后,接下来首先通过catalinaLoader加载了org.apache.catalina.startup.Catalina
l类,然后通过放射调用了org.apache.catalina.startup.Catalina#setParentClassLoader,具体代码片段如下:
Class<?> startupClass =catalinaLoader.loadClass("org.apache.catalina.startup.Catalina"); Object startupInstance = startupClass.newInstance();String methodName = "setParentClassLoader"; Class<?> paramTypes[] = new Class[1]; paramTypes[0] = Class.forName("java.lang.ClassLoader"); Object paramValues[] = new Object[1]; paramValues[0] = sharedLoader; Method method =startupInstance.getClass().getMethod(methodName, paramTypes); method.invoke(startupInstance, paramValues);
通过上面的代码,我们可以清楚的看到调用了Catalina的setParentClassLoader放,那么到这里我们可能又要想知道,设置了parentClassLoader以后,sharedLoader又是在哪里使用的呢?这就需要我们接着来分析容器启动的代码。我们通过查看org.apache.catalina.startup.Catalina#getParentClassLoader
调用栈,我们看到在StandardContext的startInternal方法中调用了它,那么我们查看一下它的代码,包含了如下代码片段:
if (getLoader() == null) {WebappLoader webappLoader = new WebappLoader(getParentClassLoader());webappLoader.setDelegate(getDelegate());setLoader(webappLoader); } try {if (ok) {// Start our subordinate components, if anyif ((loader != null) && (loader instanceof Lifecycle))((Lifecycle) loader).start();//other code } catch(Exception e){ }
通过查看上面的代码,我们看到在StandardContext启动的时候,会创建webapploader,创建webapploader的时候会将getParentClassLoader方法返回的结果(这里返回的其实就是sharedLoader)赋值给自己的parentClassLoader变量,接着又会调用到Webapploader的start方法,因为WebappLoader符合Tomcat组件生命周期管理的模板方法模式,因此会调用到它的startInternal方法。我们接下来就来看看WebappLoader的startInternal,我们摘取一部分与本篇相关的代码片段如下:
classLoader = createClassLoader(); classLoader.setResources(container.getResources()); classLoader.setDelegate(this.delegate); classLoader.setSearchExternalFirst(searchExternalFirst);
从上的代码可以看到调用了createClassLoader方法创建一个classLoader,那么我们再看来看看createClassLoader的代码:
private WebappClassLoader createClassLoader()throws Exception {Class<?> clazz = Class.forName(loaderClass);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;}
在上面的代码里面,loaderClass是WebappLoader的实例变量,其值为org.apache.catalina.loader.WebappClassLoader
,那么上面的代码其实就是通过反射调用了WebappClassLoader的构造函数,然后传递了sharedLoader作为其父亲加载器。
代码阅读到这里,我们已经基本清楚了Tomcat中ClassLoader的总体结构,总结如下: 在Tomcat存在common,cataina,shared三个公共的classloader,默认情况下,这三个classloader其实是同一个,都是common classloader,而针对每个webapp,也就是context(对应代码中的StandardContext类),都有自己的WebappClassLoader来加载每个应用自己的类。上面的描述,我们可以通过下图形象化的描述:
清楚了Tomcat总体的ClassLoader结构以后,咋们就来进一步来分析一下WebAppClassLoader的代码,我们知道Java的ClassLoader机制有parent-first的机制,而这种机制是在loadClass方法保证的,一般情况下,我们只需要重写findClass方法就好了,而对于WebAppClassLoader,通过查看源代码,我们发现loadClass和findClass方法都进行了重写,那么我们首先就来看看它的loadClass方法,它的代码如下:
1 public synchronized Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException { 3 4 if (log.isDebugEnabled()) 5 log.debug("loadClass(" + name + ", " + resolve + ")"); 6 Class<?> clazz = null; 7 8 // Log access to stopped classloader 9 if (!started) { 10 try { 11 throw new IllegalStateException(); 12 } catch (IllegalStateException e) { 13 log.info(sm.getString("webappClassLoader.stopped", name), e); 14 } 15 } 16 17 // (0) Check our previously loaded local class cache 18 // 1 19 clazz = findLoadedClass0(name); 20 if (clazz != null) { 21 if (log.isDebugEnabled()) 22 log.debug(" Returning class from cache"); 23 if (resolve) 24 resolveClass(clazz); 25 return (clazz); 26 } 27 28 // (0.1) Check our previously loaded class cache 29 // 2 30 clazz = findLoadedClass(name); 31 if (clazz != null) { 32 if (log.isDebugEnabled()) 33 log.debug(" Returning class from cache"); 34 if (resolve) 35 resolveClass(clazz); 36 return (clazz); 37 } 38 39 // (0.2) Try loading the class with the system class loader, to prevent 40 // the webapp from overriding J2SE classes 41 // 3 42 try { 43 clazz = system.loadClass(name); 44 if (clazz != null) { 45 if (resolve) 46 resolveClass(clazz); 47 return (clazz); 48 } 49 } catch (ClassNotFoundException e) { 50 // Ignore 51 } 52 53 // (0.5) Permission to access this class when using a SecurityManager 54 if (securityManager != null) { 55 int i = name.lastIndexOf('.'); 56 if (i >= 0) { 57 try { 58 securityManager.checkPackageAccess(name.substring(0,i)); 59 } catch (SecurityException se) { 60 String error = "Security Violation, attempt to use " + 61 "Restricted Class: " + name; 62 log.info(error, se); 63 throw new ClassNotFoundException(error, se); 64 } 65 } 66 } 67 68 //4 69 boolean delegateLoad = delegate || filter(name); 70 71 // (1) Delegate to our parent if requested 72 // 5 73 if (delegateLoad) { 74 if (log.isDebugEnabled()) 75 log.debug(" Delegating to parent classloader1 " + parent); 76 ClassLoader loader = parent; 77 if (loader == null) 78 loader = system; 79 try { 80 clazz = Class.forName(name, false, loader); 81 if (clazz != null) { 82 if (log.isDebugEnabled()) 83 log.debug(" Loading class from parent"); 84 if (resolve) 85 resolveClass(clazz); 86 return (clazz); 87 } 88 } catch (ClassNotFoundException e) { 89 // Ignore 90 } 91 } 92 93 // (2) Search local repositories 94 if (log.isDebugEnabled()) 95 log.debug(" Searching local repositories"); 96 // 6 97 try { 98 clazz = findClass(name); 99 if (clazz != null) { 100 if (log.isDebugEnabled()) 101 log.debug(" Loading class from local repository"); 102 if (resolve) 103 resolveClass(clazz); 104 return (clazz); 105 } 106 } catch (ClassNotFoundException e) { 107 // Ignore 108 } 109 110 // (3) Delegate to parent unconditionally 111 // 7 112 if (!delegateLoad) { 113 if (log.isDebugEnabled()) 114 log.debug(" Delegating to parent classloader at end: " + parent); 115 ClassLoader loader = parent; 116 if (loader == null) 117 loader = system; 118 try { 119 clazz = Class.forName(name, false, loader); 120 if (clazz != null) { 121 if (log.isDebugEnabled()) 122 log.debug(" Loading class from parent"); 123 if (resolve) 124 resolveClass(clazz); 125 return (clazz); 126 } 127 } catch (ClassNotFoundException e) { 128 // Ignore 129 } 130 } 131 132 throw new ClassNotFoundException(name); 133 134 }
我们一步步的来分析一下上面的代码做了什么事情。
- 标注1(第18行)代码,首先从当前ClassLoader的本地缓存中加载类,如果找到则返回。
- 标注2(第29行)代码,在本地缓存没有的情况下,调用ClassLoader的findLoadedClass方法查看jvm是否已经加载过此类,如果已经加载则直接返回。
- 标注3(第41行)代码,通过系统的来加载器加载此类,这里防止应用写的类覆盖了J2SE的类,这句代码非常关键,如果不写的话,就会造成你自己写的类有可能会把J2SE的类给替换调,另外假如你写了一个javax.servlet.Servlet类,放在当前应用的WEB-INF/class中,如果没有此句代码的保证,那么你自己写的类就会替换到Tomcat容器Lib中包含的类。
- 标注4(第68行)代码,判断是否需要委托给父类加载器进行加载,delegate属性默认为false,那么delegatedLoad的值就取决于filter的返回值了,filter方法中根据包名来判断是否需要进行委托加载,默认情况下会返回false.因此delegatedLoad为false
- 标注5(第72行)代码,因为delegatedLoad为false,那么此时不会委托父加载器去加载,这里其实是没有遵循parent-first的加载机制。
- 标注6(第96行)调用findClass方法在webapp级别进行加载
- 标注7(第111行)如果还是没有加载到类,并且不采用委托机制的话,则通过父类加载器去加载。
通过上面的描述,我们可以知道Tomcat在加载webapp级别的类的时候,默认是不遵守parent-first的,这样做的好处是更好的实现了应用的隔离,但是坏处就是加大了内存浪费,同样的类库要在不同的app中都要加载一份。
上面分析完了loadClass,我们接着在来分析一下findClass,通过分析findClass的代码,最终会调用org.apache.catalina.loader.WebappClassLoader#findClassInternal
方法,那我们就来分析一下它的代码:
1 protected Class<?> findClassInternal(String name) 2 throws ClassNotFoundException { 3 4 // 5 if (!validate(name)) 6 throw new ClassNotFoundException(name); 7 8 String tempPath = name.replace('.', '/'); 9 String classPath = tempPath + ".class"; 10 11 ResourceEntry entry = null; 12 13 if (securityManager != null) { 14 PrivilegedAction<ResourceEntry> dp = 15 new PrivilegedFindResourceByName(name, classPath); 16 entry = AccessController.doPrivileged(dp); 17 } else { 18 // 1 19 entry = findResourceInternal(name, classPath); 20 } 21 22 if (entry == null) 23 throw new ClassNotFoundException(name); 24 25 Class<?> clazz = entry.loadedClass; 26 if (clazz != null) 27 return clazz; 28 29 synchronized (this) { 30 clazz = entry.loadedClass; 31 if (clazz != null) 32 return clazz; 33 34 if (entry.binaryContent == null) 35 throw new ClassNotFoundException(name); 36 37 try { 38 // 2 39 clazz = defineClass(name, entry.binaryContent, 0, 40 entry.binaryContent.length, 41 new CodeSource(entry.codeBase, entry.certificates)); 42 } catch (UnsupportedClassVersionError ucve) { 43 throw new UnsupportedClassVersionError( 44 ucve.getLocalizedMessage() + " " + 45 sm.getString("webappClassLoader.wrongVersion", 46 name)); 47 } 48 entry.loadedClass = clazz; 49 entry.binaryContent = null; 50 entry.source = null; 51 entry.codeBase = null; 52 entry.manifest = null; 53 entry.certificates = null; 54 } 55 56 return clazz; 57 58 }
上面的代码标注1(第19行)的地方通过名称去当前webappClassLoader的仓库中查找对应的类文件,标注2(第38行)的代码,将找到的类文件通过defineClass转变为Jvm可以识别的Class对象返回。
Reference
Tomcat类加载器机制(Tomcat源代码阅读系列之六)
Tomcat学习之ClassLoader
tomcat的classloader机制
Tomcat类加载器机制相关推荐
- Tomcat源码解析六:Tomcat类加载器机制
要说Tomcat的Classloader机制,我们还得从Bootstrap开始.在BootStrap初始化的时候,调用了org.apache.catalina.startup.Bootstrap#in ...
- 类加载机制:双亲委任模型和tomcat类加载器
简介 类是如何加载的,那么必须要面对的几个问题如下 什么是类加载机制? 什么是双亲委任模型? 如何破坏双亲委任模型? Tomcat 的类加载器是怎么设计的? 类加载机制 Java 中的类加载器大致可以 ...
- JVM类加载理解(线程上下文类加载器、Tomcat类加载器)
类加载机制概念 Java虚拟机把描述类的class文件加载到内存,对其进行校验.转换解析.初始化等操作,最终得到可以被虚拟机直接使用的java类型,这就是虚拟机的加载机制. 主要有五个步骤: 加载 将 ...
- Tomcat类加载器为何违背双亲委派模型
本文来说下Tomcat类加载器为何违背双亲委派模型 文章目录 什么是类加载机制 什么是双亲委派模型 如何破坏双亲委任模型 Tomcat的类加载器是怎么设计的 本文小结 什么是类加载机制 代码编译的结果 ...
- 接口多个实现类加载哪个_深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现
本文转自互联网,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutori ...
- OSGi 规范和框架 OSGi框架类加载机制 Java默认类加载器机制和OSGI类加载器机制比较
一.OSGi 规范 OSGi(Open Service Gateway Initiative) 技术是 Java 动态化模块化系统的一系列规范.OSGi 一方面指维护 OSGi 规范的 OSGi Al ...
- 《Java虚拟机原理图解》5. JVM类加载器机制与类加载过程
参考网址:http://blog.csdn.net/luanlouis/article/details/50529868 0.前言 读完本文,你将了解到: 一.为什么说Jabalpur语言是跨平台的 ...
- 利用反射模拟Tomcat类加载器的toString方法
一直认为tomcat5的类加载器的toString方法很酷,为什么呢?因为它的toString方法列出了所有的父类加载器以及类加载器加载的资源(即classpath).可能有些朋友还没有注意过,如果想 ...
- java类加载器、双亲委派、沙箱安全机制全都让你整明白(三万字,收藏慢慢啃)
目录 一.概述 1.类加载的分类 2.类加载器的必要性 3.命名空间 4.类加载机制的基本特征 二.类加载器的分类 1.引导类加载器 2.扩展类加载器 3.系统类加载器 4.用户自定义类加载器 三.测 ...
最新文章
- 十个最常用的JVM 配置参数
- android stub.asinterface是什么意思
- Java泛型面试问题
- oracle 分页查询
- 【EOJ Monthly 2019.02 - D】进制转换(思维,取模,高精度大数)
- android微信支付服务端,Android 微信支付返回-1
- python-面向对象名词解析(类、实例、属性、方法、对象)
- HDU-6341 Problem J. Let Sudoku Rotate(dfs 剪枝)
- 利用监听器实现在线人数统计
- Andrew Ng 如何重拾梦想
- 抓取Js动态生成数据且以滚动页面方式分页的网页
- wafer map格式转换_如何将谷歌地球KML图层转换为Mapinfo TAB图层?
- 【seaborn】(五)联合分布图
- LLVM 学习(二) -LLVM IR 语法理论学习
- QQ VS 360 大战开始
- 空指针异常可能出现的原因
- padStart(),padEnd()方法
- 微信小程序超长页面生成截图的一种解决方案
- 网站软文推广类的文章怎么写?
- 使用360手机助手连接真机。图文教程
热门文章
- Unity应用架构设计(11)——一个网络层的构建
- Citrix Xendesktop中VDA注册DDC的流程
- 美容院会籍管理,看着简单,其实很复杂
- 手机+笔记本上NET网教程--5步让你轻松上网
- Tungsten Fabric SDN — SmartNIC vRouter 软件架构设计
- Linux Kernel TCP/IP Stack — L2 Layer — Linux VLAN device for 802.1.q(虚拟局域网)
- Python 笔试集:什么时候 i = i + 1 并不等于 i += 1?
- 快速理解 session/token/cookie 认证方式
- Keil错误Unable to automatically place AT section malloc.o(.ARM.__AT_0x20000000) with required base add
- 6.3 OpenSSH