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. 【HDU 1847】 Good Luck in CET-4 Everybody!

    [题目链接] http://acm.hdu.edu.cn/showproblem.php?pid=1847 [算法] 我们知道,每一种状态,要么必胜,要么必败 记忆化搜索即可 [代码] #includ ...

  2. css3 背景background

    Css3背景<background> Css3包含多个新的背景属性,它们提供了对背景更强大的控制.可以自定义背景图的大小,可以规定背景图片的定位区域,css3还允许我们为元素使用多个背景图 ...

  3. 数据科学的完整学习路径(Python版)

    转载自:http://python.jobbole.com/80981/ 英文(原文)连接:https://www.analyticsvidhya.com/learning-paths-data-sc ...

  4. Android WiFi热点完全研究(自定义创建、跳转系统界面设置、读取配置、切换,Android6.0适配)

    前言: WiFi热点设置页面的安全性选项在Android 4.x上有“无”.“WPA PSK”.“WPA2 PSK”三个选项,在Android 5.0(含)之后去掉了WPA PSK选项(部分手机厂家会 ...

  5. 【Oracle】创建用户

    任务: 1)创建用户siebel,密码oracle 2)授予sse_role,tblo_role角色 3)siebel用户没有对system,sysaux的使用权限 4)默认表空间ts_users,无 ...

  6. C#的split函数分割

    C#的split函数分割 string str = textBox1.Text; string[] strlist = str.Split("\r\n".ToCharArray() ...

  7. form 表单的另类触发方式:报错触发

    在用form表单提交的时候,遇到一个问题:表单未验证完,表单就提前提交了. 然后通过断点调试,发现form提交会因为函数报错提前提交. 即如果你的form提交过程中,没有执行到return true之 ...

  8. ZJOI2015 幻想乡战略游戏 动态点分治_树链剖分_未调完

    Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来, ...

  9. C#学习 第九节

    构造器 1.构造器(constructor)是类型的成员之一: 2.狭义的构造器是指“实例构造器”(instance constructor): 3.构造器的调用 student stu =new s ...

  10. css 垂直居中方法总结

    工作中遇到垂直居中问题,特此总结了一下几种方式与大家分享.本文讨论的垂直居中仅支持IE8+ 1.使用绝对定位垂直居中 HTML <div class="container"& ...