tomcat性能调优
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
张贺,多年互联网行业工作经验,担任过网络工程师、系统集成工程师、LINUX系统运维工程师
个人网站:www.zhanghehe.cn
笔者微信:zhanghe15069028807,现居济南历下区
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
tomcat性能调优
明确一点,tomcat的调优仅是应用层面的调优,网站性能不好的原因有很多,tomcat仅是一方面,此外代码层面、系统层面、硬件层面都要有所考虑,这里面主要要介绍tomcat应用和jvm的调优。
下面所列出的参数并不是通用的,调优最好是根据自己的环境。
tomcat的工作模式
Tomcat有三种工作模式:Bio、Nio和Apr,下面简单了解下他们工作原理:
-
Bio(Blocking I/O):默认工作模式,阻塞式I/O操作,没有任何优化技术处理,性能比较低。
-
Nio(New I/O or Non-Blocking):非阻塞式I/O操作,有Bio有更好的并发处理性能,一般网站使用这个工作模式即可。
-
Apr(Apache Portable Runtime,Apache可移植运行库):首选工作模式,主要为上层的应用程序提供一个可以跨越多操作系统平台使用的底层支持接口库。
tomcat利用基于Apr库tomcat-native来实现操作系统级别控制,提供一种优化技术和非阻塞式I/O操作,大大提高并发处理能力。但是需要安装apr和tomcat-native库。
这里不过多IO,我在这一篇博文里面有详细介绍: https://www.cnblogs.com/yizhangheka/p/12349032.html#io%E6%A8%A1%E5%9E%8B
jvm
java代码的执行仰赖于jvm,jvm的性能是影响网站的最关键因素,我上一篇博文就是专门介绍jvm的,这里不多说: https://www.cnblogs.com/yizhangheka/p/12665768.html
这里对上一篇博文当中的jvm垃圾回收做一个补充,在上篇博文当中我们提到新生代的垃圾回收的算法是小GC,而老年代的垃圾回收是使用的full-gc(大GC),其中大GC的垃圾回收是使用先标记再清除的机制进行工作的。其实还有另外两种垃圾回收的机制。
第一种机制: 标记-清除(Mark-Sweep)
GC分为两个阶段,标记和清除。首先标记所有可回收的对象,在标记完成后统一回收所有被标记的对象。同时会产生不连续的内存碎片。碎片过多会导致以后程序运行时需要分配较大对象时,无法找到足够的连续内存,而不得已再次触发GC。
第二种机制: 复制(Copy)
将内存按容量划分为两块,每次只使用其中一块。当这一块内存用完了,就将存活的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。这样使得每次都是对半个内存区回收,也不用考虑内存碎片问题,简单高效。缺点需要两倍的内存空间。
第三种机制: 标记-整理(Mark-Compact)
也分为两个阶段,首先标记可回收的对象,再将存活的对象都向一端移动,然后清理掉边界以外的内存。此方法避免标记-清除算法的碎片问题,同时也避免了复制算法的空间问题。
一般年轻代中执行GC后,会有少量的对象存活,就会选用复制算法,只要付出少量的存活对象复制成本就可以完成收集。而老年代中因为对象存活率高,没有额外过多内存空间分配,就需要使用标记-清理或者标记-整理算法来进行回收。
垃圾收集
在上一篇jvm当中有提及到当垃圾回收的时候 必须暂停应用的工作线程,直到收集结束,下面总结一下几种垃圾垃圾器:
1)串行收集器(Serial)
比较老的收集器,单线程。收集时,必须暂停应用的工作线程,直到收集结束。
2)并行收集器(Parallel)
多条垃圾收集线程并行工作,在多核CPU下效率更高,应用线程仍然处于等待状态。
3)CMS收集器(Concurrent Mark Sweep)
CMS收集器是缩短暂停应用时间为目标而设计的,是基于标记-清除算法实现,整个过程分为4个步骤,包括:
- 初始标记(Initial Mark)
- 并发标记(Concurrent Mark)
- 重新标记(Remark)
- 并发清除(Concurrent Sweep)
其中,初始标记、重新标记这两个步骤仍然需要暂停应用线程。初始标记只是标记一下GC Roots能直接关联到的对象,速度很快,并发标记阶段是标记可回收对象,而重新标记阶段则是为了修正并发标记期间因用户程序继续运作导致标记产生变动的那一部分对象的标记记录,这个阶段暂停时间比初始标记阶段稍长一点,但远比并发标记时间段。
由于整个过程中消耗最长的并发标记和并发清除过程收集器线程都可以与用户线程一起工作,所以,CMS收集器内存回收与用户一起并发执行的,大大减少了暂停时间。
4)G1收集器(Garbage First)
G1收集器将堆内存划分多个大小相等的独立区域(Region),并且能预测暂停时间,能预测原因它能避免对整个堆进行全区收集。G1跟踪各个Region里的垃圾堆积价值大小(所获得空间大小以及回收所需时间),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region,从而保证了再有限时间内获得更高的收集效率。
G1收集器工作工程分为4个步骤,包括:
- 初始标记(Initial Mark)
- 并发标记(Concurrent Mark)
- 最终标记(Final Mark)
- 筛选回收(Live Data Counting and Evacuation)
初始标记与CMS一样,标记一下GC Roots能直接关联到的对象。并发标记从GC Root开始标记存活对象,这个阶段耗时比较长,但也可以与应用线程并发执行。而最终标记也是为了修正在并发标记期间因用户程序继续运作而导致标记产生变化的那一部分标记记录。最后在筛选回收阶段对各个Region回收价值和成本进行排序,根据用户所期望的GC暂停时间来执行回收。
note:总结一下,上述列出来的算法由上到下垃圾回收时卡住的时间越来越少,但前两种在回收时虽然会卡住的时间长一些,但回收的非常彻底,而后两种方法卡住的时候虽然会少一些,但回收的并不彻底。
调优思路
1、根据自己服务器的性能增加最大连接数
2、调整工作模式,使用NIO即可
3、根据cpu的占用率启用gzip压缩,如果cpu已经很忙了就不要再压缩,雪上加霜。
4、调整JVM内存大小,也要根据实际情况调整,并不是越大越好
5、作为Web服务器时,用nginx或apache保护,这个没什么好说的,一般不会让tomcat单独工作的。
6、合理选择垃圾回收算法
7、尽量使用较新JDK版本
配置实例:
<Connectorport="8080"protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="1000"
minSpareThreads="100"
maxSpareThreads="200"
acceptCount="900"
disableUploadTimeout="true"
connectionTimeout="20000"
URIEncoding="UTF-8"
enableLookups="false"
redirectPort="8443"
compression="on"
compressionMinSize="1024"
compressableMimeType="text/html,text/xml,text/css,text/javascript"/
参数说明:
org.apache.coyote.http11.Http11NioProtocol:调整工作模式为Nio
maxThreads:最大线程数,默认150。增大值避免队列请求过多,导致响应缓慢。
minSpareThreads:最小空闲线程数。
maxSpareThreads:最大空闲线程数,如果超过这个值,会关闭无用的线程。
acceptCount:当处理请求超过此值时,将后来请求放到队列中等待。
disableUploadTimeout:禁用上传超时时间
connectionTimeout:连接超时,单位毫秒,0代表不限制
URIEncoding:URI地址编码使用UTF-8
enableLookups:关闭dns解析,提高响应时间
compression:启用压缩功能
compressionMinSize:最小压缩大小,单位Byte
compressableMimeType:压缩的文件类型
注意:不是JVM内存设置越大越好,具体还是根据项目对象实际占用内存大小而定,可以通过Java自带的分析工具来查看。如果设置过大,会增加回收时间,从而增加暂停应用时间。
意外
java项目非常吃内存,很有可能会发生 OOM异常,那造成这个结果原因可能是:
1)老年代内存不足:java.lang.OutOfMemoryError:Javaheapspace
2)永久代内存不足:java.lang.OutOfMemoryError:PermGenspace
3)代码bug,占用内存无法及时回收。
前两种情况通过加大内存容量,可以得到解决。如果是代码bug,就要通过jstack、jmap、jstat自带的工具分析问题,定位到相关代码,让开发解决。