2012-09-04 22:19 8993人阅读 评论(4) 收藏 举报
 分类:
WEB服务器(13) 

版权声明:本文为博主原创文章,未经博主允许不得转载。

 

目录(?)[+]

 

类装载器

JDK中提供了3种不同的类加载器:启动类装载器,扩展类装载器和系统类装载器。引导类装载器,用于引导启动Java虚拟机,当执行一个JAVA程序时,就会启动引导类装载器,它是使用本地代码来实现的,会装载%JAVA_HOME%\\jre\lib\rt.jar,它是所有类装载器类的父装载器。扩展类装载器负责载入标准扩展目录中的类,其搜索路径是%JAVA_HOME%\jre\lib\ext,只需要将打包好的jar文件放入这个目录就可以了,给开发提供了很大的便利性。系统类装载器是默认的装载器,其搜索路径是classpath。

JVM到底使用的是哪一个类装载器,取决于类装载器的代理模式。每当需要装载一个类的时候,会首先调用系统类装载器,但是系统类装载器并不会立即装载,而是将其交给父装载器:扩展类装载器,扩展类装载器其将交给引导类装载器,引导类装载器没有父装载器,它会尝试装载这个类,如果找不到这个类,会交给扩展类装载器,如果扩展类装载器还是没有找到,会交给系统类装载器,如果系统类装载器还是没有找到这个类,则会抛出java.lang.ClassNotFoundException异常。代理模式主要是为了解决类装载的安全问题。例如:对于自定类的java.lang.Object类,永远得不到装载,除非,rt.jar中确实没有这个类。

tomcat也提供了几种不同的类装载器用于加载不同位置的jar包和class文件,特别是Context容器需要有一个单独的类装载器,因为不同应用可能有相同的类,如果用同一个类装载器去装载,就不知道该加载哪个应用里面的类了。这些类装载器之间的关系如下图所示:

系统类装载器

tomcat的系统类装载器和JDK的系统类装载器有点不同的地方是搜索路径并不相同,在catalina.bat中做了如下修改:

  1. rem Add on extra jar file to CLASSPATH
  2. rem Note that there are no quotes as we do not want to introduce random
  3. rem quotes into the CLASSPATH
  4. if "%CLASSPATH%" == "" goto emptyClasspath
  5. set "CLASSPATH=%CLASSPATH%;"
  6. :emptyClasspath
  7. set "CLASSPATH=%CLASSPATH%%CATALINA_HOME%\bin\bootstrap.jar"
  8. rem Add tomcat-juli.jar to classpath
  9. rem tomcat-juli.jar can be over-ridden per instance
  10. if not exist "%CATALINA_BASE%\bin\tomcat-juli.jar" goto juliClasspathHome
  11. set "CLASSPATH=%CLASSPATH%;%CATALINA_BASE%\bin\tomcat-juli.jar"

先将classpath清空,因为classpath中可能有tomcat启动相关的类会影响tomcat的正常启动。然后将bootstrap.jar和tomcat-juli.jar加入classpath中,在catalina.bat中调用了Bootstrap类的main方法,这里系统类装载器会装载Bootstrap类,Bootstrap类用到的Catalina类也是由系统类装载器装载的。

随后在Bootstrap的init方法中创建了3个类装载器:
  1. public void init() throws Exception{
  2. // Set Catalina path
  3. setCatalinaHome();
  4. setCatalinaBase();
  5. initClassLoaders();
  6. Thread.currentThread().setContextClassLoader(catalinaLoader);
  7. SecurityClassLoad.securityClassLoad(catalinaLoader);
  8. ...
  9. }
  10. private void initClassLoaders() {
  11. try {
  12. commonLoader = createClassLoader("common", null);
  13. if( commonLoader == null ) {
  14. // no config file, default to this loader - we might be in a 'single' env.
  15. commonLoader=this.getClass().getClassLoader();
  16. }
  17. catalinaLoader = createClassLoader("server", commonLoader);
  18. sharedLoader = createClassLoader("shared", commonLoader);
  19. } catch (Throwable t) {
  20. handleThrowable(t);
  21. log.error("Class loader creation threw exception", t);
  22. System.exit(1);
  23. }
  24. }

Common Class Loader

首先创建CommonLoader,是在createClassLoader方法中完成的,代码如下:
  1. private ClassLoader createClassLoader(String name, ClassLoader parent)throws Exception {
  2. String value = CatalinaProperties.getProperty(name + ".loader");
  3. if ((value == null) || (value.equals("")))
  4. return parent;
  5. value = replace(value);
  6. List<Repository> repositories = new ArrayList<Repository>();
  7. StringTokenizer tokenizer = new StringTokenizer(value, ",");
  8. while (tokenizer.hasMoreElements()) {
  9. String repository = tokenizer.nextToken().trim();
  10. if (repository.length() == 0) {
  11. continue;
  12. }
  13. // Check for a JAR URL repository
  14. try {
  15. @SuppressWarnings("unused")
  16. URL url = new URL(repository);
  17. repositories.add(new Repository(repository, RepositoryType.URL));
  18. continue;
  19. } catch (MalformedURLException e) {
  20. // Ignore
  21. }
  22. // Local repository
  23. if (repository.endsWith("*.jar")) {
  24. repository = repository.substring(0, repository.length() - "*.jar".length());
  25. repositories.add(new Repository(repository, RepositoryType.GLOB));
  26. } else if (repository.endsWith(".jar")) {
  27. repositories.add(new Repository(repository, RepositoryType.JAR));
  28. } else {
  29. repositories.add(new Repository(repository, RepositoryType.DIR));
  30. }
  31. }
  32. ClassLoader classLoader = ClassLoaderFactory.createClassLoader(repositories, parent);
  33. ...
  34. return classLoader;
  35. }

(1)、从bootstrap.jar包可以找到catalina.properties文件:common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar,很明显这个类装载器搜索路径就是${catalina.home}/lib和${catalina.home}/lib/*.jar,之所以叫common class loader,是因为它加载每个应用要用到的公共jar包和class文件;

(2)、遍历values,将每个value封装成Repository,然后根据repository和parent创建这个classLoader,在这个方法中parent传入的是null值,代表这个类装载器的父装载器是系统类装载器,实际上返回的是StandardClassLoader类,StandardClassLoader类是URLClassLoader的子类,即将被废弃。之所以返回的是StandardClassLoader是在ClassLoaderFactory的createClassLoader方法中被包装了一层。

Catalina Class Loader

common class loader创建好之后,又创建了catalinaLoader,其搜索路径为空,以下是catalina.properties的配置项:
  1. common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
  2. server.loader=
  3. shared.loader=

common class loader是以common class loader为父装载器的,因此其搜索路径和common class loader一样。catalina class loader创建好后,在init方法中随即调用了Thread.currentThread().setContextClassLoader(catalinaLoader);将其设置为当前线程的类装载器

Shared Class Loader

已不再使用,tomcat早期的版本在使用这个类装载器,负责装载应用中公用的类,后来这些公用的类被移到了{catalina.base}/lib目录下,这个装载器暂时未被使用
综上所述:tomcat在启动的时候初始化了三个类加载器,commonLoader,catalinaLoader,sharedLoader.其中commonLoader是另外两个的父装载器,且为standardClassLoader类型,tomcat真正使用的是commonLoader,engine,host,connector等都是使用commonLoader装载的。
 

Webapp Class Loader

这个类装载器是tomcat自定义的类装载器,先来看看类图:

tomcat的一个service除了容器和连接器外还有很多组件,比如sessionManager,logger,loader等,这个类装载器是以组件的形式附着在每个容器上的,Engine和Host的这两个容器的loader组件为null,context里面是有值的,看看context的startInternal方法:
  1. protected synchronized void startInternal() throws LifecycleException {
  2. ...
  3. if (getLoader() == null) {
  4. WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
  5. webappLoader.setDelegate(getDelegate());
  6. setLoader(webappLoader);
  7. }
  8. ...
  9. // Binding thread
  10. ClassLoader oldCCL = bindThread();
  11. try {
  12. if (ok) {
  13. // Start our subordinate components, if any
  14. if ((loader != null) && (loader instanceof Lifecycle))
  15. ((Lifecycle) loader).start();
  16. // since the loader just started, the webapp classloader is now
  17. // created.
  18. // By calling unbindThread and bindThread in a row, we setup the
  19. // current Thread CCL to be the webapp classloader
  20. unbindThread(oldCCL);
  21. oldCCL = bindThread();
  22. }
  23. ...
  24. } finally {
  25. // Unbinding thread
  26. unbindThread(oldCCL);
  27. }

很明显,context在启动的时候创建了一个loader组件,webapploader正是loader的实现类,这个类并不是最终的类装载器,在这个类里面有一个webappclassloader类型的字段叫classloader,这个classloader的创建是在loader组件的start方法中完成的

  1. protected void startInternal() throws LifecycleException {
  2. ...
  3. // Construct a class loader based on our current repositories list
  4. try {
  5. classLoader = createClassLoader();
  6. classLoader.setResources(container.getResources());
  7. classLoader.setDelegate(this.delegate);
  8. classLoader.setSearchExternalFirst(searchExternalFirst);
  9. if (container instanceof StandardContext) {
  10. classLoader.setAntiJARLocking(
  11. ((StandardContext) container).getAntiJARLocking());
  12. classLoader.setClearReferencesStatic(
  13. ((StandardContext) container).getClearReferencesStatic());
  14. classLoader.setClearReferencesStopThreads(
  15. ((StandardContext) container).getClearReferencesStopThreads());
  16. classLoader.setClearReferencesStopTimerThreads(
  17. ((StandardContext) container).getClearReferencesStopTimerThreads());
  18. classLoader.setClearReferencesHttpClientKeepAliveThread(
  19. ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
  20. }
  21. for (int i = 0; i < repositories.length; i++) {
  22. classLoader.addRepository(repositories[i]);
  23. }
  24. // Configure our repositories
  25. setRepositories();
  26. setClassPath();
  27. setPermissions();
  28. ((Lifecycle) classLoader).start();
  29. ...
  30. } catch (Throwable t) {
  31. ...
  32. }
  33. ...
  34. }

(1)、在createClassLoader方法中通过反射实例化了org.apache.catalina.loader.WebappClassLoader这个类,并调用了它的setParentClassLoader设置其父装载器为standardClassLoader

  1. private WebappClassLoader createClassLoader()
  2. throws Exception {
  3. Class<?> clazz = Class.forName(loaderClass);
  4. WebappClassLoader classLoader = null;
  5. if (parentClassLoader == null) {
  6. parentClassLoader = container.getParentClassLoader();
  7. }
  8. Class<?>[] argTypes = { ClassLoader.class };
  9. Object[] args = { parentClassLoader };
  10. Constructor<?> constr = clazz.getConstructor(argTypes);
  11. classLoader = (WebappClassLoader) constr.newInstance(args);
  12. return classLoader;
  13. }

(2)、为webappclassloader添加仓库(仓库表示类装载器会在哪些路径搜索类)

将/WEB-INF/classes目录添加到仓库中,然后将/WEB-INF/lib目录下的jar包也添加到仓库中
  1. private void setRepositories() throws IOException {
  2. ...
  3. // Setting up the class repository (/WEB-INF/classes), if it exists
  4. String classesPath = "/WEB-INF/classes";
  5. ...
  6. // Adding the repository to the class loader
  7. classLoader.addRepository(classesPath + "/", classRepository);
  8. // Setting up the JAR repository (/WEB-INF/lib), if it exists
  9. String libPath = "/WEB-INF/lib";
  10. ...
  11. // Looking up directory /WEB-INF/lib in the context
  12. NamingEnumeration<NameClassPair> enumeration = libDir.list("");
  13. while (enumeration.hasMoreElements()) {
  14. NameClassPair ncPair = enumeration.nextElement();
  15. String filename = libPath + "/" + ncPair.getName();
  16. if (!filename.endsWith(".jar"))
  17. continue;
  18. ...
  19. try {
  20. JarFile jarFile = new JarFile(destFile);
  21. classLoader.addJar(filename, jarFile, destFile);
  22. } catch (Exception ex) {
  23. ...
  24. }
  25. ...
  26. }
  27. }

(3)、为类装载器设置权限,这里Globals.IS_SECURITY_ENABLED值为false,表示安全机制未打开,直接返回

(4)、启动这个loader
Webappclassloader设计的过程中考虑了优化和安全两方面。例如,它会缓存之前已经载入的类以提高性能。此外,它还会缓存失败的类的名字,下次再次请求加载相同的类时直接抛出ClassNotFoundException异常。考虑到安全性,不允许载入指定的某些类,这些类在triggers数组中,目前有两个类:
  1. protected static final String[] triggers = {
  2. "javax.servlet.Servlet", "javax.el.Expression"       // Servlet API
  3. };

WebappClassLoader装载类

loadClass是在其loadClass方法中完成的,下面详细分析这个方法:
  1. public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  2. ...
  3. // 先检查本地缓存
  4. clazz = findLoadedClass0(name);
  5. if (clazz != null) {
  6. if (log.isDebugEnabled())
  7. log.debug("  Returning class from cache");
  8. if (resolve)
  9. resolveClass(clazz);
  10. return (clazz);
  11. }
  12. // 如果本地缓存没有,则检查上一级缓存
  13. clazz = findLoadedClass(name);
  14. if (clazz != null) {
  15. if (log.isDebugEnabled())
  16. log.debug("  Returning class from cache");
  17. if (resolve)
  18. resolveClass(clazz);
  19. return (clazz);
  20. }
  21. // 如果两个缓存都没有,则使用系统的类装载器进行装载,防止Web应用程序中的类覆盖J2EE的类
  22. try {
  23. clazz = system.loadClass(name);
  24. if (clazz != null) {
  25. if (resolve)
  26. resolveClass(clazz);
  27. return (clazz);
  28. }
  29. } catch (ClassNotFoundException e) {
  30. // Ignore
  31. }
  32. // 如果启用了SecurityManager,则检查此类是否允许被载入,如果不允许,则抛出异常
  33. if (securityManager != null) {
  34. int i = name.lastIndexOf('.');
  35. if (i >= 0) {
  36. try {
  37. securityManager.checkPackageAccess(name.substring(0, i));
  38. } catch (SecurityException se) {
  39. String error = "Security Violation, attempt to use " + "Restricted Class: " + name;
  40. log.info(error, se);
  41. throw new ClassNotFoundException(error, se);
  42. }
  43. }
  44. }
  45. boolean delegateLoad = delegate || filter(name);
  46. // 若打开了delegateLoad标志位,调用父装载器来加载。如果父装载器为null,使用系统类装载器装载
  47. if (delegateLoad) {
  48. if (log.isDebugEnabled())
  49. log.debug("  Delegating to parent classloader1 " + parent);
  50. ClassLoader loader = parent;
  51. if (loader == null)
  52. loader = system;
  53. try {
  54. clazz = Class.forName(name, false, loader);
  55. if (clazz != null) {
  56. if (log.isDebugEnabled())
  57. log.debug("  Loading class from parent");
  58. if (resolve)
  59. resolveClass(clazz);
  60. return (clazz);
  61. }
  62. } catch (ClassNotFoundException e) {
  63. // Ignore
  64. }
  65. }
  66. // 从本地仓库中载入相关类
  67. if (log.isDebugEnabled())
  68. log.debug("  Searching local repositories");
  69. try {
  70. clazz = findClass(name);
  71. if (clazz != null) {
  72. if (log.isDebugEnabled())
  73. log.debug("  Loading class from local repository");
  74. if (resolve)
  75. resolveClass(clazz);
  76. return (clazz);
  77. }
  78. } catch (ClassNotFoundException e) {
  79. // Ignore
  80. }
  81. // 若当前仓库中没有需要的类,且delegateLoad标志位关闭,则使用父装载器。若父装载器为null,使用系统类装载器来装载
  82. if (!delegateLoad) {
  83. if (log.isDebugEnabled())
  84. log.debug("  Delegating to parent classloader at end: " + parent);
  85. ClassLoader loader = parent;
  86. if (loader == null)
  87. loader = system;
  88. try {
  89. clazz = Class.forName(name, false, loader);
  90. if (clazz != null) {
  91. if (log.isDebugEnabled())
  92. log.debug("  Loading class from parent");
  93. if (resolve)
  94. resolveClass(clazz);
  95. return (clazz);
  96. }
  97. } catch (ClassNotFoundException e) {
  98. // Ignore
  99. }
  100. }
  101. //仍未找到,抛出异常
  102. throw new ClassNotFoundException(name);
  103. }

整个思路是:先到缓存中获取,如果缓存中有直接返回,否则根据delegateLoad采取不同的加载方式。如果未启用这个标志:先本地仓库加载再父装载器或者系统类装载器装载;如果启用了这个标志:直接由父装载器或者系统类装载器装载。

类缓存

tomcat之所以采用自定义类装载器,除了不同应用之间有相同类不好解决之外,还有一个原因是可以缓存类以提高速度。每个由webappclassloader装载的类被视为资源,用ResourceEntry表示。加入缓存的代码是在loadclass方法中完成的,前面提到会搜索本地仓库,就是在这步调用了findClass方法完成了类的查找,并把找到的类封装成ResourceEntry,最后把这个resourceEntry放入resourceEntries中缓存起来。

Tomcat学习之ClassLoader的更多相关文章

  1. Tomcat学习—Tomcat的简介和目录以及配置文件介绍(Windows环境)

    tomcat学习(8) 版权声明:本文为博主原创文章,未经博主允许不得转载. 今天学习TOMCAT,主要学习的是Tomcat的目录结构,配置文件! 1:Tomcat简介 Tomcat 服务器是一个免费 ...

  2. Tomcat 学习进阶历程之Tomcat架构与核心类分析

    前面的http及socket两部分内容,主要是为了后面看Tomcat源代码而学习的一些网络基础.从这章開始.就開始实际深入到Tomcat的'内在'去看一看. 在分析Tomcat的源代码之前,准备先看一 ...

  3. Tomcat学习之Wrapper

    Tomcat学习之Wrapper 分类: WEB服务器2012-08-30 22:16 1547人阅读 评论(0) 收藏 举报 tomcatservletwrapperservletslistexce ...

  4. Tomcat学习 HttpConnector和HttpProcessor启动流程和线程交互

    一.tomat启动流程 1.启动HttpConnector connector等待连接请求,只负责接受socket请求,具体处理过程交给HttpProcessor处理. tomcat用户只能访问到co ...

  5. Tomcat 学习笔记二

    学习一 java.bean.PropertyChangeListener用来监听bean类的属性值改变.当改变时同时执行对应事件.而且是线程安全的.tomcat用此reload的Boolean值改变是 ...

  6. Tomcat ----> 学习笔记

    源码之几个常见类和接口的关系 在学习Servlet的时候经常见到以下几个合成单词和非合成单词:Servlet.GenericServlet.HttpServlet.它们之间有联系的.接下来我把它们的联 ...

  7. Tomcat学习总结(7)——Tomcat与Jetty比较

    Jetty 基本架构 Jetty目前的是一个比较被看好的 Servlet 引擎,它的架构比较简单,也是一个可扩展性和非常灵活的应用服务器. 它有一个基本数据模型,这个数据模型就是 Handler(处理 ...

  8. Tomcat学习总结(3)——Tomcat优化详细教程

    Tomcat是我们经常使用的 servlet容器之一,甚至很多线上产品都使用 Tomcat充当服务器.而且优化后的Tomcat性能提升显著,本文从以下几方面进行分析优化. 一.内存优化 默认情况下To ...

  9. tomcat学习步骤,附带打破双亲委派模型企业应用实战

    1. tomcat入门 入门模块仅做学习大纲梳理,忽略了具体操作指引. Tomcat的三种部署模式: 简单架构模型 连接器的非阻塞模式(NIO) 通道(Channel).缓冲区(Buffer).选择器 ...

随机推荐

  1. [JXOI 2018] 守卫 解题报告 (DP)

    interlinkage: https://www.luogu.org/problemnew/show/P4563 description: solution: 注意到对于范围$[l,r]$,$r$这 ...

  2. WAMP安装之坑

    Apache安装目录不能有空格 Apache根目录修改后不能直接localhost打开,可以通过改变端口,然后输入 localhost:端口号 打开

  3. 04--深入探讨C++中的引用

    深入探讨C++中的引用           引用是C++引入的新语言特性,是C++常用的一个重要内容之一,正确.灵活地使用引用,可以使程序简洁.高效.我在工作中发现,许多人使用它仅仅是想当然,在某些微 ...

  4. 文件系统VFS数据结构(超级块 inode dentry file)(收集整理)

    Linux虚拟文件系统四大对象: 1)超级块(super block) 2)索引节点(inode) 3)目录项(dentry) 4)文件对象(file) 一个进程在对一个文件进行操作时各种对象的引用过 ...

  5. 远程连接windows出现身份验证错误,提示"由于CredSSP加密Oracle修正"解决方案

    本机操作系统(OS版本:10.0.17134) 远程计算机操作系统(OS版本:6.3.9600) 远程连接的时候报错“出现身份验证错误,要求的函数不受支持.远程计算机:xxx 这可能是由于CredSS ...

  6. 【转】【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之缓存融合技术和主要后台进程(四)

    原文地址:http://www.cnblogs.com/baiboy/p/orc4.html   阅读目录 目录 Cache Fusion 原理 什么是 Cache Fusion? 什么是高可用 FA ...

  7. FastFDS常用命令

    1.启停fastdfs相关服务 #start fastdfs  启动服务 /usr/bin/fdfs_trackerd /etc/fdfs/tracker.conf restart /usr/loca ...

  8. js中时间戳与日期时间之间的相互转换

    1.时间戳转换为标准日期时间格式: function timeFormat(dateStr) { var date = new Date(dateStr); Y = date.getFullYear( ...

  9. HH的项链 树状数组动态维护前缀

    #include<cstdio> #include<algorithm> #include<cstring> using namespace std; const ...

  10. java 文件夹不存在的解决方案

    使用new File(path).mkdirs()创建所需路径,几十有多层不存在的路径也可以直接创建,切记方法名以s结尾,不带s的智能创建一层不存在的目录,不能自动创建多层目录结构.