Tomcat类加载

Posted 风走了,雨停了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Tomcat类加载相关的知识,希望对你有一定的参考价值。

一.为什么会有类加载

1.在类加载阶段,虚拟机需要完成以下3件事情

      1)通过一个全限类定名来获取此类的二进制字节流

      2) 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

      3)在内存中生成一个代表这个类的java.lang.Class 对象,作为方法区这个类的各种数据结构访问入口

2.虚拟机将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示: 

2)链接:

  验证:确保被加载类的正确性;(验证不过会抛出 java.lang.VerifyError或其子类异常)

  准备:为类的静态变量分配内存,并将其初始化为默认值;

  解析:把类中的符号引用转换为直接引用;

3)初始化:为类的静态变量赋予正确的初始值;

          那为什么我要有验证这一步骤呢?首先如果由编译器生成的class文件,它肯定是符合JVM字节码格式的,但是万一有高手自己写一个class文件,让JVM加载并运行,用于恶意用途,就不妙了,因此这个class文件要先过验证这一关,不符合的话不会让它继续执行的,也是为了安全考虑吧。

        准备阶段和初始化阶段看似有点牟盾,其实是不牟盾的,如果类中有语句:private static int a = 10,它的执行过程是这样的,首先字节码文件被加载到内存后,先进行链接的验证这一步骤,验证通过后准备阶段,给a分配内存,因为变量a是static的,所以此时a等于int类型的默认初始值0,即a=0,然后到解析(后面在说),到初始化这一步骤时,才把a的真正的值10赋给a,此时a=10。

在上述的整个过程中 链接(Link)和初始化(Initialize) 都是由虚拟机完成的,而装载则虚拟机则允许开发者自定义完成。

 

二.Java虚拟机类加载

Java虚拟机提供了三个类加载器

  1.启动类加载器:Bootstrap ClassLoader,跟上面相同。它负责加载存放在JAVA_HOME\\jre\\lib(JAVA_HOME代表JDK的安装目)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库(如rt.jar,所有的java.*开头的类均被Bootstrap ClassLoader加载)。启动类加载器是无法被Java程序直接引用的。

  2.扩展类加载器: Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载JAVA_HOME\\jre\\lib\\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。

  3.系统类加载器: Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径ClassPath  (System.getProperty("java.class.path"))所指定路径下的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。

三.自定义类加载器

   除了上面三个类加载器外,开发者还可以自定义类加载器,自定义类加载器需要实现 ClassLoader这个抽象类,可以选择重载 loadClass 或者 findClass方法。

   一个自定义类加载器的例子

 1 package com.dianping.loader;
 2 
 3 import java.io.InputStream;
 4 
 8 public class CustomerLoader extends ClassLoader {
 9 
10 
11     public CustomerLoader() {
12 
13     }
14 
15 
16     public CustomerLoader(ClassLoader parent) {
17         super(parent);
18     }
19 
20     @Override
21     public Class<?> loadClass(String name) throws ClassNotFoundException {
22         try {
23 
24             Class c = findLoadedClass(name);
25             if (c != null) return c;
26 
27             if (getParent() != null) {
28                 try {
29                     c = getParent().loadClass(name);
30                 } catch (ClassNotFoundException e) {
31                 }
32             }
33 
34             if (c == null) {
35                 String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
36                 InputStream in = getClass().getResourceAsStream(fileName);
37                 if (in == null) {
38                     return super.loadClass(name);
39                 }
40                 byte[] bytes = new byte[in.available()];
41                 in.read(bytes);
42                 c = defineClass(name, bytes, 0, bytes.length);
43             }
44             return c;
45         } catch (Exception e) {
46             throw new ClassNotFoundException(name);
47         }
48     }
49 
50 }

四、类加载的双亲委任模型

  类加载器虽然只用于实现类的加载动作,但它在Java程序中起到的作用却远远不限于类加载阶段。对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立其在Java虚拟机中的唯一性,每一个类加载器,都拥有一个独立的类名称空间。 这句话可以表达得更通俗一些:比较两个类是否“相等”,只有在这两个类是由同一个类加载器加载的前提下才有意义,否则,即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,那这两个类就必定不相等。
这里所指的“相等”,包括代表类的Class对象的equals()方法、 isAssignableFrom()方法、 isInstance()方法的返回结果,也包括使用instanceof关键字做对象所属关系判定等情况。
JVM的类加载的实现方式称为双亲委任模型,其流程是当收到一个类加载请求时,首先会交给父加载器完成,如果父加载器反馈自己无法加载时,子加载器才尝试自己完成加载,每一层次的类加载器都是如此。

  双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。 这里类加载器之间的父子关系一般不会以继承(Inheritance)的关系来实现,而是都使用组合(Composition)关系来复用父加载器的代码。       

  双亲委任可以解决了类加载过程中的安全性问题,如果定义了一个Java基础类库中的一个类,使用双亲委任模型保证了Java基础类的加载由上层类加载器优先加载。这样可以确保即使用户编写了一个java.lang.Object的类也不会影响虚拟机加载rt.jar下的java.lang.Object的类。

    当然双亲模型只是一个规范,tomcat并不是完全按照双亲模型规范的。

 

五、Tomcat类加载

在tomcat中类的加载稍有不同,tomcat并没有完全遵守双亲模型

    

 

1.在tomcat中 会初始化三个类加载器  commonLoader, catalinaLoader, sharedLoader  这三个类加载器都是继承 URLClassLoader这个类

加载类的路径是 ${tomcat_home}/conf/catalina.properties  文件配置的  

catalinaLoader 对应的配置是 server.loader ,sharedLoader对应的是 shared.loader 

默认的情况server.loader 和 shared.loader 都是没有配置,所以catalinaLoader,和 sharedLoader 其实都是 commonLoader ,他们父类加载器都是sun.misc.Launcher$AppClassLoader系统类加载器

 1 #
 2 #
 3 # List of comma-separated paths defining the contents of the "common"
 4 # classloader. Prefixes should be used to define what is the repository type.
 5 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
 6 # If left as blank,the JVM system loader will be used as Catalina\'s "common"
 7 # loader.
 8 # Examples:
 9 #     "foo": Add this folder as a class repository
10 #     "foo/*.jar": Add all the JARs of the specified folder as class
11 #                  repositories
12 #     "foo/bar.jar": Add bar.jar as a class repository
13 common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
14 
15 #
16 # List of comma-separated paths defining the contents of the "server"
17 # classloader. Prefixes should be used to define what is the repository type.
18 # Path may be relative to the CATALINA_HOME or CATALINA_BASE path or absolute.
19 # If left as blank, the "common" loader will be used as Catalina\'s "server"
20 # loader.
21 # Examples:
22 #     "foo": Add this folder as a class repository
23 #     "foo/*.jar": Add all the JARs of the specified folder as class
24 #                  repositories
25 #     "foo/bar.jar": Add bar.jar as a class repository
26 server.loader=
27 
28 #
29 # List of comma-separated paths defining the contents of the "shared"
30 # classloader. Prefixes should be used to define what is the repository type.
31 # Path may be relative to the CATALINA_BASE path or absolute. If left as blank,
32 # the "common" loader will be used as Catalina\'s "shared" loader.
33 # Examples:
34 #     "foo": Add this folder as a class repository
35 #     "foo/*.jar": Add all the JARs of the specified folder as class
36 #                  repositories
37 #     "foo/bar.jar": Add bar.jar as a class repository
38 # Please note that for single jars, e.g. bar.jar, you need the URL form
39 # starting with file:.
40 shared.loader=

2.catalinaLoader 主要用于加载Tomcat代码 

 

3.每一个web应用会对应一个WebappClassLoader,每个应用的WebappClassLoader是不一样的,这样可以确保应用间隔离。 WebappClassLoader的父类加载器是sharedLoader,因为默认情况sharedLoader是commonLoader,所以默认情况WebappClassLoader的父类加载器是commonLoader。

 

这个里说Tomcat没有完全准守双亲模型的原因是  在每一个应用中,WebappClassLoader加载类的时候会有以下步骤

1. 看类是不是可以被系统类加载器加载,如果可以在返回

2. 如果这个类不可以被系统类加载加载,这交给WebappClassLoader 加载,WebappClassLoader会在WEB-INF/classes中查找

3. 在WEB-INF/classes中没有找到,则会在WEB-INF/lib中查找

4.如果在WEB-INF/lib中也没有找到则会交给父类加载加载,默认情况父类加载器是commonLoader。

5. 如果以上没有找到,则抛出异常。

WebappClassLoader会对这个类的结果进行缓存,即找到了则缓存结果,找不到也会缓存找不到,所以以上行为其实是web应用第一次加载类的时候才会发生。

 

 

六、总结当tomcat 的WEB应用需要到某个类时,则会按照下面的顺序进行类加载

  1. 使用启动类加载器:Bootstrap ClassLoader      加载 JAVA_HOME\\jre\\lib  

      2. 使用扩展类加载器: Extension ClassLoader     加载JAVA_HOME\\jre\\lib\\ext 

  3. 使用系统类加载器: Application ClassLoader    它负责加载用户ClassPath  (System.getProperty("java.class.path") 这个参数所指定的文件夹)所指定的类

  4. 使用应用类加载器 WebappClassLoader          在该应用的WEB-INF/classes中路径中查找 

  5. 使用应用类加载器 WebappClassLoader          在WEB-INF/lib中查找

  6. 使用sharedLoader类加载器(默认情况下sharedLoader是commonLoader)    在CATALINA_HOME/lib中加载

 

参考地址:1. 图解Tomcat类加载机制

               2. Java类加载器总结

 

 

以上是关于Tomcat类加载的主要内容,如果未能解决你的问题,请参考以下文章

Android 逆向ART 脱壳 ( DexClassLoader 脱壳 | DexClassLoader 构造函数 | 参考 Dalvik 的 DexClassLoader 类加载流程 )(代码片段

JVM17_Tomcat打破双亲委派机制执行顺序底层代码原理Tomcat|JDBC破坏双亲委派机制带来的面试题

Tomcat 类加载器打破双亲委派模型

Tomcat的类加载架构

Tomcat打破双亲委派机制执行顺序底层代码原理JVM04_Tomcat JDBC破坏双亲委派机制带来的面试

tomcat 5.5 动态加载类