在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式。接下来就以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. [原创]cocos2dx加载网络图片&异步加载图片

    [动机] 之前看到一款卡牌游戏,当你要看全屏高清卡牌的时候,游戏会单独从网络上下载,本地只存了非高清的,这样可以省点包大小,所以我萌生了实现一个读取网络图片的类. [联想] 之前浏览网页的时候经常看到 ...

  2. iOS开发-NSOperation与GCD区别

    Mac OS X 10.6及iOS4.0之后导入了可以使全体线程更高效运行,并且使并行处理应用更易开发的架构,GCD(Grand Central  Dispatch),同时引入的还有Run Loop, ...

  3. [leetcode 25]Reverse Nodes in k-Group

    1 题目: Given a linked list, reverse the nodes of a linked list k at a time and return its modified li ...

  4. java HashMap那点事

    集合类的整体架构 比较重要的集合类图如下:   有序否 允许元素重复否 Collection 否 是 List 是 是 Set AbstractSet 否 否 HashSet TreeSet 是(用二 ...

  5. SQL入门经典(九) 之自定义函数

    UDF和存储过程很类似,用户自定义函数是一组有序的T-SQL语句,这些语句被预先优化和编译,并且可以作为一个单元来测试调用.UDF和存储过程的主要区别在于结果返回方式,为了能支持更多返回值,UDF比存 ...

  6. WebStorm 11激活方法

    由于最新jetbrains发布了IntelliJ IDEA 15. PyCharm 5.PhpStorm10.WebStorm 11等各个版本,但是改变了注册方法.原先的注册码包括注册机都已经不能使用 ...

  7. 冲刺阶段 day 10

    项目进展 目前我们已经完成了系部管理,教师管理,班级管理,学生管理这四大部分代码的编写及数据库的搭建与连接.就差最后专业管理这一部分了. 存在问题 其实我们从开始这个项目到现在,最大的问题还是在代码编 ...

  8. Programming Entity Framework CodeFirst--数据库约定和配置

    这一章主要主要讲的是我们的模型如何映射到数据库,而不影响模型,以及不同的映射场景. 一.表名和列名 1.指定表名 [Table("PersonPhotos")] public cl ...

  9. windbg 基础命令实战 - 简单程序破解

    以前玩游戏遇到一些实在过不去的管卡,经常会找一些游戏修改软件来修改游戏,让自己变得无比强大,将boss一路砍瓜切菜过足游戏瘾.其实游戏修改软件的功能大多都比较简单,我们可以通过windbg的一些简单命 ...

  10. PyQt5应用与实践

    一个典型的GUI应用程序可以抽象为:主界面(菜单栏.工具栏.状态栏.内容区域),二级界面(模态.非模态),信息提示(Tooltip),程序图标等组成.本篇根据作者使用PyQt5编写的一个工具,介绍如何 ...