tomcat 是我们常用的 web 容器, 它的性能高低直接影响到应用对外提供服务的能力和用户的体验, 所以 tomcat 的优化至关重要. 对于单台 tomcat 服务器而言, 优化主要是两方面: 内存优化和配置优化(例如, 连接数, 线程池, iO 等), 当然还有使用 tomcat 原生库. 除了这些 tomcat 本身固有配置优化, 还有一些减少占用 tomcat 连接时间的配置, 比如数据压缩. 如果单个服务器经过优化后依然无法满足, 性能要求, 这时服务器集群就是必须的手段了, 下面针对这些方面的优化详细介绍.
内存优化
主要是针对 jvm 各个内存大小的分配进行优化, 以提高内存的使用率和减少资源变化带来的时间消耗. 有关文件: catalina.sh(Linux 下)或者 catalina.bat(Windows 下).
注意: 对于 32 位操作系统上对 jvm 的内存有不能超过 2G 的限制, 但是 64 位系统上没有这个限制. 但很多情况下 32 操作系统上 jvm 的最大使用内存是不到 2G 的, 2G 只是个理想值, 根据服务器的配置不同, 可能有些只能用 1500M 或者 1700M. 具体支持多少, 可以在控制台输入: java -Xmx2089M -version, 如果出现 Java 版本, 说明你的 jvm 内存可以设置到 2089M.
Linux 系统中 tomcat 的启动参数:
export JAVA_OPTS="-server -Xms1400M -Xmx1400M -Xss512k -XX:+AggressiveOpts -XX:+UseBiasedLocking -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=31 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true"
Windows 系统中 tomcat 的启动参数:
- set JAVA_OPTS=-server -Xms1400M -Xmx1400M -Xss512k -XX:+AggressiveOpts -XX:+UseBiasedLocking -XX:PermSize=128M -XX:MaxPermSize=256M -XX:+DisableExplicitGC -XX:MaxTenuringThreshold=31 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -Djava.awt.headless=true
- -server
这个参数必须加上, 因为 tomcat 默认是以一种叫 java -client 的模式来运行的, server 即意味着你的 tomcat 是以真实的 production 的模式在运行的, 这也就意味着你的 tomcat 以 server 模式运行时将拥有: 更大, 更高的并发处理能力, 更快更强捷的 JVM 垃圾回收机制等.
-Xms 和 - Xmx
Xms 是 JVM 初始化时的内存大小; Xmx 是 jvm 的最大内存大小. Xms 默认是物理内存的 1/64,Xmx 默认是物理内存的 1/4, 建议是物理内存的 80%, 但是一般我们会把 Xms 和 Xmx 设置一样大, 这样在服务器启动时就是最大值可以避免内存膨胀和回落时带来的资源的消耗. 特别是回落时会带来 CPU 的高速运转进行垃圾回收, 系统可能会出现几秒甚至几十秒的卡顿现象.
-Xmn
Xmn 是年轻代大小. 整个堆大小 = 年轻代大小 + 年老代大小 + 持久代大小. 持久代一般固定大小为 64m, 所以增大年轻代后, 将会减小年老代大小. 此值对系统性能影响较大, Sun 官方推荐配置为整个堆的 3/8.
-Xss
是指设定每个线程的堆栈大小. 这个就要依据你的程序, 看一个线程 大约需要占用多少内存, 可能会有多少线程同时运行等. 一般不易设置超过 1M, 要不然容易出现 out ofmemory.
-XX:+AggressiveOpts
作用如其名(aggressive), 启用这个参数, 则每当 JDK 版本升级时, 你的 JVM 都会使用最新加入的优化技术(如果有的话)
-XX:+UseBiasedLocking
启用一个优化了的线程锁, 我们知道在我们的 appserver, 每个 http 请求就是一个线程, 有的请求短有的请求长, 就会有请求排队的现象, 甚至还会出现线程阻塞, 这个优化了的线程锁使得你的 appserver 内对线程处理自动进行最优调配.
-XX:PermSize=128M 和 - XX:MaxPermSize=256M
JVM 使用 - XX:PermSize 设置非堆内存初始值, 默认是物理内存的 1/64; 在数据量的很大的文件导出时, 一定要把这两个值设置上, 否则会出现内存溢出的错误. 由 XX:MaxPermSize 设置最大非堆内存的大小, 默认是物理内存的 1/4. 那么, 如果是物理内存 4GB, 那么 64 分之一就是 64MB, 这就是 PermSize 默认值, 也就是永生代内存初始大小; 四分之一是 1024MB, 这就是 MaxPermSize 默认大小.
-XX:+DisableExplicitGC
在程序代码中不允许有显示的调用 "System.gc()". 看到过有两个极品工程中每次在 DAO 操作结束时手动调用 System.gc()一下, 觉得这样做好像能够解决它们的 out ofmemory 问题一样, 付出的代价就是系统响应时间严重降低, 就和我在关于 Xms,Xmx 里的解释的原理一样, 这样去调用 GC 导致系统的 JVM 大起大落, 性能不到什么地方去哟!
-XX:+UseParNewGC
对年轻代采用多线程并行回收, 这样收得快.
-XX:+UseConcMarkSweepGC
即 CMS gc, 这一特性只有 jdk1.5 即后续版本才具有的功能, 它使用的是 gc 估算触发和 heap 占用触发.
我们知道频频繁的 GC 会造面 JVM 的大起大落从而影响到系统的效率, 因此使用了 CMS GC 后可以在 GC 次数增多的情况下, 每次 GC 的响应时间却很短, 比如说使用了 CMS GC 后经过 jprofiler 的观察, GC 被触发次数非常多, 而每次 GC 耗时仅为几毫秒.
-XX:MaxTenuringThreshold
设置垃圾最大年龄. 如果设置为 0 的话, 则年轻代对象不经过 Survivor 区, 直接进入年老代. 对于年老代比较多的应用, 可以提高效率. 如果将此值设置为一个较大值, 则年轻代对象会在 Survivor 区进行多次复制, 这样可以增加对象再年轻代的存活时间, 增加在年轻代即被回收的概率.
这个值的设置是根据本地的 jprofiler 监控后得到的一个理想的值, 不能一概而论原搬照抄.
-XX:+CMSParallelRemarkEnabled
在使用 UseParNewGC 的情况下, 尽量减少 mark 的时间
-XX:+UseCMSCompactAtFullCollection
在使用 concurrent gc 的情况下, 防止 memoryfragmention, 对 live object 进行整理, 使 memory 碎片减少.
-XX:LargePageSizeInBytes
指定 Java heap 的分页页面大小
-XX:+UseFastAccessorMethods
get,set 方法转成本地代码
-XX:+UseCMSInitiatingOccupancyOnly
指示只有在 oldgeneration 在使用了初始化的比例后 concurrent collector 启动收集
-XX:CMSInitiatingOccupancyFraction=70
CMSInitiatingOccupancyFraction, 这个参数设置有很大技巧, 基本上满足 (Xmx-Xmn)*(100- CMSInitiatingOccupancyFraction)/100>=Xmn 就不会出现 promotion failed. 在我的应用中 Xmx 是 6000,Xmn 是 512, 那么 Xmx-Xmn 是 5488 兆, 也就是年老代有 5488 兆, CMSInitiatingOccupancyFraction=90 说明年老代到 90% 满的时候开始执行对年老代的并发垃圾回收(CMS), 这时还 剩 10% 的空间是 5488*10%=548 兆, 所以即使 Xmn(也就是年轻代共 512 兆) 里所有对象都搬到年老代里, 548 兆的空间也足够了, 所以只要满 足上面的公式, 就不会出现垃圾回收时的 promotion failed;
因此这个参数的设置必须与 Xmn 关联在一起.
-Djava.awt.headless=true
这个参数一般我们都是放在最后使用的, 这全参数的作用是这样的, 有时我们会在我们的 J2EE 工程中使用一些图表工具如: jfreechart, 用于在 Web 网页输出 GIF/JPG 等流, 在 winodws 环境下, 一般我们的 App server 在输出图形时不会碰到什么问题, 但是在 Linux/unix 环境下经常会碰到一个 exception 导致你在 winodws 开发环境下图片显示的好好可是在 Linux/unix 下却显示不出来, 因此加上这个参数以免避这样的情况出现.
配置优化
主要是优化 conf/server.xml 文件中相关配置参数.
连接器
这里主要是优化 connector 标签中的相关参数, 用于处理访问的请求; 当然此标签中也可以设置线程的相关参数, 但是为了更好的性能一般我们使用外部的线程池, 这样有利于线程的复用, 以减少线程的创建和销毁带来的资源消耗; 另外, 我们也可以修改 tomcat 的运行模式: BIO,NIO,AIO(NIO2),APR.
首先, 我们需要禁用 AJP 协议
AJP 连接器:
用于将 Apache 与 Tomcat 集成在一起, 当 Apache 接收到动态内容请求时, 通过在配置中指定的端口号将请求发送给在此端口号上监听的 AJP 连接器组件.
属性:
backlog: 当所有可能的请求处理线程都在使用时, 队列中排队的请求最大数目. 默认为 10, 当队列已满, 任何请求都将被拒绝
maxSpareThread: 允许存在空闲线程的最大数目, 默认值为 50
maxThread: 最大线程数, 默认值为 200
minSpareThreads: 设当连接器第一次启动时创建线程的数目, 确保至少有这么多的空闲线程可用, 默认值为 4
port: 服务端套接字的 TCP 端口号, 默认值为 8089(必须)
topNoDelay: 为 true 时, 可以提高性能, 默认值为 true
soTimeout: 超时值
例:
- <!-Define an AJP1.3 Connector on port 8089-->
- <Connector port="8089" enableLookups="false" redirectPort="8443" protocol="AJP/1.3"
- />
AJPv13 协议是面向包的. Web 服务器和 Servlet 容器通过 TCP 连接来交互; 为了节省 SOCKET 创建的昂贵代价, Web 服务器会尝试维护一个永久 TCP 连接到 servlet 容器, 并且在多个请求和响应周期过程会重用连接. 我们一般是使用 Nginx+tomcat 的架构, 所以用不着 AJP 协议, 所以把 AJP 连接器禁用.
2.Connector 标签常见参数的配置:
- <Connector port="8080" protocol="HTTP/1.1"
- // 编码格式
- URIEncoding="UTF-8"
- // 线程配置
- maxThreads="1000" maxProcessors="1000" minProcessors="5" minSpareThreads="100" maxSpareThreads="1000
- // 连接数配置
- maxConnections="1000"
- connectionTimeout="20000"
- acceptCount="1000"
- // 是否关闭 DNS 的反响查询 false: 关闭, true: 开启
- enableLookups="false"
- // 是否保持长时间连接, false: 关闭, ture: 开启
- disableUploadTimeout="true"
- // 是否开启对 url 进行校验的配置 ,false: 关闭, true: 开启
- useURIValidationHack="false"
- // 启用压缩的配置
- compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/javascript,text/CSS,text/plain"
- //post 请求提交最大大小
- maxPostSize="10485760"
- acceptorThreadCount="8"
- redirectPort="8443" />
port: 代表 Tomcat 监听端口, 也就是网站的访问端口, 默认为 8080, 可以根据需要改成其他.
protocol: 协议类型, 可选类型有四种, 分别为 BIO(阻塞型 IO),NIO,NIO2 和 APR.
(1)BIO:BIO(Blocking I/O), 顾名思义, 即阻塞式 I/O 操作, 表示 Tomcat 使用的是传统的 Java I/O 操作(即 java.io 包及其子包).Tomcat 在默认情况下, 是以 bio 模式运行的. 遗憾的是, 就一般而言, bio 模式是三种运行模式中性能最低的一种. BIO 配置采用默认即可.
(2)NIO:NIO(New I/O), 是 Java SE 1.4 及后续版本提供的一种新的 I/O 操作方式 (即 java.nio 包及其子包).Java nio 是一个基于缓冲区, 并能提供非阻塞 I/O 操作的 java API, 因此 nio 也被看成是 non- blocking I/O 的缩写. 它拥有比传统 I/O 操作(bio) 更好的并发运行性能. 要让 Tomcat 以 nio 模式来运行也比较简单, 我们只需要 protocol 类型修改为:
//NIO protocol="org.apache.coyote.http11.Http11NioProtocol" //NIO2 protocol="org.apache.coyote.http11.Http11Nio2Protocol"
即可.
(3)APR:APR(Apache Portable Runtime/Apache 可移植运行时), 是 Apache HTTP 服务器的支持库. 你可以简单地理解为: Tomcat 将以 JNI 的形式调用 Apache HTTP 服务器的核心动态链接库来处理文件读取或 网络传输操作, 从而大大地提高 Tomcat 对静态文件的处理性能.
与配置 NIO 运行模式一样, 也需要将对应的 Connector 节点的 protocol 属性值改为: protocol="org.apache.coyote.http11.Http11AprProtocol"
相关 APR 介绍及配置会在下面专门讲.
maxThreads: 由该连接器创建的处理请求线程的最大数目, 也就是可以处理的同时请求的最大数目. 如果未配置默认值为 200. 如果一个执行器与此连接器关联, 则忽略此属性, 因为该属性将被忽略, 所以该连接器将使用执行器而不是一个内部线程池来执行任务. 但是这个值的是指和 jvm 内存和为每个线程所分配的内存大小有关
minSpareThreads:tomcat 初始化时创建的线程数, 线程的最小运行数目, 这些始终保持运行. 如果未指定, 默认值为 10.
maxSpareThreads: 一旦创建的线程超过这个值, Tomcat 就会关闭不再需要的 socket 线程.
acceptCount: 当所有可能的请求处理线程都在使用时传入连接请求的最大队列长度. 如果未指定, 默认值为 100. 一般是设置的跟 maxThreads 一样或一半, 此值设置的过大会导致排队的请求超时而未被处理. 所以这个值应该是主要根据应用的访问峰值与平均值来权衡配置.
maxConnections: 在任何给定的时间内, 服务器将接受和处理的最大连接数. 当这个数字已经达到时, 服务器将接受但不处理, 等待进一步连接. NIO 与 NIO2 的默认值为 10000,APR 默认值为 8192.
connectionTimeout: 当请求已经被接受, 但未被处理, 也就是等待中的超时时间. 单位为毫秒, 默认值为 60000. 通常情况下设置为 30000.
maxHttpHeaderSize: 请求和响应的 HTTP 头的最大大小, 以字节为单位指定. 如果没有指定, 这个属性被设置为 8192(8 KB).
tcpNoDelay: 如果为 true, 服务器 socket 会设置 TCP_NO_DELAY 选项, 在大多数情况下可以提高性能. 缺省情况下设为 true.
compression: 是否启用 gzip 压缩, 默认为关闭状态. 这个参数的可接受值为 "off"(不使用压缩),"on"(压缩文本数据),"force"(在所有的情况下强制压缩).
compressionMinSize: 如果 compression="on", 则启用此项. 被压缩前数据的最小值, 也就是超过这个值后才被压缩. 如果没有指定, 这个属性默认为 "2048"(2K), 单位为 byte.
disableUploadTimeout: 这个标志允许 servlet Container 在一个 servlet 执行的时候, 使用一个不同的, 更长的连接超时. 最终的结果是给 servlet 更长的时间以便完成其执行, 或者在数据上载的时候更长的超时时间. 如果没有指定, 设为 false.
enableLookups: 关闭 DNS 反向查询.
URIEncoding:URL 编码字符集.
maxPostSize: 设置 post 提交内容大小. 通过将此属性设置为小于零的值, 可以禁用限制. 如果未指定, 则此属性设置为 2097152(2 兆字节).
acceptorThreadCount: 默认是 1, 一般调成和 CPU 的线程数保持一致, 比如 8 线程的 CPU, 值就调成: 8.
当然 Connector 标签中的参数还有很多, 这是只是常见的参数设置; 并且这些中的参数也不一定都要设置, 需要根据项目的具体情况去设置.
线程池
在介绍 Connector 标签中我们看到已经有线程的相关参数的设置, 为什么还要有线程池? 其实这个和 Java 中的线程池是一个道理, 使用线程池可以使多连接公用线程, 避免线程的频繁的创建和销毁带来的资源消耗.
引入线程池的方法, 使用 Connector 标签中的 executor 的属性, 将其值设置为 Executor 标签的 name 的值. 例如:
<Connector port="8066" executor="tomcatThreadPool" protocol="org.apache.coyote.http11.Http11NioProtocol" connectionTimeout="20000" enableLookups="false" maxPostSize="10485760" URIEncoding="UTF-8" useBodyEncodingForURI="true" acceptCount="100" acceptorThreadCount="2" disableUploadTimeout="true" maxConnections="10000" SSLEnabled="false" /> <Executor name="tomcatThreadPool" namePrefix="catalina-exec-" maxThreads="1000" minSpareThreads="100" maxIdleTime="60000" maxQueueSize="Integer.MAX_VALUE" prestartminSpareThreads="false" threadPriority="5" className="org.apache.catalina.core.StandardThreadExecutor"/> io
压缩
tomcat 原生库
来源: http://www.bubuko.com/infodetail-2979605.html