1、整体分析
主流的Java Web服务器,如Tomcat、Jetty、WebLogic、WebSphere等都实现了本身定义的类加载器(通常都不止一个)。由于一个功能健全的Web服务器,须要解决以下的几个问题:java
- 部署在同一个服务器上的两个Web应用程序使用的Java 类库能够实现相互隔离,这是最基本的要求.两个不一样应用程序可能会依赖同一个第三方类库的不一样版本的,不能要求一个类库在一个服务器中只有一份,服务器应当保证两个应用程序的类库能够互相独立使用
- 部署在同一个服务器上的两个Web应用程序所使用的Java类库能够互相共享,这个需求也很常见,若是Java类库不能共享使用,虚拟机的方法区很容易出现过分膨胀的风险
- 服务器须要尽量保证自身安全不受部署的Web应用程序影响.目前有许多主流的Java Web服务器都使用Java语言开发,所以服务器自己也有类库依赖的问题,通常来讲,基于安全的考虑,服务器所使用的类库应该与应用程序使用的类库互相独立
- 支持JSP的服务器,大部分都须要支持HotSwap功能(热交换功能)
本文基于Tomcat7.0.69的Java源码,对其类加载体系进行分析。web
因为上述的种种问题,在部署Web应用的时候若是只使用一个单独的ClassPath是没法知足需求的,因此各类Web服务器都不约而同的提供了多个ClassPath路径供用户存在第三方类库,这些路径通常都以lib,classes命名,被放置到不一样路径的类库,具有不一样的访问范围和服务对象.tomcat服务器划分用户类库结构和类加载描述以下,而后用一张图片来展现Tomcat的类加载体系:各个类加载器之间不是继承关系,而是一种委派关系。apache

这里结合以前对双亲委派模式的类加载过程的描述,对上图所示类加载体系进行介绍:
ClassLoader:Java提供的类加载器抽象类,用户自定义的类加载器须要继承实现
commonLoader:Tomcat最基本的类加载器,加载路径中的class能够被Tomcat容器自己以及各个Webapp访问;
catalinaLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见;
sharedLoader:各个Webapp共享的类加载器,加载路径中的class对于全部Webapp可见,可是对于Tomcat容器不可见;缓存
WebappClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;tomcat
经过下面类关系图以及逻辑关系图,同时对比上文内容梳理这些类加载器之间的关系。安全
一、类关系图

从图中看到了Common,Catalina,Shared类加载器是URLClassLoader类的一个实例,只是它们的类加载路径不同,在tomcat/conf/catalina.properties配置文件中配置(common.loader,server.loader,shared.loader).WebAppClassLoader继承自WebAppClassLoaderBase,基本全部逻辑都在WebAppClassLoaderBase为中实现了,能够看出tomcat的全部类加载器都是以URLClassLoader为基础进行扩展。服务器
二、逻辑关系图

上面说到Common,Catalina,Shared类加载器是URLClassLoader类的一个实例,在默认的配置中,它们其实都是同一个对象,即commonLoader,结合初始化时的代码(只保留关键代码):session
private void initClassLoaders() {
commonLoader = createClassLoader("common", null); // commonLoader的加载路径为common.loader
if( commonLoader == null ) {
commonLoader=this.getClass().getClassLoader();
}
catalinaLoader = createClassLoader("server", commonLoader); // 加载路径为server.loader,默认为空,父类加载器为commonLoader
sharedLoader = createClassLoader("shared", commonLoader); // 加载路径为shared.loader,默认为空,父类加载器为commonLoader
}
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent; // catalinaLoader与sharedLoader的加载路径均为空,因此直接返回commonLoader对象,默认3者为同一个对象
}
在上面的代码初始化时很明确是指出了,catalina与shared类加载器的父类加载器为common类加载器,而初始化commonClassLoader时父类加载器设置为null,最终会调到createClassLoader
静态方法:数据结构
public static ClassLoader createClassLoader(List<Repository> repositories,
final ClassLoader parent)
throws Exception {
.....
return AccessController.doPrivileged(
new PrivilegedAction<URLClassLoader>() {
@Override
public URLClassLoader run() {
if (parent == null)
return new URLClassLoader(array); //该构造方法默认获取系统类加载器为父类加载器,即AppClassLoader
else
return new URLClassLoader(array, parent);
}
});
}
在createClassLoader
中指定参数parent==null
时,最终会以系统类加载器(AppClassLoader)做为父类加载器,这解释了为何commonClassLoader的父类加载器是AppClassLoader.app
一个web应用对应着一个StandardContext
实例,每一个web应用都拥有独立web应用类加载器(WebClassLoader),这个类加载器在StandardContext.startInternal()
中被构造了出来:
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
这里getParentClassLoader()
会获取父容器StandarHost.parentClassLoader
对象属性,而这个对象属性是在Catalina$SetParentClassLoaderRule.begin()
初始化,初始化的值其实就是Catalina.parentClassLoader
对象属性,再来跟踪一下Catalina.parentClassLoader
,在Bootstrap.init()
时经过反射调用了Catalina.setParentClassLoader()
,将Bootstrap.sharedLoader
属性设置为Catalina.parentClassLoader
,因此WebClassLoader的父类加载器是Shared ClassLoader.
三、类加载逻辑
Tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用本身的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。具体的加载逻辑位于WebAppClassLoaderBase.loadClass()
方法中,代码篇幅长,这里以文字描述加载一个类过程:
- 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在
resourceEntries
这个数据结构中),若是已经加载即返回,不然 继续下一步。
- 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,若是加载到即返回,返回继续。
- 前两步均没加载到目标类,那么web应用的类加载器将自行加载,若是加载到则返回,不然继续下一步。
- 最后仍是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。
第3第4两个步骤的顺序已经违反了双亲委托机制,除了tomcat以外,JDBC,JNDI,Thread.currentThread().setContextClassLoader();
等不少地方都同样是违反了双亲委托。
2、源码分析
commonLoader、catalinaLoader和sharedLoader在Tomcat容器初始化的一开始,即调用Bootstrap的init方法时建立。catalinaLoader会被设置为Tomcat主线程的线程上下文类加载器,而且使用catalinaLoader加载Tomcat容器自身容器下的class。Bootstrap的init方法的部分代码见代码清单1。
代码清单1 Bootstrap的init方法的部分实现
[java] view plain copy
- /**
- * Initialize daemon.
- */
- public void init()
- throws Exception
- {
-
- // Set Catalina path
- setCatalinaHome();
- setCatalinaBase();
-
- initClassLoaders();
-
- Thread.currentThread().setContextClassLoader(catalinaLoader);
-
- SecurityClassLoad.securityClassLoad(catalinaLoader);
- // 省略后边的代码
代码清单1中,咱们首先关注initClassLoaders方法的实现,见代码清单2.initClassLoaders方法用来初始化commonLoader、catalinaLoader、sharedLoader。
代码清单2 initClassLoaders方法的实现
[java] view plain copy
- 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);
- }
- }
从代码清单2中看到建立类加载器是经过调用createClassLoader方法实现的,createClassLoader的实现见代码清单3.
代码清单3 createClassLoader方法的实现
[java] view plain copy
- private ClassLoader createClassLoader(String name, ClassLoader parent)
- throws Exception {
-
- String value = CatalinaProperties.getProperty(name + ".loader");
- if ((value == null) || (value.equals("")))
- return parent;
-
- ArrayList<String> repositoryLocations = new ArrayList<String>();
- ArrayList<Integer> repositoryTypes = new ArrayList<Integer>();
- int i;
-
- StringTokenizer tokenizer = new StringTokenizer(value, ",");
- while (tokenizer.hasMoreElements()) {
- String repository = tokenizer.nextToken();
-
- // Local repository
- boolean replace = false;
- String before = repository;
- while ((i=repository.indexOf(CATALINA_HOME_TOKEN))>=0) {
- replace=true;
- if (i>0) {
- repository = repository.substring(0,i) + getCatalinaHome()
- + repository.substring(i+CATALINA_HOME_TOKEN.length());
- } else {
- repository = getCatalinaHome()
- + repository.substring(CATALINA_HOME_TOKEN.length());
- }
- }
- while ((i=repository.indexOf(CATALINA_BASE_TOKEN))>=0) {
- replace=true;
- if (i>0) {
- repository = repository.substring(0,i) + getCatalinaBase()
- + repository.substring(i+CATALINA_BASE_TOKEN.length());
- } else {
- repository = getCatalinaBase()
- + repository.substring(CATALINA_BASE_TOKEN.length());
- }
- }
- if (replace && log.isDebugEnabled())
- log.debug("Expanded " + before + " to " + repository);
-
- // Check for a JAR URL repository
- try {
- new URL(repository);
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_URL);
- continue;
- } catch (MalformedURLException e) {
- // Ignore
- }
-
- if (repository.endsWith("*.jar")) {
- repository = repository.substring
- (0, repository.length() - "*.jar".length());
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_GLOB);
- } else if (repository.endsWith(".jar")) {
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_JAR);
- } else {
- repositoryLocations.add(repository);
- repositoryTypes.add(ClassLoaderFactory.IS_DIR);
- }
- }
-
- String[] locations = repositoryLocations.toArray(new String[0]);
- Integer[] types = repositoryTypes.toArray(new Integer[0]);
-
- ClassLoader classLoader = ClassLoaderFactory.createClassLoader
- (locations, types, parent);
-
- // Retrieving MBean server
- MBeanServer mBeanServer = null;
- if (MBeanServerFactory.findMBeanServer(null).size() > 0) {
- mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
- } else {
- mBeanServer = ManagementFactory.getPlatformMBeanServer();
- }
-
- // Register the server classloader
- ObjectName objectName =
- new ObjectName("Catalina:type=ServerClassLoader,name=" + name);
- mBeanServer.registerMBean(classLoader, objectName);
-
- return classLoader;
-
- }
createClassLoader方法的执行步骤以下:
- 获取各个类加载器相应的资源配置文件(分别为common.loader、server.loader、shared.loader),从中获取类资源路径的配置信息;
- 解析类资源路径下的各个资源位置和类型,也包括对jar资源的检查;
- 调用ClassLoaderFactory.createClassLoader(locations, types, parent)方法建立ClassLoader;
- 将ClassLoader注册到JMX服务中,有个JMX的内容能够参照《Tomcat7.0源码分析——生命周期管理 》一文中的相关介绍。
咱们回头看看代码清单1中的SecurityClassLoad.securityClassLoad(catalinaLoader)的实现,见代码清单4.这说明加载Tomcat容器自己的类资源的确是使用catalinaLoader来完成的。
代码清单4 securityClassLoad的实现
[java] view plain copy
- public static void securityClassLoad(ClassLoader loader)
- throws Exception {
-
- if( System.getSecurityManager() == null ){
- return;
- }
-
- loadCorePackage(loader);
- loadLoaderPackage(loader);
- loadSessionPackage(loader);
- loadUtilPackage(loader);
- loadJavaxPackage(loader);
- loadCoyotePackage(loader);
- loadTomcatPackage(loader);
- }
securityClassLoad方法主要加载Tomcat容器所需的class,包括:
- Tomcat核心class,即org.apache.catalina.core路径下的class;
- org.apache.catalina.loader.WebappClassLoader$PrivilegedFindResourceByName;
- Tomcat有关session的class,即org.apache.catalina.session路径下的class;
- Tomcat工具类的class,即org.apache.catalina.util路径下的class;
- javax.servlet.http.Cookie;
- Tomcat处理请求的class,即org.apache.catalina.connector路径下的class;
- Tomcat其它工具类的class,也是org.apache.catalina.util路径下的class;
咱们以加载Tomcat核心class的loadCorePackage方法为例,其实现见代码清单5所示。
代码清单5 loadCorePackage的实现
[java] view plain copy
- private final static void loadCorePackage(ClassLoader loader)
- throws Exception {
- String basePackage = "org.apache.catalina.";
- loader.loadClass
- (basePackage +
- "core.ApplicationContextFacade$1");
- loader.loadClass
- (basePackage +
- "core.ApplicationDispatcher$PrivilegedForward");
- loader.loadClass
- (basePackage +
- "core.ApplicationDispatcher$PrivilegedInclude");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$AsyncState");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$DebugException");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$1");
- loader.loadClass
- (basePackage +
- "core.AsyncContextImpl$2");
- loader.loadClass
- (basePackage +
- "core.AsyncListenerWrapper");
- loader.loadClass
- (basePackage +
- "core.ContainerBase$PrivilegedAddChild");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$1");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$2");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$3");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$4");
- loader.loadClass
- (basePackage +
- "core.DefaultInstanceManager$5");
- loader.loadClass
- (basePackage +
- "core.ApplicationHttpRequest$AttributeNamesEnumerator");
- }
至此,有关commonLoader、catalinaLoader和sharedLoader三个类加载器的初始化以及使用catalinaLoader加载Tomcat容器自身类资源的内容已经介绍完了,可是咱们尚未看到WebappClassLoader。启动StandardContext的时候会建立WebappLoader,根据《Tomcat7.0源码分析——生命周期管理 》一文的内容,咱们知道启动StandardContext时会最终调用其startInternal方法,其实现见代码清单6.
代码清单6 StandardContext的startInternal方法
[java] view plain copy
- /**
- * Start this component and implement the requirements
- * of {@link LifecycleBase#startInternal()}.
- *
- * @exception LifecycleException if this component detects a fatal error
- * that prevents this component from being used
- */
- @Override
- protected synchronized void startInternal() throws LifecycleException {
-
- // 省略前边的代码
-
- if (getLoader() == null) {
- WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
- webappLoader.setDelegate(getDelegate());
- setLoader(webappLoader);
- }
- // 省略中间的代码
- // Start our subordinate components, if any
- if ((loader != null) && (loader instanceof Lifecycle))
- ((Lifecycle) loader).start();
- // 省略后边的代码
- }
从代码清单6看到首先建立WebappLoader实例,而后调用WebappLoader的start方法,start又调用了startInternal方法,WebappLoader的startInternal的实现见代码清单7.
代码清单7 WebappLoader的startInternal实现
[java] view plain copy
- /**
- * Start associated {@link ClassLoader} and implement the requirements
- * of {@link LifecycleBase#startInternal()}.
- *
- * @exception LifecycleException if this component detects a fatal error
- * that prevents this component from being used
- */
- @Override
- protected void startInternal() throws LifecycleException {
-
- // Register a stream handler factory for the JNDI protocol
- URLStreamHandlerFactory streamHandlerFactory =
- new DirContextURLStreamHandlerFactory();
- if (first) {
- first = false;
- try {
- URL.setURLStreamHandlerFactory(streamHandlerFactory);
- } catch (Exception e) {
- // Log and continue anyway, this is not critical
- log.error("Error registering jndi stream handler", e);
- } catch (Throwable t) {
- // This is likely a dual registration
- log.info("Dual registration of jndi stream handler: "
- + t.getMessage());
- }
- }
-
- // Construct a class loader based on our current repositories list
- try {
-
- classLoader = createClassLoader();
- classLoader.setResources(container.getResources());
- classLoader.setDelegate(this.delegate);
- classLoader.setSearchExternalFirst(searchExternalFirst);
- if (container instanceof StandardContext) {
- classLoader.setAntiJARLocking(
- ((StandardContext) container).getAntiJARLocking());
- classLoader.setClearReferencesStatic(
- ((StandardContext) container).getClearReferencesStatic());
- classLoader.setClearReferencesStopThreads(
- ((StandardContext) container).getClearReferencesStopThreads());
- classLoader.setClearReferencesStopTimerThreads(
- ((StandardContext) container).getClearReferencesStopTimerThreads());
- classLoader.setClearReferencesThreadLocals(
- ((StandardContext) container).getClearReferencesThreadLocals());
- }
-
- for (int i = 0; i < repositories.length; i++) {
- classLoader.addRepository(repositories[i]);
- }
咱们看到代码清单7中经过调用createClassLoader来建立类加载器,而且设置其资源路径为当前Webapp下某个context的类资源。最后咱们看看createClassLoader的实现,见代码清单8.
代码清单8 createClassLoader的实现
[java] view plain copy
- /**
- * Create associated classLoader.
- */
- private WebappClassLoader createClassLoader()
- throws Exception {
-
- //loaderClass即字符串org.apache.catalina.loader.WebappClassLoader
- Class<?> clazz = Class.forName(loaderClass);
- 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;
-
- }
这里loaderClass的值是字符串org.apache.catalina.loader.WebappClassLoader,经过反射来实例化WebappClassLoader。因为每一个Webapp下的类资源由不一样的WebappClassLoader负责加载,所以Webapp下各个Context的类资源是独立的。至此,整个Tomcat的类加载体系构建完毕。
此外每一个jsp为了实现热替换,会有专门的类加载器负责加载。
参考文档:http://blog.csdn.net/beliefer/article/details/50995516
《深刻理解Java虚拟机》
http://blog.csdn.net/czmacd/article/details/54017027