`
jackyhongvip
  • 浏览: 159699 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

关于tomcat的类加载

 
阅读更多

关于tomcat和classloader的文章,网上多如牛毛,且互相转载,所以大多数搜到的基本上是讲到了tomcat中classloader的几个层次,对于初接触classloader,看了之后还是只知其然不知其所以然。

    我以为,学习tomcat classloader,先得追溯到为什么tomcat要自己造一堆自己的classloader出来,了解了这个之后才知道classloader用在什么地方,什么时候该新写,什么时候使用jvm的默认classloader;之后,得明白每个原因背后的道理,所谓明白,就是能将整个过程理清楚。

    开始之前,不得不费点篇幅申明,关于classloader,jvm的classloader等内容,请自行查阅资料(网上搜索或jvm相关资料),做到心中有个概念,相信读完此文,应该会加深这个印象。

    一直比较好奇,为什么tomcat需要实现自己的classloader,jvm提供的classloader有什么不符合需要?

   事实上,tomcat之所以造了一堆自己的classloader,大致是出于下面三类目的:

   1. 对于各个webapp中的class和lib,需要相互隔离,不能出现一个应用中加载的类库会影响另一个应用的情况;而对于许多应用,需要有共享的lib以便不浪费资源,举个例子,如果webapp1和webapp2都用到了log4j,可以将log4j提到tomcat/lib中,表示所有应用共享此类库,试想如果log4j很大,并且20个应用都分别加载,那实在是没有必要的。

   2. 第二个原因则是与jvm一样的安全性问题。使用单独的classloader去装载tomcat自身的类库,以免其他恶意或无意的破坏;

   3. 第三个原因是热部署的问题。相信大家一定为tomcat修改文件不用重启就自动重新装载类库而惊叹吧。

   由于篇幅所限,本文集中探讨第一个和第三个原因,即tomcat中如何利用classloader做到部分隔离,部分共享的,以及tomcat如何做到热部署的。

   首先,我们讨论tomcat中如何做到lib的部分隔离,部分共享的。在Bootstrap中,可以找到如下代码:

 

[java] view plaincopy
  1. private void initClassLoaders() {  
  2.         try {  
  3.             commonLoader = createClassLoader("common"null);  
  4.             if( commonLoader == null ) {  
  5.                 // no config file, default to this loader - we might be in a 'single' env.  
  6.                 commonLoader=this.getClass().getClassLoader();  
  7.             }  
  8.             catalinaLoader = createClassLoader("server", commonLoader);  
  9.             sharedLoader = createClassLoader("shared", commonLoader);  
  10.         } catch (Throwable t) {  
  11.             log.error("Class loader creation threw exception", t);  
  12.             System.exit(1);  
  13.         }  
  14.     }  

   应该可以看出来,这里创建了3个classloader,分别是common,server和shared,并且common是server和shared之父。如果感兴趣,可以看下createClassLoader,它会调用进ClassLoaderFactory.createClassLoader,这个工厂方法最后会创建一个StandardClassLoader,StandardClassLoader仅仅继承了URLClassLoader而没有其他更多改动,也就是说上面3个classloader都是StandardClassLoader,除了层次关系之外,他们与jvm定义的classloader并没有区别,这就意味着他们同样遵循双亲委派模型,只要我们能够用它装载指定的类,则它就自然的嵌入到了jvm的classloader体系中去了。

 

   问题来了,tomcat是如何将自己和webapp的所有类用自己的classloader加载的呢?是否需要有个专门的地方遍历所有的类并将其加载,可是代码里并不能找到这样的地方。而且相对来说,将不用的类显式的加载进来也是一种浪费,那么,tomcat(或者说jvm)是如何做到这点呢?

   这里有个隐式加载的问题,所谓的隐式加载,就是指在当前类中所有new的对象,如果没有被加载,则使用当前类的类加载器加载,即this.getClass(),getClassLoader()会默认加载该类中所有被new出来的对象的类(前提是他们没在别处先被加载过)。从这里思考,我们一个一个的应用,本质上是什么样子,事实上,正如所有程序都有一个main函数一样,所有的应用都有一个或多个startup的类(即入口),这个类是被最先加载的,并且随后的所有类都像树枝一样以此类为根被加载,只要控制了加载该入口的classloader,等于就控制了所有其他相关类的classloader。

   以此为线索来看tomcat的Bootstrap中的init代码

 

[java] view plaincopy
  1. public void init()  
  2.         throws Exception  
  3.     {  
  4.   
  5.         // Set Catalina path  
  6.         setCatalinaHome();  
  7.         setCatalinaBase();  
  8.   
  9.         initClassLoaders();  
  10.   
  11.         Thread.currentThread().setContextClassLoader(catalinaLoader);  
  12.   
  13.         SecurityClassLoad.securityClassLoad(catalinaLoader);  
  14.   
  15.         // Load our startup class and call its process() method  
  16.         if (log.isDebugEnabled())  
  17.             log.debug("Loading startup class");  
  18.         Class startupClass =  
  19.             catalinaLoader.loadClass  
  20.             ("org.apache.catalina.startup.Catalina");  
  21.         Object startupInstance = startupClass.newInstance();  
  22.   
  23.         // Set the shared extensions class loader  
  24.         if (log.isDebugEnabled())  
  25.             log.debug("Setting startup class properties");  
  26.         String methodName = "setParentClassLoader";  
  27.         Class paramTypes[] = new Class[1];  
  28.         paramTypes[0] = Class.forName("java.lang.ClassLoader");  
  29.         Object paramValues[] = new Object[1];  
  30.         paramValues[0] = sharedLoader;  
  31.         Method method =  
  32.             startupInstance.getClass().getMethod(methodName, paramTypes);  
  33.         method.invoke(startupInstance, paramValues);  
  34.   
  35.         catalinaDaemon = startupInstance;  
  36.   
  37.     }  

    在catalinaLoader.loadClass之后,Catalina事实上就由server这个classloader加载进来了,而下一句newInstance时,所有以Catalina为根的对象的类也会全部被隐式加载进来,但是为什么这里需要在其后费尽笔墨去setParentClassLoader呢,直接用((Catalina)startupInstance).setParentClassLoader岂不是更加方便?要注意,如果这样写,这边的Catalina便会由加载BootStrap的classloader(URLClassLoader)加载进来,而startupInstance是由StandardClassLoader加载进来的,并不是一个class,由此会抛一个ClassCastException。这也是类库可能发生冲突的一个原因

 

    搞明白这点,其实就可以理解tomcat是如何使用自己的classloader加载类进来并且如何隔离server和shared类的加载了。

    但是另一个问题,tomcat又是如何隔离不同的webapp的加载呢?

    对于每个webapp应用,都会对应唯一的StandContext,在StandContext中会引用WebappLoader,该类又会引用WebappClassLoader,WebappClassLoader就是真正加载webapp的classloader。

    StandContext隶属于Lifecycle管理,在start方法中会做一系列准备工作(有兴趣可以参考,实际上该方法比较重要,但是篇幅太长),比如新建WebappClassLoader,另外loadOnStartup便会加载所有配置好的servlet(每个StandardWrapper负责管理一个servlet),这里同样的一个问题是,在我们自己写的web应用程序中,入口是什么?答案就是Servlet, Listener, Filter这些组件,如果我们控制好入口的classloader,便等于控制了其后所加载的全部类,那么,tomcat是如何控制的呢?且看StandardWrapper中一个重要的方法loadServlet(篇幅所限,隐去了大部分不想关内容),getLoader()事实上调用到了StandContext中保存的WebappLoader,于是,用该loader加载Servlet,从而控制住了Servlet中所有待加载的类。

 

[java] view plaincopy
  1. public synchronized Servlet loadServlet() throws ServletException {  
  2.   
  3.         ...  
  4.   
  5.         Servlet servlet;  
  6.         try {  
  7.             ...  
  8.   
  9.             // Acquire an instance of the class loader to be used  
  10.             Loader loader = getLoader();  
  11.             if (loader == null) {  
  12.                 unavailable(null);  
  13.                 throw new ServletException  
  14.                     (sm.getString("standardWrapper.missingLoader", getName()));  
  15.             }  
  16.   
  17.             ClassLoader classLoader = loader.getClassLoader();  
  18.   
  19.             // Special case class loader for a container provided servlet  
  20.             //    
  21.             if (isContainerProvidedServlet(actualClass) &&   
  22.                     ! ((Context)getParent()).getPrivileged() ) {  
  23.                 // If it is a priviledged context - using its own  
  24.                 // class loader will work, since it's a child of the container  
  25.                 // loader  
  26.                 classLoader = this.getClass().getClassLoader();  
  27.             }  
  28.   
  29.             // Load the specified servlet class from the appropriate class loader  
  30.             Class classClass = null;  
  31.             try {  
  32.                 if (SecurityUtil.isPackageProtectionEnabled()){  
  33.                     ...  
  34.                 } else {  
  35.                     if (classLoader != null) {  
  36.                         classClass = classLoader.loadClass(actualClass);  
  37.                     } else {  
  38.                         classClass = Class.forName(actualClass);  
  39.                     }  
  40.                 }  
  41.             } catch (ClassNotFoundException e) {  
  42.                 unavailable(null);  
  43.                 getServletContext().log( "Error loading " + classLoader + " " + actualClass, e );  
  44.                 throw new ServletException  
  45.                     (sm.getString("standardWrapper.missingClass", actualClass),  
  46.                      e);  
  47.             }  
  48.   
  49.             if (classClass == null) {  
  50.                 unavailable(null);  
  51.                 throw new ServletException  
  52.                     (sm.getString("standardWrapper.missingClass", actualClass));  
  53.             }  
  54.   
  55.             // Instantiate and initialize an instance of the servlet class itself  
  56.             try {  
  57.                 servlet = (Servlet) classClass.newInstance();  
  58.                 // Annotation processing  
  59.                 if (!((Context) getParent()).getIgnoreAnnotations()) {  
  60.                     if (getParent() instanceof StandardContext) {  
  61.                        ((StandardContext)getParent()).getAnnotationProcessor().processAnnotations(servlet);  
  62.                        ((StandardContext)getParent()).getAnnotationProcessor().postConstruct(servlet);  
  63.                     }  
  64.                 }  
  65.             } catch (ClassCastException e) {  
  66.                 ...  
  67.             } catch (Throwable e) {  
  68.                 ...  
  69.             }  
  70.   
  71.             ...  
  72.         return servlet;  
  73.   
  74.     }  

     这里的加载过程与之前的一致,至于如何做到不同webapp之间的隔离,我想大家已经明白,不同的StandardContext有不同的WebappClassLoader,那么不同的webapp的类装载器就是不一致的。装载器的不一致带来了名称空间不一致,所以webapp之间是相互隔离的。

 

    关于tomcat是如何做到热部署的,相信不用说也能猜到个十之八九。简单讲就是定期检查是否需要热部署,如果需要,则将类装载器也重新装载,并且去重新装载其他相关类。关于tomcat是如何做的,可以具体看以下分析。

    首先来看一个后台的定期检查,该定期检查是StandardContext的一个后台线程,会做reload的check,过期session清理等等,这里的modified实际上调用了WebappClassLoader中的方法以判断这个class是不是已经修改。注意到他调用了StandardContext的reload方法。

 

[java] view plaincopy
  1. public void backgroundProcess() {  
  2.         if (reloadable && modified()) {  
  3.             try {  
  4.                 Thread.currentThread().setContextClassLoader  
  5.                     (WebappLoader.class.getClassLoader());  
  6.                 if (container instanceof StandardContext) {  
  7.                     ((StandardContext) container).reload();  
  8.                 }  
  9.             } finally {  
  10.                 if (container.getLoader() != null) {  
  11.                     Thread.currentThread().setContextClassLoader  
  12.                         (container.getLoader().getClassLoader());  
  13.                 }  
  14.             }  
  15.         } else {  
  16.             closeJARs(false);  
  17.         }  
  18.     }  

 

    那么reload方法具体做了什么?非常简单,就是tomcat lifecycle中标准的启停方法stop和start,别忘了,start方法会重新造一个WebappClassLoader并且重复loadOnStartup的过程,从而重新加载了webapp中的类,注意到一般应用很大时,热部署通常会报outofmemory: permgen space not enough之类的,这是由于之前加载进来的class还没有清除而方法区内存又不够的原因

 

[java] view plaincopy
  1. public synchronized void reload() {  
  2.   
  3.         // Validate our current component state  
  4.         if (!started)  
  5.             throw new IllegalStateException  
  6.                 (sm.getString("containerBase.notStarted", logName()));  
  7.   
  8.         // Make sure reloading is enabled  
  9.         //      if (!reloadable)  
  10.         //          throw new IllegalStateException  
  11.         //              (sm.getString("standardContext.notReloadable"));  
  12.         if(log.isInfoEnabled())  
  13.             log.info(sm.getString("standardContext.reloadingStarted",  
  14.                     getName()));  
  15.   
  16.         // Stop accepting requests temporarily  
  17.         setPaused(true);  
  18.   
  19.         try {  
  20.             stop();  
  21.         } catch (LifecycleException e) {  
  22.             log.error(sm.getString("standardContext.stoppingContext",  
  23.                     getName()), e);  
  24.         }  
  25.   
  26.         try {  
  27.             start();  
  28.         } catch (LifecycleException e) {  
  29.             log.error(sm.getString("standardContext.startingContext",  
  30.                     getName()), e);  
  31.         }  
  32.   
  33.         setPaused(false);  
  34.   
  35.     }  



 

 

     参考资料:http://blog.csdn.net/aesop_wubo/article/details/7582266

     http://my.oschina.net/321tiantian/blog/69399

     http://developer.51cto.com/art/201003/185704.htm

分享到:
评论

相关推荐

    tomcat类加载器

    这个"DevLoader.zip"文件可能包含与Tomcat自定义类加载器相关的资料,特别是名为"DevLoader"的类加载器,这可能是Tomcat为开发者提供的一种特殊加载器。 首先,我们来理解一下类加载器的基本概念。在Java中,类加载...

    tomcat 类加载机制 —— ClassLoader

    《Tomcat类加载机制——ClassLoader详解》 在Java Web开发中,Tomcat作为最常用的Servlet容器,其类加载机制对于理解和优化应用性能至关重要。本文将深入探讨Tomcat的ClassLoader是如何工作的,以及它如何影响到...

    java类加载器-tomcat中的类加载器

    下面我们将深入探讨Java类加载器以及Tomcat中的类加载器。 在Java中,类加载器主要分为三个层次:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap ClassLoader负责加载JDK的核心库,如rt...

    Tomcat启动时类加载顺序

    ### Tomcat启动时类加载顺序详解 #### 一、引言 Apache Tomcat是一款开源的Servlet容器,主要用于部署Java Web应用程序。它支持最新的Servlet、JSP等规范,并且以其轻量级、简单易用的特点而受到开发者的青睐。在...

    Tomcat7 启动类加载日志

    Tomcat7.0.62 启动类加载日志

    Java 类在 Tomcat 中是如何加载的(过程分析)

    当Tomcat加载类时,它遵循以下顺序: - 使用Bootstrap类加载器尝试加载。 - 使用System类加载器尝试加载。 - 使用Webapp类加载器加载`WEB-INF/classes`下的类。 - 使用Webapp类加载器加载`WEB-INF/lib`中的jar...

    让tomcat自动加载修改过的类和servlet

    因此,让Tomcat自动加载修改过的类和Servlet成为提高开发效率的关键需求之一。 ### 让Tomcat自动加载修改过的类和Servlet #### 知识点一:理解Tomcat的热部署机制 Tomcat默认情况下并不会自动检测到类或Servlet的...

    类加载器与Tomcat

    首先,Tomcat的类加载器层次结构由Bootstrap类加载器、Common类加载器、Shared类加载器和Web应用程序类加载器组成。Bootstrap类加载器是JVM启动时的第一个加载器,负责加载JDK的核心类库。Tomcat使用Bootstrap类加载...

    Eclipse Tomcat Server 加载项目

    ### Eclipse Tomcat Server 加载项目的详细步骤与配置 在开发Java Web应用时,Eclipse集成开发环境(IDE)因其强大的功能、丰富的插件支持以及友好的用户界面而被广泛使用。其中,Eclipse内置的Tomcat服务器是进行...

    myeclipse下tomcat动态加载

    用户可以通过MyEclipse的服务器向导来添加、配置和管理Tomcat服务器实例。配置过程中,可以选择Tomcat的安装路径,设置端口号,以及决定是否在调试模式下运行。 2. **动态加载的设置**:在MyEclipse中,可以为...

    Linux中将Tomcat添加到守护进程

    本文将详细介绍如何将Tomcat添加到Linux的守护进程,并处理"Invalid user name 'tomcat' specified"的异常问题。 首先,我们需要设置环境变量。打开 `/etc/profile` 文件,并添加以下两行来指定Tomcat和Java的安装...

    3-7Tomcat中自定义类加载器的使用与源码实现(1).mp4

    3-7Tomcat中自定义类加载器的使用与源码实现(1).mp4

    如何让tomcat自动加载

    介绍了让tomcat自动加载的 代码及文件

    JVM、Tomcat、OSGI等类加载器整理文档

    JVM、OSGI(Open Service Gateway Initiative)和Tomcat等容器都涉及到了类加载器的概念,理解它们的工作原理对优化应用性能和解决依赖冲突至关重要。 1. JVM类加载器: - **父类加载器**:在Java中,类加载器之间...

    Tomcat加载顺序

    ### Tomcat加载顺序详解 Apache Tomcat作为一款广泛使用的开源Servlet容器,其类加载机制是理解和维护基于Tomcat的应用程序的重要部分。Tomcat的类加载器遵循特定的加载顺序,确保了不同应用间的隔离性和资源的正确...

    tomcat类包中的一个

    只需将新版本的文件覆盖旧文件,Tomcat会自动检测变化并重新加载。 10. **与其他框架集成**:Tomcat作为基础服务器,常与Spring Boot、Struts、Hibernate等框架集成,构建更复杂的Web应用。 以上就是关于Tomcat和...

    Tomcat启动顺序

    Bootstrap类加载器完成后,Tomcat会使用系统类加载器(System ClassLoader)加载服务器的全局配置文件,如`server.xml`。这个文件定义了Tomcat的整体架构,包括服务(Service)、连接器(Connector)和引擎(Engine)等组件...

    Tomcat 类加载器的实现方法及实例代码

    Tomcat 类加载器的实现主要围绕着Java的类加载机制进行,旨在确保应用之间的类隔离,并提供灵活的资源访问策略。下面我们将深入探讨这些知识点。 首先,Java的类加载机制是基于“双亲委托模型”的。当一个类加载器...

    tomcat 源码分析系列文档

    5. "tomcat加载类的顺序.doc":详细说明了Tomcat加载类的具体步骤和顺序,这对于理解和调试类加载问题至关重要。 6. "Tomcat源码研究.pdf":提供了一个全面的源码分析概览,可能包括了Tomcat的主要组件、设计模式...

    tomcat7,tomcat8,tomcat9

    2. 在Eclipse中,通过"Window" -> "Preferences" -> "Server" -> "Runtime Environments" 添加Tomcat实例,指定Tomcat的安装路径。 3. 配置服务器运行时环境,设置JRE版本和服务器端口等。 4. 创建或导入Web项目,将...

Global site tag (gtag.js) - Google Analytics