Tomcat7源码解读 —— 类加载器

这一节将分析,Tomcat启动以及请求处理过程中所涉及到的类与对象,是由谁加载的,Tomcat的类加载器的特点。

 

4.1 JAVA的类加载过程

1)类加载load:从字节码二进制文件.class文件将类加载到内存将内存中的class放到运行时数据区的方法区内。类的初始化过程会在堆区建立一个java.lang.Class对象,用来封装该类相关的数据结构。

 

2)  连接:连接又分为以下小步骤

    a) 验证:出于安全性的考虑,验证内存中的字节码是否符合JVM的规范,类的结构规范、语义检查、字节码操作是否合法、这个是为了防止用户自己建立一个非法的XX.class文件就进行工作了,或者是JVM版本冲突的问题,比如在JDK6下面编译通过的class(其中包含注解特性的类),是不能在JDK1.4的JVM下运行的。

    b) 准备:将类的静态变量进行分配内存空间、初始化默认值。(对象还没生成呢,所以这个时候没有实例变量什么事情)

    c)  解析:不同的JVM实现可能选择不同的解析策略。一种做法是递归的把所有依赖的形式引用(只是申明,并不适用类)都进行解析。而另外的做法则可能是只在一个形式引用真正需要的时候(也就是说在所引用类的对象被创建)才进行解析。也就是说如果一个Java类只是被引用了,但是并没有被真正用到,那么这个类有可能就不会被解析。当前的JVM一般都采用了第二种策略。

 

 3) 类的初始化: 将类的静态变量赋予正确的初始值,这个初始值是开发者自己定义时赋予的初始值,而不是默认值。

 

4.2 类加载器的加载模式

Parent First加载模式(也就是在加载的时候会委托给父亲类加载器优先加载的模式)只是JVM的一种默认实现,并不是所有的类加载都需要实现该模式的。

 

Parent Last加载模式,Tomcat的应用类加载器,WebappClassLoader,每一个Context都会尝试优先加载所需要的类,只有在无法加载到的时候才委托给父亲加载器完成加载操作。之所以实现这样一种策略为了在多应用的环境中,例如应用A,B使用了默认的数据库连接实现,而应用C需要定制,那么这样C应用中只要包含该实现就可以实现覆盖默认的实现。

 

4.3 Tomcat7的类加载器的层级结构

Tomcat7源码解读 —— 类加载器
Tomcat7运行时类的加载说明:

1)Bootstrap Class Loader是JVM的内核由C实现的,加载了JVM的核心包rt.jar。rt.jar中的所有类执行其class的getClassLoader()方法都将返回null,例如String.class.getClassLoader()。

 

2)Extension Class Loader主要加载了JVM扩展包中相关的jar包。例如运行下列代码将System.out.println(ZipPath.class.getClassLoader());将得到如下的运行结果:sun.misc.Launcher$ExtClassLoader

 

3)System Class Loader加载CLASSPATH相关的类,例如在Tomcat的Bootstrap的main方法中执行System.out.println(Bootstrap.class.getClassLoader());则将得到:sun.misc.Launcher$AppClassLoader

 

4)Common Class Loader,Tomcat7中的CATALINA_HOME/lib下的jar包。注意Tomcat在启动文件中将启动时配置了-classpath "%CATALINA_HOME%\lib\catalina.jar"因此catalina.jar中的类虽然指定使用类加载器Common Class Loader,但是按JVM的委托加载原则System.out.println(Bootstrap.class.getClassLoader());得到的类加载器是:sun.misc.Launcher$AppClassLoader。

 

5)Webapp Class Loader, 主要负责加载Context容器中的所有的类。实际上该加载器提供了参数delegateLoad供用户设定是否使用parent-first加载。默认该值为false,默认用parent-last加载。出于安全性的考虑对于核心类WebappClassLoader是不允许加载的。包括:java.,javax.servlet.jsp.jstl,javax.servlet.,javax.el.


Webapp Class Loader的过程如下:

  1. 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在resourceEntries这个数据结构中),如果已经加载即返回,否则 继续下一步。
  2. 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,返回继续。
  3. 前两步均没加载到目标类,那么web应用的类加载器将自行加载,如果加载到则返回,否则继续下一步。
  4. 最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。

 

4.4 类加载器的相关类结构图

Tomcat7源码解读 —— 类加载器
 4.5 类加载器的相关源代码

(一)ClassLoader的load方法

Java代码  Tomcat7源码解读 —— 类加载器
  1. protected Class<?> loadClass(String name, boolean resolve)  
  2.         throws ClassNotFoundException  
  3.     {  
  4.         synchronized (getClassLoadingLock(name)) {  
  5.             // First, check if the class has already been loaded  
  6.             Class c = findLoadedClass(name);  
  7.             if (c == null) {  
  8.                 try {  
  9.                     if (parent != null) {  
  10.                         c = parent.loadClass(name, false);  
  11.                     } else {  
  12.                         c = findBootstrapClassOrNull(name);  
  13.                     }  
  14.                 }   
  15.                 if (c == null) {  
  16.                     // If still not found, then invoke findClass in order  
  17.                     // to find the class.  
  18.                     long t1 = System.nanoTime();  
  19.                     c = findClass(name);  
  20.   
  21.                     // this is the defining class loader; record the stats  
  22.                     sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);  
  23.                     sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);  
  24.                     sun.misc.PerfCounter.getFindClasses().increment();  
  25.                 }  
  26.             }  
  27.             if (resolve) {  
  28.                 resolveClass(c);  
  29.             }  
  30.             return c;  
  31.         }  
  32.     }  

1)在该方法中,首先检查是否已经加载了该类,这里有个问题JVM如何判断一个类是否被加载过的?这里涉及到了类的命名空间问题。在JAVA中判断一个类是否相同不仅看类名是否相同,还要看其类加载器是否相同。同一个类可以被不同的类加载器所加载,并且认为是不同的。该问题可以分解下面两个方面看。

a) 单一加载原则:在加载器链中,一个类只会被链中的某一个加载器加载一次。而不会被重复加载。实现类的共享,Tomcat多个应用,如果需要共享一些jar包,那么只需要交给commonClassLoader加载,那么所有的应用就可以共享这些类。

b) 可见性原则:父加载器加载的类,子加载器是可以访问的。而自加载器所加载的类,父加载器无法访问。不同加载器链之间其是相互不可见,无法访问的。实现隔离,Tomcat就是应用该特性,为每一个Context容器创建一个WebappClassLoader类加载器对象,从而实现了应用间的相互隔离。应用间的类是不可见的所以无法相互访问。

 

2) 如果步骤一中无缓存,查看该类父加载器,如果存在那么委托给付加载器。如果没有父加载器那么认为BootstrapClassLoader是其父加载器,委托进行加载。

 

3)如果父加载器无法加载则抛出ClassNotFoundException,调用抽象方法findClass方法。

 

4)此处的resolveClass方法指的是上文类加载过程中连接的第三步操作。resolve该类的形式引用等等。

 

(二)类URLClassLoader的findClass方法

Java代码  Tomcat7源码解读 —— 类加载器
  1. protected Class<?> findClass(final String name)  
  2.          throws ClassNotFoundException  
  3.     {  
  4.         try {  
  5.             return AccessController.doPrivileged(  
  6.                 new PrivilegedExceptionAction<Class>() {  
  7.                     public Class run() throws ClassNotFoundException {  
  8.                         String path = name.replace('.''/').concat(".class");  
  9.                         Resource res = ucp.getResource(path, false);  
  10.                         if (res != null) {  
  11.                             try {  
  12.                                 return defineClass(name, res);  
  13.                             } catch (IOException e) {  
  14.                                 throw new ClassNotFoundException(name, e);  
  15.                             }  
  16.                         } else {  
  17.                             throw new ClassNotFoundException(name);  
  18.                         }  
  19.                     }  
  20.                 }, acc);  
  21.         } catch (java.security.PrivilegedActionException pae) {  
  22.             throw (ClassNotFoundException) pae.getException();  
  23.         }  
  24.     }  

该方法的核心是获取到JAVA类的字节码,然后调用父类的defineClass方法完成类的构造过程。defineClass是由JVM实现的,不允许被覆写,因此用户类文件就必须遵循JVM的文件规范才能被正确的解析。

 

(三)WebappClassLoader重新覆写了ClassLoader的loadClass方法(删除了部分代码)

Java代码  Tomcat7源码解读 —— 类加载器
  1. @Override  
  2. public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {  
  3.         Class<?> clazz = null;  
  4.         ………  
  5.         // (0) 当前对象缓存中检查是否已经加载该类  
  6.         clazz = findLoadedClass0(name);  
  7.   
  8.         // (0.1) 检查JVM的缓存,是否已经加载过该类  
  9.         clazz = findLoadedClass(name);  
  10.   
  11.         // (0.2) 防止加载一些系统相关的类  
  12.         try {  
  13.             clazz = system.loadClass(name);  
  14.             if (clazz != null) {  
  15.                 if (resolve)  
  16.                     resolveClass(clazz);  
  17.                 return (clazz);  
  18.             }  
  19.         } catch (ClassNotFoundException e) {}  
  20.   
  21.         boolean delegateLoad = delegate || filter(name);  
  22.   
  23.         // (1) 如果配置了parent-first模式,那么委托给父加载器  
  24.         if (delegateLoad) {  
  25.             ClassLoader loader = parent;  
  26.             if (loader == null) loader = system;  
  27.             try {  
  28.                 clazz = Class.forName(name, false, loader);  
  29.                 if (clazz != null) {  
  30.                     if (resolve) resolveClass(clazz);  
  31.                     return (clazz);  
  32.                 }  
  33.             } catch (ClassNotFoundException e) {}  
  34.         }  
  35.   
  36.         // (2) 从应用环境中查找类,主要是应用下的/lib目录与classes目录  
  37.         try {  
  38.             clazz = findClass(name);  
  39.             if (clazz != null) {  
  40.                 if (resolve)  
  41.                     resolveClass(clazz);  
  42.                 return (clazz);  
  43.             }  
  44.         } catch (ClassNotFoundException e) {}  
  45.   
  46.         // (3) 如果在当前应用下无法找到所需要的类,再委托给父加载器加载(parent-last)  
  47.         if (!delegateLoad) {  
  48.             ClassLoader loader = parent;  
  49.             if (loader == null)  
  50.                 loader = system;  
  51.             try {  
  52.                 clazz = Class.forName(name, false, loader);  
  53.                 if (clazz != null) {  
  54.                     if (resolve)  
  55.                         resolveClass(clazz);  
  56.                     return (clazz);  
  57.                 }  
  58.             } catch (ClassNotFoundException e) {}  
  59.         }  
  60.   
  61.         throw new ClassNotFoundException(name);  
  62.   
  63.     }  

 

 

4.6 总结

本节介绍了JVM的类加载器原理,Tomcat的类加载器结构,类加载器的实现,以及类加载器的部分源代码。重点需要理解的是类加载器的命名空间这一设计特性。所以的其他内容都是服务该特性的,从而实现了类加载的安全性,可见性等。