文章目录
前言
我们知道java代码写好的java类,需要先经过编译,生成一个字节码文件(.class文件),然后这个字节码文件还需要北加载到jvm内存当中(这个过程就是一个类加载的过程)
类加载器是什么呢?
就是ClassLoader,说白了其实也是一个类,jvm启动的时候先把类加载器读取到内存当中去,其他的类(比如各种jar包中的字节码文件以及自己开发的代码编译后形成的.class文件等)由类加载器进行加载
Tomcat的类加载机制其实就是在Jvm的类加载机制基础上进行了一些变动
一、JVM的类加载机制
1.1加载器的种类和作用
JVM的类加载机制中有一个非常重要的角色叫做类加载器(ClassLoader),类加载器有自己的体系,JVM内置了几种类加载器,包括:引导类加载器、扩展类加载器、系统类加载器,他们之间形成父子关系
类加载器 | 作用 |
---|---|
引导类加载器(BootstrapClassLoader) | c++编写,用来加载java和辛苦java.*,比如rt.jar中的类,构造ExtClassLoader和AppClassLoader |
扩展类加载器(ExtClassLoader) | java编写,用来加载库JAVA_HOME/lib/ext目录下的jar中的类,如classpath中的jre.javax.*或者java.ext.dir指定位置中的类 |
系统类加载器SystemClassLoader/AppClassLoader | 默认的类加载器,搜索环境变量classpath中指明的路径 |
另外,用户可以自定义类加载器(Java编写,用户自定义的类加载器,可以加载指定路径的class文件)
1.2用户自定义类加载器的执行步骤(父类委托机制,也叫双亲委派机制)
当JVM运行过程中,用户自己定义了类加载器去加载某些类时,会按照下面的步骤
1)用户自己的类加载器把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层
2)最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转嫁给子类
3)如果一直到底层的类加载器都没有加载到,那么就会抛出异常:ClassNotFoundException
因此,按照这个过程可以想到,如果同样在classpath指定的目录中和自己工作目录中存放相同的class,会优先加载classpath目录中的文件
二、双亲委派机制
2.1什么是双亲委派机制
当某个类加载器需要加载某个.class文件是,他首先会把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类
2.2双亲委派机制的作用
1)防止重复加载同一个.class文件,通过委托去上面问一问,记载过了就不用在加载一遍了,保证数据的安全
2)保证核心的.class文件不被篡改。通过委托方式,不回去篡改核心的.class,及时篡改也不会被加载,及时加载也不会是同一个.class对象。不同的类加载器价在同一个.class也不是同一个.class对象。这样保证了class执行安全(如果子类加载器先加载,那么我们可以写一些与java.lang包中基础类同名 的类, 然后再定义一个子类加载器,这样整个应用使用的基础类就都变成我们自己定义的类了。)
举个例子:
自定义一个Object类
然后自定义一个类加载器
如果自定义类加载器先加载或者直接加载,由于有一个自定义个Object类,导致真实的基础包的Object类不会被加载,那么就造成了真正的Object类被篡改的现象
三、Tomcat的类加载机制
Tomcat的类加载机制相对于Jvm的类加载机制做了一些改变
没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制
为什么这么做呢?我们来举个例子
比如:有一个tomcat,webapps下部署了两个应用
app1引用了/lib/a-1.0.jar中的com.lagou.edu.Abc
app2引用了/lib/a-2.0.jar中的com.lagou.edu.Abc
不同版本中Abc类的内容是不同的,代码是不一样的,但是他们在classpath里的全限定类名是一样的
如果tomcat完全按照双亲委派机制来做的话,就会出现,app1启动的时候加载了1.0版本中的Abc类,然后app2启动的时候由于Abc类的全限定类名是一样的,就不会重新加载了,就会导致app2引用了错误版本的代码
3.1Tomcat的类加载器结构及作用
1)引导类加载器 和 扩展类加载器 的作用不变
2)系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使用该变 量,而是加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。 位于CATALINA_HOME/bin下
3)Common 通用类加载器加载Tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下, 比如servlet-api.jar
4)Catalina ClassLoader 用于加载服务器内部可⻅类,这些类应用程序不能访问
5)Shared ClassLoader 用于加载应用程序共享类,这些类服务器不会依赖
6)Webapp ClassLoader,每个应用程序都会有一个独一无二的Webapp ClassLoader,他用来加载本应用程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
tomcat 8.5 默认改变了严格的双亲委派机制
1)首先从 Bootstrap Classloader加载指定的类
2)如果未加载到,则从 /WEB-INF/classes加载
3)如果未加载到,则从 /WEB-INF/lib/*.jar 加载
4)如果未加载到,则依次从 System、Common、Shared 加载(在这最后一步,遵从双亲委派机制)
也就是说要加载一个类的时候会先用引导类加载器去加载,
如果没有加载到的话直接使用WebApp 类加载器从 /WEB-INF/classes加载或者从 /WEB-INF/lib/*.jar 加载,
再没加载到的话才依次从 System、Common、Shared 类加载器加载,且在这最后一步,遵从双亲委派机制
也就是说要加载一个类的时候,同样会先通过引导类加载器避免核心class文件被篡改,然后会先用每个应用自己的WebApp类加载器进行加载
这样就避免了我们上面的两个应用引用同样的全路径类但是版本不一致的问题