一、Tomcat 类加载机制剖析
- Java类(.java)—>字节码文件(.class) —> 字节码文件需要被加载到jvm内存当中(这个过程就是⼀个类加载的过程)
- 类加载器(ClassLoader,说白了也是⼀个类,jvm启动的时候先把类加载器读取到内存当中去,其他的类(比如各种jar中的字节码文件,自己开发的代码编译之后的.class文件等等))
- 要说 Tomcat 的类加载机制,首先需要来看看 Jvm 的类加载机制,因为 Tomcat 类加载机制是在 Jvm 类加载机制基础之上进行了⼀些变动。
第 1 节 JVM 的类加载机制
JVM 的类加载机制中有⼀个非常重要的角色叫做类加载器(ClassLoader),类加载器有自己的体系,JVM内置了几种类加载器,包括:引导类加载器、扩展类加载器、系统类加载器,他们之间形成父子关系,通过 Parent 属性来定义这种关系,最终可以形成树形结构。
另外:用户可以自定义类加载器(Java编写,用户自定义的类加载器,可加载指定路径的 class 文件)
当 JVM 运行过程中,用户自定义了类加载器去加载某些类时,会按照下面的步骤(父类委托机制)
- 用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,⼀直到加载器树的顶层
- 最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类
- 如果⼀直到底层的类加载都没有加载到,那么就会抛出异常 ClassNotFoundException
因此,按照这个过程可以想到,如果同样在 classpath 指定的目录中和自己工作目录中存放相同的 class,会优先加载 classpath 目录中的文件。
第 2 节 双亲委派机制
2.1 什么是双亲委派机制
当某个类加载器需要加载某个.class文件时,它首先把这个任务委托给他的上级类加载器,递归这个操作,如果上级的类加载器没有加载,自己才会去加载这个类。
2.2 JDK的双亲委派机制
- 首先由
AppClassLoader
判断是否加载过Hello.class
文件,已经加载过就不在加载,直接返回。 - AppClassLoader未加载过的话,再交由上一级ExtClassLoader进行判断。
ExtClassLoader
进行判断是否加载过Hello.class文件,已经加载过就不在加载,直接返回。- ExtClassLoader未加载过的话,在交由上一级BootstrapClassLoader进行判断。
BootstrapClassLoader
进行判断是否加载过Hello.class文件,已经加载过就不在加载,直接返回。- BootstrapClassLoader未加载过的话,再判断自己是否可以加载该Hello.class,可以加载就自己加载此类,不能加载的话在交由下一级ExtClassLoader判断。
- ExtClassLoader判断自己是否可以加载该Hello.class,可以加载就自己加载此类,不能加载的话在交由下一级AppClassLoader判断。
- AppClassLoader判断自己是否可以加载该Hello.class,可以加载就自己加载此类,不能加载的话就抛出
ClassNotFoundException
异常。
2.3 双亲委派机制的作用
- 防止重复加载同⼀个.class。通过委托去向上面问⼀问,加载过了,就不用再加载⼀遍。保证数据安全。
- 保证核心.class不能被篡改。通过委托方式,不会去篡改核心.class,即使篡改也不会去加载,即使加载也不会是同⼀个.class对象了。不同的加载器加载同⼀个.class也不是同⼀个.class对象。这样保证了class执行安全(如果子类加载器先加载,那么我们可以写⼀些与java.lang包中基础类同名的类, 然后再定义⼀个子类加载器,这样整个应用使用的基础类就都变成我们自己定义的类了。)
Object类 -----> 自定义类加载器(会出现问题的,那么真正的Object类就可能被篡改了)
第 3 节 Tomcat 的类加载机制
3.1 简述
Tomcat 的类加载机制相对于 JVM 的类加载机制做了⼀些改变。没有严格的遵从双亲委派机制,也可以说打破了双亲委派机制
比如:假设有⼀个tomcat,webapps下部署了两个应用
一个应用app1依赖了/lib/a-1.0.jar包,该jar包底下有 com.cyd.Abc类,另一个应用app2依赖了/lib/a-2.0.jar包,该jar包底下有 com.cyd.Abc类,不同版本中Abc类的内容是不同的,代码是不⼀样的,如果遵从双亲委派原则的话,那么只能加载其中一个版本的Abc类(假设是1.0版本的),另一个版本(2.0)的Abc类来加载的时候,会判断成已经加载过此类,直接返回了1.0版本的Abc类。
- 引导类加载器和扩展类加载器的作用不变
- 系统类加载器正常情况下加载的是 CLASSPATH 下的类,但是 Tomcat 的启动脚本并未使用该变量,而是加载tomcat启动的类,比如bootstrap.jar,通常在catalina.bat或者catalina.sh中指定。位于CATALINA_HOME/bin下
- Common 通用类加载器加载Tomcat使用以及应用通用的⼀些类,位于CATALINA_HOME/lib下,比如servlet-api.jar
- Catalina ClassLoader 用于加载服务器内部可见类,这些类应用程序不能访问
- Shared ClassLoader 用于加载应用程序共享类,这些类服务器不会依赖
- Webapp ClassLoader,每个应用程序都会有⼀个独一无二的Webapp ClassLoader,它用来加载本应用程序 /WEB-INF/classes 和 /WEB-INF/lib 下的类。
tomcat 8.5 默认改变了严格的双亲委派机制
3.2 源码角度分析
(1)Tomcat在哪个地方违背双亲委派原则?
在WebAppClassLoader加载classes目录以及lib目录的类库的时候,正常情况下是要先交由上一级SharedClassLoader加载,上一级未能加载才到自己加载,但是这边WebAppClassLoader自己先进行了加载classes目录以及lib目录的类库,自己无法加载才交由上一级SharedClassLoader加载,这样就打破了双亲委派原则。
(2)Tomcat为什么要违背双亲委派原则?
WebAppClassLoader加载classes目录以及lib目录的类库,每一个WebAppClassLoader就对应webapps底下的一个Web项目,那么有一个问题,假设其中一个web项目部署的Spring 4版本的,另一个web项目部署的Spring 5版本,如果都交由父类SharedClassLoader去加载的话,那么就出问题了,一个加载4版本一个加载5版本,最终SharedClassLoader只能加载一个,假设加载的是Spring 4版本,那么依赖Spring 5的项目就无法使用到Spring 5相关的方法、类库了。所以WebAppClassLoader这边就违背了双亲委派原则,WebAppClassLoader自行加载,依次加载WEB-INF\classes,不会委托父类加载。
(3)源码体现
WebAppClassLoader
- 检测WebAppClassLoader的class缓存ResourceEntries(Map集合)中是否有待加载的Class,如果有直接返回。
- 检查JVM是否已经加载过该Class,如果加载过直接返回。
- 让系统类加载器(AppClassLoader)尝试加载该类(主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,否则继续往下走)
- 如果配置了“委派机制”(默认是false,即违背双亲委派原则),先让WebAppClassLoader的 parent ClassLoader,即CommonLoader来加载,此时没有违背双亲委派机制原则。
- WebAppClassLoader自行加载,依次在WEB-INF\class、WEB-INF\lib下搜寻,违背双亲委派机制原则。
- 如果未配置“委派机制”,最后让WebAppClassLoader的parent ClassLoad即CommonLoader来加载。
未配置委派机制时,从第5步和第6步可以看出,tomcat先让WebAppClassLoader进行加载,再让其父类进行加载,这违背了双亲委派原则。
二、Tomcat 对 Https 的支持及 Tomcat 性能优化策略
第 1 节 Tomcat 对 HTTPS 的支持
Https是用来加强数据传输安全的
1.1 HTTPS 简介
HTTP超文本传输协议,明文传输 ,传输不安全,https在传输数据的时候会对数据进行加密
1.2 HTTPS和HTTP的主要区别
- HTTPS协议使用时需要到垫子商务认证授权机构(CA)申请SSL证书
- HTTP默认使用8080端⼝,HTTPS默认使用8443端口
- HTTPS则是具有SSL加密的安全性传输协议,对数据的传输进行加密,效果上相当于HTTP的升级版
- HTTP的连接是无状态的,不安全的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。
1.3 HTTPS工作原理
1.4 Tomcat 对 HTTPS 的支持
1) 使用 JDK 中的 keytool 工具生成免费的秘钥库文件(证书)。
keytool -genkey -alias cyd-keyalg RSA -keystore cyd.keystore
2) 配置conf/server.xml
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true">
<SSLHostConfig>
<Certificate certificateKeystoreFile="F:\ideaWorkerSpace\lagou\tomcat\apache-tomcat-8.5.50-src\source\conf\cyd.keystore"
certificateKeystorePassword="cydcyd"
type="RSA" />
</SSLHostConfig>
</Connector>
3)使用https协议访问8443端口(https://localhost:8443)
第 2 节 Tomcat 性能优化策略
系统性能的衡量指标,主要是响应时间和吞吐量。
- 响应时间:执行某个操作的耗时;
- 吞吐量:系统在给定时间内能够支持的事务数量,单位为TPS(Transactions PerSecond的缩写,也就是事务数/秒,⼀个事务是指⼀个客户机向服务器发送请求然后服务器做出反应的过程。
Tomcat 优化从两个方面进行
- JVM虚拟机优化(优化内存模型)
- Tomcat自身配置的优化(比如是否使用了共享线程池?IO模型?)
学习优化的原则
提供给大家优化思路,没有说有明确的参数值大家直接去使用,必须根据自己的真实生产环境来进行调整,调优是⼀个过程。
2.1 虚拟机运行优化(参数调整)
Java 虚拟机的运行优化主要是内存分配和垃圾回收策略的优化:
- 内存直接影响服务的运行效率和吞吐量
- 垃圾回收机制会不同程度地导致程序运行中断(垃圾回收策略不同,垃圾回收次数和回收效率都是不同的)
2.1.1 Java 虚拟机内存相关参数
JVM内存模型回顾
参数调整示例
JAVA_OPTS="-server -Xms2048m -Xmx2048m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"
调整后查看可使用JDK提供的内存映射工具
2.1.2 垃圾回收(GC)策略
垃圾回收性能指标
- 吞吐量:工作时间(排除GC时间)占总时间的百分比,工作时间并不仅是程序运行的时间,还包含内存分配时间。
- 暂停时间:由垃圾回收导致的应用程序停止响应次数/时间。
垃圾收集器
- 串行收集器(Serial Collector)
单线程执行所有的垃圾回收工作,适用于单核CPU服务器
工作进程-----|(单线程)垃圾回收线程进行垃圾收集|—工作进程继续 - 并行收集器(Parallel Collector)
工作进程-----|(多线程)垃圾回收线程进行垃圾收集|—工作进程继续
又称为吞吐量收集器(关注吞吐量),以并行的方式执行年轻代的垃圾回收, 该方式可以显著降低垃圾回收的开销(指多条垃圾收集线程并行工作,但此时用户线程仍然处于等待状态)。适用于多处理器或多线程硬件上运行的数据量较大的应用。 - 并发收集器(Concurrent Collector)
以并发的方式执行大部分垃圾回收工作,以缩短垃圾回收的暂停时间。适用于那些响应时间优先于吞吐量的应用,因为该收集器虽然最小化了暂停时间(指用户线程与垃圾收集线程同时执行,但不⼀定是并行的,可能会交替进行), 但是会降低应用程序的性能。 - CMS收集器(Concurrent Mark Sweep Collector)
并发标记清除收集器, 适用于那些更愿意缩短垃圾回收暂停时间并且负担的起与垃圾回收共享处理器资源的应⽤ - G1收集器(Garbage-First Garbage Collector)
适用于大容量内存的多核服务器, 可以在满足垃圾回收暂停时间目标的同时, 以最大可能性实现高吞吐量(JDK1.7之后)
垃圾回收器参数
在bin/catalina.bat的脚本中 , 追加如下配置 :
JAVA_OPTS="-XX:+UseConcMarkSweepGC"
2.2 Tomcat 配置调优
Tomcat自身相关的调优
- 调整tomcat线程池
- 调整tomcat的连接器
调整tomcat/conf/server.xml 中关于连接器的配置可以提升应用服务器的性能。
- 禁用 AJP 连接器
- 调整 IO 模式
Tomcat8之前的版本默认使⽤BIO(阻塞式IO),对于每⼀个请求都要创建⼀个线程来处理,不适合高并发;Tomcat8以后的版本默认使用NIO模式(非阻塞式IO)
当Tomcat并发性能有较高要求或者出现瓶颈时,我们可以尝试使用APR模式,APR(Apache PortableRuntime)是从操作系统级别解决异步IO问题,使用时需要在操作系统上安装APR和Native(因为APR原理是使用JNI技术调用操作系统底层的IO接口) - 动静分离
可以使用Nginx+Tomcat相结合的部署方案,Nginx负责静态资源访问,Tomcat负责Jsp等动态资
源访问处理(因为Tomcat不擅长处理静态资源)。