在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式。接下来就以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类的类初始化方法:

  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方法:

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都是指向同一个类加载器实例,看代码如下:(限于篇幅只贴部分代码)

 private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception { String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;

而他们指向那个类加载器类的实例呢?跟踪到最后我们发现如下代码:

 StandardClassLoader classLoader = null;
if (parent == null)
classLoader = new StandardClassLoader(array);
else
classLoader = new StandardClassLoader(array, parent);
return (classLoader);

就是StandardClassLoader的实例,下文再对StandardClassLoader进行源码讲解。

  接下来再看看webappclassloader的创建过程,webappclassLoader是在WebappLoader类中的createClassLoader方法中通过反射实例化的。下边是源代码:

    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的这个方法:

    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的源码实现:

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方法。这个类有三千多行这里就不将代码全部贴出来了。

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);
}

  

java类加载器-Tomcat类加载器的更多相关文章

  1. Tomcat类加载器体系结构

    <深入理解java虚拟机>——Tomcat类加载器体系结构 标签: java / 虚拟机 / tomcat Tomcat 等主流Web服务器为了实现下面的基本功能,都实现了不止一个自定义的 ...

  2. Tomcat系列(7)——Tomcat类加载机制

    1. 核心部分 1. 类加载器: 通过一个类的全限定名来获取描述此类的二进制字节流. 对于任意一个类,都需要由加载他的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一 ...

  3. Java类加载机制与Tomcat类加载器架构

    Java类加载机制 类加载器 虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类.实现这 ...

  4. Tomcat类加载器机制

    Tomcat为什么需要定制自己的ClassLoader: 1.定制特定的规则:隔离webapp,安全考虑,reload热插拔 2.缓存类 3.事先加载 要说Tomcat的Classloader机制,我 ...

  5. Tomcat类加载器

    1JVM类加载机制   JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构.其中引导类.扩展类.系统类三个加载器是JVM内置的. 它们的作用分别是: 1)引导类加载器:使 ...

  6. Tomcat类加载器破坏双亲委派

    转载:https://blog.csdn.net/qq_38182963/article/details/78660779 http://www.cnblogs.com/aspirant/p/8991 ...

  7. Tomcat源码分析 (五)----- Tomcat 类加载器

    在研究tomcat 类加载之前,我们复习一下或者说巩固一下java 默认的类加载器.楼主以前对类加载也是懵懵懂懂,借此机会,也好好复习一下. 楼主翻开了神书<深入理解Java虚拟机>第二版 ...

  8. Tomcat 类加载器的实现

    Tomcat 内部定义了多个 ClassLoader,以便应用和容器访问不同存储库中的类和资源,同时达到应用间类隔离的目的.本文首发于公众号:顿悟源码. 1. Java 类加载机制 类加载就是把编译生 ...

  9. 深入理解JVM虚拟机7:JNDI,OSGI,Tomcat类加载器实现

    打破双亲委派模型 JNDI JNDI 的理解   JNDI是 Java 命名与文件夹接口(Java Naming and Directory Interface),在J2EE规范中是重要的规范之中的一 ...

随机推荐

  1. blender 2.6 快捷键

    右键单击 选中 A 取消选择/全选 中键拖动 旋转 中键滚动放大/缩小 Shift+中键拖动 平移 右上斜线三角区域拖动 产生新视窗 Shift+c准星复位 Num1前视图 Ctrl+Num1后视图 ...

  2. activity启动模式区别和优化

    初学android的开发人员,可能会经常忽略这个重要的设置. Activity一共有以下四种launchMode:1.standard2.singleTop3.singleTask4.singleIn ...

  3. How and Where Concurrent Asynchronous I/O with ASP.NET Web API 对异步编程分析的非常的好

    How and Where Concurrent Asynchronous I/O with ASP.NET Web API http://www.tugberkugurlu.com/archive/ ...

  4. Nim教程【七】

    这是国内第一个关于Nim的系列教程 先说废话 很开心,在今天凌晨快一点多的时候拿到了 nim-lang.com:nim-lang.cn:nim-lang.net 这三个域名,到不是为了投资,准备用ni ...

  5. C#版的eval,C#Light开源嵌入式脚本,unity热更新不再愁

    目前最新版本AlphaV0.06 完全的c#语法,可用于一切能运行C#的场合,wp windows xamarin mono asp.net unity3d 内嵌了int uint bool stri ...

  6. ueditor上传大容量视频报http请求错误的解决方法

    故障现象: 当使用百度编辑器ueditor上传大容量视频或大容量图片的时候,编辑器报"http请求错误"的解决方法详解: 原因分析: 目前很多CMS整合了百度的ueditor编辑器 ...

  7. 谈谈c#中异步编程模型的变迁

    大家在编程过程中都会用到一些异步编程的情况.在c#的BCL中,很多api都提供了异步方法,初学者可能对各种不同异步方法的使用感到迷惑,本文主要为大家梳理一下异步方法的变迁以及如何使用异步方法. Beg ...

  8. 解决服务器每次都要输入Enter PEM pass phrase

    今天架设好Python的HTTPS云服务器, 发现每次连接都要Enter PEM pass phrase 把服务器端的key里面的key剥离掉就好了 openssl rsa -in server.ke ...

  9. 30分钟带你快速入门MySQL教程

    这是一篇真正适合初学者的MySQL数据库入门文章,哪怕你从来没有接触过数据库,或者说你从来没有听说过有数据库这东西,请一定要相信我,我当时就是这么过来的. 如果你刚开始接触MySQL数据库,或者你需要 ...

  10. 知方可补不足~sqlserver中触发器的使用

    回到目录 触发器在过去的10年中,即存储过程和ado.net称霸江湖期间是那么的重要,而现在,trigger显得不是那么必要的,我们很少将复杂的业务写在SQL里,当然也会没有机会写到trigger里了 ...