Tomcat - 都说Tomcat违背了双亲委派机制,到底对不对?

简介: Tomcat - 都说Tomcat违背了双亲委派机制,到底对不对?

20200604001310762.png

类加载的本质


ClassLoader是用来加载 Class 的。它负责将 Class 的字节码形式转换成内存形式的 Class 对象。


字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,字节码的本质就是一个字节数组 []byte,它有特定的复杂的内部格式。


JVM 运行实例中会存在多个 ClassLoader,不同的 ClassLoader 会从不同的地方加载字节码文件。


它可以从不同的文件目录加载,也可以从不同的 jar 文件中加载,也可以从网络上不同的静态文件服务器来下载字节码再加载。


JVM 双亲委派机制



20200604001435946.png



Java1.2之后引入双亲委派模式 。


核心原理: 如果其中一个类加载器收到了类加载的请求,它并不会自己去加载而是会将该请求委托给父类的加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,如此递归,请求最终到达顶层的启动类加载器。


如果父类能加载,则直接返回,如果父类加载不了则交由子类加载,这就是双亲委派模式。



20200604091811339.png


好处

  • 向上委托给父类加载,父类加载不了再自己加载
  • 避免重复加载,防止Java核心api被篡改


BootstrapClassLoader(启动类加载器)


负责加载 JVM 运行时核心类,加载System.getProperty("sun.boot.class.path")所指定的路径或jar


ExtensionClassLoader


负责加载 JVM 扩展类,比如 swing 系列、内置的 js 引擎、xml 解析器 等等,这些库名通常以 javax 开头,它们的 jar 包位于 JAVAHOME/lib/rt.jar文件中.

加载System.getProperty("java.ext.dirs")所指定的路径或jar。


在使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。


AppClassLoader


AppClassLoader才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录。


我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。


加载System.getProperty("java.class.path")所指定的路径或jar。


在使用Java运行程序时,也可以加上-cp来覆盖原有的Classpath设置,例如: java -cp ./lavasoft/classes HelloWorld


Tomcat的 类加载顺序



20200604001833717.png



在Tomcat中,默认的行为是先尝试在Bootstrap和Extension中进行类型加载,如果加载不到则在WebappClassLoader中进行加载,如果还是找不到则在Common中进行查找 .


Common是公共的包, tomcat可以外挂很多个webapps (tomcat和app分开部署) ,优先以webapps下的为主。


tomcat7 —> 默认 WebappClassLoader 类加载器

tomcat 8 ---->默认 ParallelWebappClassLoader 类加载器


Tomcat要解决什么问题?


作为一个Web容器,Tomcat要解决什么问题 , Tomcat 如果使用默认的双亲委派类加载机制能不能行?


我们知道Tomcat可以部署多个应用,不同的应用程序可能会依赖同一个第三方类库的不同版本,不能要求同一个类库在同一个服务器只有一份,因此要保证每个应用程序的类库都是独立的,保证相互隔离 .


举个例子 假设APP1 使用的是 Spring4 , APP2 使用的是Spring5 , 毫无疑问 Spring4 和 Spring 5 肯定有 类的全路径一样的类吧,如果使用双亲委派 ,父加载器加载谁?


部署在同一个web容器中相同的类库相同的版本可以共享, 比如jdk的核心jar包,否则,如果服务器有n个应用程序,那么要有n份相同的类库加载进虚拟机。


web容器 自己依赖的类库 (tomcat lib目录下),不能与应用程序的类库混淆。基于安全考虑,应该让容器的类库和程序的类库隔离开来。


2020061300050537.png



web容器要支持jsp的修改, jsp 文件最终也是要编译成class文件才能在虚拟机中运行, web容器需要支持 jsp 修改后不用重启 ,就是热加载的功能。


结合上面的4个问题,我们看下双亲委派能不能支持?


第一个问题,如果使用默认的类加载器机制,肯定是无法加载两个相同类库的不同版本的,如果使用双亲委派,让父加载器去加载 ,不管你是什么版本的,只要你的全限定类名一样,那肯定只有一份,APP 隔离 无法满足


第二个问题,默认的类加载器是能够实现的,很好理解嘛, 就是双亲委派的功能,保证唯一性。


第三个问题和第一个问题一样。


第四个问题, 要怎么实现jsp文件的热加载呢? jsp 文件其实也就是class文件,那么如果修改了,但类名还是一样,类加载器会直接取方法区中已经存在的,修改后的jsp是不会重新加载的。那么怎么办呢?可以直接卸载掉这jsp文件的类加载器 .当一个jsp文件修改了,就直接卸载这个jsp类加载器。重新创建类加载器,重新加载jsp文件。 源码详见: org.apache.jasper.servlet.JasperLoader


Tomcat违反了双亲委派机制?


也不尽然,核心的Java的加载还是遵从双亲委派 。


Tomcat中 各个web应用自己的类加载器(WebAppClassLoader)会优先加载,打破了双亲委派机制。加载不到时再交给commonClassLoader走双亲委托 .


原因有二


  • 为了避免类冲突,每个 webapp 项目中各自使用的类库要有隔离机制
  • 不同 webapp 项目支持共享某些类库

Tomcat加载机制小结


20200604090610632.png



当tomcat启动时,会创建几种类加载器:


Bootstrap 引导类加载器 : 加载JVM启动所需的类,以及标准扩展类(位于jre/lib/ext下)


System 系统类加载器 : 加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下



20200613093125911.png


4. webapp 应用类加载器: 每个应用在部署后,都会创建一个唯一的类加载器。该类加载器会加载位于 WEB-INF/lib下的jar文件中的class 和 WEB-INF/classes下的class文件。


Common 通用类加载器:加载tomcat使用以及应用通用的一些类,位于CATALINA_HOME/lib下,比如servlet-api.jar

20200613093211921.png


总之 当应用需要到某个类时,则会按照下面的顺序进行类加载:


1 使用bootstrap引导类加载器加载 (JVM 的东西 )


2 使用system系统类加载器加载 (tomcat的启动类Bootstrap包)


3 使用WebAppClassLoader 加载 WEB-INF/classes (应用自定义的class)


4 使用WebAppClassLoader 加载在WEB-INF/lib (应用的依赖包)


5 使用common类加载器在CATALINA_HOME/lib中加载 (tomcat的依赖包,公共的,被各个应用共享的)


Tomcat 热加载JasperLoader


原理:后台启动线程监听jsp文件变化,如果变化了找到该jsp对应的servlet类的加载器引用(gcroot),重新生成新的JasperLoader加载器赋值给引用,然后加载新的jsp对应的servlet类,之前的那个加载器因为没有gcroot引用了,下一次gc的时候会被销毁。


常见错误


NoClassDefFoundError


NoClassDefFoundError : 由于JVM或者类加载器实例尝试加载类型的定义,但是该定义却没有找到,影响了执行路径。

换句话说,在编译时这个类是能够被找到的,但是在执行时却没有找到。


NoSuchMethodError


NoSuchMethodError代表这个类型确实存在,但是一个不正确的版本被加载了。 检查该类中是否真的有对应的方法


ClassCastException


ClassCastException,在一个类加载器的情况下,一般出现这种错误都会是在转型操作时,比如:A a = (A) method();,很容易判断出来method()方法返回的类型不是类型A,但是在 JavaEE 多个类加载器的环境下可能会出现一些不好去定位的情况。

相关文章
|
6月前
|
安全 Java 应用服务中间件
打破Tomcat中的双亲委派机制:探讨与实践
打破Tomcat中的双亲委派机制:探讨与实践
|
安全 Java 应用服务中间件
【JavaWeb】Tomcat底层机制和Servlet运行原理
网络通信:Tomcat使用Java的Socket API来监听特定的端口(通常是8080),接收来自客户端的HTTP请求。 线程池:Tomcat使用线程池来处理并发的请求。当有新的请求到达时,Tomcat会从线程池中获取一个空闲线程来处理该请求,这样可以提高处理效率。 生命周期管理:Tomcat负责管理Servlet和其他Web组件的生命周期,包括初始化、请求处理和销毁等阶段。(init(), run())
|
缓存 Java 应用服务中间件
Tomcat是如何打破"双亲委派"机制的?
上文我们详细了解了类加载以及什么是双亲委派机制,相信很多童鞋都了解Tomcat打破了双亲委派机制,本文将对Tomcat为什么要打破双亲委派机制,以及Tomcat是如何打破双亲委派机制的,进行完整性的复盘与解析。
3008 0
Tomcat是如何打破"双亲委派"机制的?
|
1月前
|
网络协议 Java 应用服务中间件
深入浅出Tomcat网络通信的高并发处理机制
【10月更文挑战第3天】本文详细解析了Tomcat在处理高并发网络请求时的机制,重点关注了其三种不同的IO模型:NioEndPoint、Nio2EndPoint 和 AprEndPoint。NioEndPoint 采用多路复用模型,通过 Acceptor 接收连接、Poller 监听事件及 Executor 处理请求;Nio2EndPoint 则使用 AIO 异步模型,通过回调函数处理连接和数据就绪事件;AprEndPoint 通过 JNI 调用本地库实现高性能,但已在 Tomcat 10 中弃用
深入浅出Tomcat网络通信的高并发处理机制
|
2月前
|
设计模式 人工智能 安全
【Tomcat源码分析】生命周期机制 Lifecycle
Tomcat内部通过各种组件协同工作,构建了一个复杂的Web服务器架构。其中,`Lifecycle`机制作为核心,管理组件从创建到销毁的整个生命周期。本文详细解析了Lifecycle的工作原理及其方法,如初始化、启动、停止和销毁等关键步骤,并展示了LifecycleBase类如何通过状态机和模板模式实现这一过程。通过深入理解Lifecycle,我们可以更好地掌握组件生命周期管理,提升系统设计能力。欢迎关注【码上遇见你】获取更多信息,或搜索【AI贝塔】体验免费的Chat GPT。希望本章内容对你有所帮助。
|
4月前
|
Java 应用服务中间件 API
开发与运维机制问题之Tomcat要打破双亲委派机制如何解决
开发与运维机制问题之Tomcat要打破双亲委派机制如何解决
40 0
|
6月前
|
Java 应用服务中间件 容器
JavaWeb手写Tomcat底层机制
综上所述,Tomcat作为JavaWeb应用的Servlet容器,在接收请求、解析请求、查找Servlet、创建请求和响应对象、请求分发、生成响应、连接管理等方面起着关键作用。其底层机制通过Socket通信、Servlet生命周期管理、线程池、Session管理等技术实现了整个JavaWeb应用的运行。
43 0
|
6月前
|
缓存 负载均衡 应用服务中间件
【分布式技术专题】「分析Web服务器架构」Tomcat服务器的运行架构和LVS负载均衡的运行机制(修订版)
在本章内容中,我们将深入探讨 Tomcat 服务器的运行架构、LVS 负载均衡的运行机制以及 Cache 缓存机制,并提供相应的解决方案和指导。通过理解这些关键概念和机制,您将能够优化您的系统架构,提高性能和可扩展性。
313 4
【分布式技术专题】「分析Web服务器架构」Tomcat服务器的运行架构和LVS负载均衡的运行机制(修订版)
|
Java 应用服务中间件 Maven
JavaWeb 手写Tomcat底层机制
JavaWeb——手写Tomcat底层 BIO线程模型 + 反射机制。
58 0
|
存储 Java 应用服务中间件