一总结前一天的学习
从第三天的性能测试一节中, 我们得知了决定性能测试的几个重要指标, 它们是:
吞吐量
- Responsetime
- Cpuload
- MemoryUsage
我们也在第三天的学习中对 Apache 做过了一定的优化, 使其最优化上述 4 大核心指标的读数, 那么我们的 Apache 调优了, 我们的 Tomcat 也作些相应的调整, 当完成今的课程后, 到时你的小猫到时真的会飞起来的, 所以请用心看完, 这篇文章一方面用来向那位曾写过 Tomcat 如何承受 1000 个用户的作都的敬, 一方面又是这篇原文的一个扩展, 因为在把原文的知识用到相关的两个大工程中去后解决了:
承受更大并发用户数
取得了良好的性能与改善(系统平均性能提升达 20 倍, 极端一个交易达 80 倍)
另外值的一提的是, 我们当时工程里用的小猫是跑在 32 位机下的, 也就是我们的 JVM 最大受到 2GB 内存的限制, 都已经跑成飞了如果在 64 位机下跑这头小猫大家可想而知, 会得到什么样的效果呢? 下面就请请详细的设置吧!
二一切基于 JVM(内存)的优化
2.1 32 位操作系统与 64 位操作系统中 JVM 的对比
我们一般的开发人员, 基本用的是都是 32 位的 Windows 系统, 这就导致了一个严重的问题即: 32 位 windows 系统对内存限制, 下面先来看一个比较的表格:
操作系统
|
操作系统位数
|
内存限制
|
解决办法
|
Winxp
|
32
|
4GB
|
超级兔子
|
Win7
|
32
|
4GB
|
可以通过设置 / PAE
|
Win2003
|
32
|
可以突破 4GB 达 16GB
|
必需要装 win2003 advanced server 且要打上 sp2 补丁
|
Win7
|
64
|
无限制
|
机器能插多少内存,系统内存就能支持到多大
|
Win2003
|
64
|
无限制
|
机器能插多少内存,系统内存就能支持到多大
|
Linux
|
64
|
无限制
|
机器能插多少内存,系统内存就能支持到多大
|
Unix
|
64
|
无限制
|
机器能插多少内存,系统内存就能支持到多大
|
上述问题解决后, 我们又碰到一个新的问题, 32 位系统下 JVM 对内存的限制: 不能突破 2GB 内存, 即使你在 Win2003 Advanced Server 下你的机器装有 8GB-16GB 的内存, 而你的 JAVA, 只能用到 2GB 的内存
其实我一直很想推荐大家使用 Linux 或者是 Mac 操作系统的, 而且要装 64 位, 因为必竟我们是开发用的不是打游戏用的, 而 Java 源自 Unix 归于 Unix(Linux 只是运行在 PC 上的 Unix 而己)
所以很多开发人员运行在 win32 位系统上更有甚者在生产环境下都会布署 win32 位的系统, 那么这时你的 Tomcat 要优化, 就要讲究点技巧了而在 64 位操作系统上无论是系统内存还是 JVM 都没有受到 2GB 这样的限制
Tomcat 的优化分成两块:
Tomcat 启动命令行中的优化参数即 JVM 优化
Tomcat 容器自身参数的优化(这块很像 ApacheHttp Server)
这一节先要讲的是 Tomcat 启动命令行中的优化参数
Tomcat 首先跑在 JVM 之上的, 因为它的启动其实也只是一个 java 命令行, 首先我们需要对这个 JAVA 的启动命令行进行调优
需要注意的是:
这边讨论的 JVM 优化是基于 Oracle Sun 的 jdk1.6 版有以上, 其它 JDK 或者低版本 JDK 不适用
2.2 Tomcat 启动行参数的优化
Tomcat 的启动参数位于 tomcat 的安装目录 \ bin 目录下, 如果你是 Linux 操作系统就是 catalina.sh 文件, 如果你是 Windows 操作系统那么你需要改动的就是 catalina.bat 文件打开该文件, 一般该文件头部是一堆的由 ## 包裹着的注释文字, 找到注释文字的最后一段如:
- # $Id: catalina.sh 522797 2007-03-27 07:10:29Z fhanik $
- # -----------------------------------------------------------------------------
- # OS specific support. $var _must_ be set to either true or false.
敲入一个回车, 加入如下的参数
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
上面参数好多啊, 可能有人写到现在都没见一个 tomcat 的启动命令里加了这么多参数, 当然, 这些参数只是我机器上的, 不一定适合你, 尤其是参数后的 value(值)是需要根据你自己的实际情况来设置的
参数解释:
-server
我不管你什么理由, 只要你的 tomcat 是运行在生产环境中的, 这个参数必须给我加上
因为 tomcat 默认是以一种叫 java client 的模式来运行的, server 即意味着你的 tomcat 是以真实的 production 的模式在运行的, 这也就意味着你的 tomcat 以 server 模式运行时将拥有: 更大更高的并发处理能力, 更快更强捷的 JVM 垃圾回收机制, 可以获得更多的负载与吞吐量更还有更
Y 给我记住啊, 要不然这个 - server 都不加, 那是要打屁股了
-XmsXmx
即 JVM 内存设置了, 把 Xms 与 Xmx 两个值设成一样是最优的做法, 有人说 Xms 为最小值, Xmx 为最大值不是挺好的, 这样设置还比较人性化, 科学化人性? 科学? 你个头啊
大家想一下这样的场景:
一个系统随着并发数越来越高, 它的内存使用情况逐步上升, 上升到最高点不能上升了, 开始回落, 你们不要认为这个回落就是好事情, 由其是大起大落, 在内存回落时它付出的代价是 CPU 高速开始运转进行垃圾回收, 此时严重的甚至会造成你的系统出现卡壳就是你在好好的操作, 突然网页像死在那边一样几秒甚至十几秒时间, 因为 JVM 正在进行垃圾回收
因此一开始我们就把这两个设成一样, 使得 Tomcat 在启动时就为最大化参数充分利用系统的效率, 这个道理和 jdbcconnection pool 里的 minpool size 与 maxpool size 的需要设成一个数量是一样的原理
如何知道我的 JVM 能够使用最大值啊? 拍脑袋? 不行!
在设这个最大内存即 Xmx 值时请先打开一个命令行, 键入如下的命令:
看, 能够正常显示 JDK 的版本信息, 说明, 这个值你能够用不是说 32 位系统下最高能够使用 2GB 内存吗? 即: 2048m, 我们不防来试试
可以吗? 不可以! 不要说 2048m 呢, 我们小一点, 试试 1700m 如何
嘿嘿, 连 1700m 都不可以, 更不要说 2048m 了呢, 2048m 只是一个理论数值, 这样说吧我这边有几台机器, 有的机器 - Xmx1800 都没问题, 有的机器最高只能到 - Xmx1500m
因此在设这个 - Xms 与 - Xmx 值时一定一定记得先这样测试一下, 要不然直接加在 tomcat 启动命令行中你的 tomcat 就再也起不来了, 要飞是飞不了, 直接成了一只瘟猫了
Xmn
设置年轻代大小为 512m 整个堆大小 = 年轻代大小 + 年老代大小 + 持久代大小持久代一般固定大小为 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 下却显示不出来, 因此加上这个参数以免避这样的情况出现
上述这样的配置, 基本上可以达到:
系统响应时间增快
JVM 回收速度增快同时又不影响系统的响应率
JVM 内存最大化利用
线程阻塞情况最小化
2.3 Tomcat 容器内的优化
前面我们对 Tomcat 启动时的命令进行了优化, 增加了系统的 JVM 可使用数垃圾回收效率与线程阻塞情况增加了系统响应效率等还有一个很重要的指标, 我们没有去做优化, 就是吞吐量
还记得我们在第三天的学习中说的, 这个系统本身可以处理 1000, 你没有优化和配置导致它默认只能处理 25 因此下面我们来看 Tomcat 容器内的优化
打开 tomcat 安装目录 \ conf\server.xml 文件, 定位到这一行:
<Connector port=8080 protocol=HTTP/1.1
这一行就是我们的 tomcat 容器性能参数设置的地方, 它一般都会有一个默认值, 这些默认值是远远不够我们的使用的, 我们来看经过更改后的这一段的配置:
- <Connector port="8080" protocol="HTTP/1.1"
- URIEncoding="UTF-8" minSpareThreads="25" maxSpareThreads="75"
- enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000"
- acceptCount="300" maxThreads="300" maxProcessors="1000" minProcessors="5"
- useURIValidationHack="false"
- compression="on" compressionMinSize="2048"
- compressableMimeType="text/html,text/xml,text/javascript,text/CSS,text/plain"
- redirectPort="8443"
- />
好大一陀唉
没关系, 一个个来解释
URIEncoding=UTF-8
使得 tomcat 可以解析含有中文名的文件的 url, 真方便, 不像 apache 里还有搞个 mod_encoding, 还要手工编译
maxSpareThreads
maxSpareThreads 的意思就是如果空闲状态的线程数多于设置的数目, 则将这些线程中止, 减少这个池中的线程总数
minSpareThreads
最小备用线程数, tomcat 启动时的初始化的线程数
enableLookups
这个功效和 Apache 中的 HostnameLookups 一样, 设为关闭
connectionTimeout
connectionTimeout 为网络连接超时时间毫秒数
maxThreads
maxThreads Tomcat 使用线程来处理接收的每个请求这个值表示 Tomcat 可创建的最大的线程数, 即最大并发数
acceptCount
acceptCount 是当线程数达到 maxThreads 后, 后续请求会被放入一个等待队列, 这个 acceptCount 是这个队列的大小, 如果这个队列也满了, 就直接 refuse connection
maxProcessors 与 minProcessors
在 Java 中线程是程序运行时的路径, 是在一个程序中与其它控制线程无关的能够独立运行的代码段它们共享相同的地址空间多线程帮助程序员写出 CPU 最 大利用率的高效程序, 使空闲时间保持最低, 从而接受更多的请求
通常 Windows 是 1000 个左右, Linux 是 2000 个左右
useURIValidationHack
我们来看一下 tomcat 中的一段源码:
- security
- if (connector.getUseURIValidationHack()) {
- String uri = validate(request.getRequestURI());
- if (uri == null) {
- res.setStatus(400);
- res.setMessage("Invalid URI");
- throw new IOException("Invalid URI");
- } else {
- req.requestURI().setString(uri);
- // Redoing the URI decoding
- req.decodedURI().duplicate(req.requestURI());
- req.getURLDecoder().convert(req.decodedURI(), true);
- }
- }
可以看到如果把 useURIValidationHack 设成 false, 可以减少它对一些 url 的不必要的检查从而减省开销
enableLookups=false
为了消除 DNS 查询对性能的影响我们可以关闭 DNS 查询, 方式是修改 server.xml 文件中的 enableLookups 参数值
disableUploadTimeout
类似于 Apache 中的 keeyalive 一样
给 Tomcat 配置 gzip 压缩 (HTTP 压缩) 功能
- compression="on" compressionMinSize="2048"
- compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
HTTP 压缩可以大大提高浏览网站的速度, 它的原理是, 在客户端请求网页后, 从服务器端将网页文件压缩, 再下载到客户端, 由客户端的浏览器负责解压缩并浏览相对于普通的浏览过程 HTML,CSS,Javascript , Text , 它可以节省 40% 左右的流量更为重要的是, 它可以对动态生成的, 包括 CGIPHP , JSP , ASP , Servlet,SHTML 等输出的网页也能进行压缩, 压缩效率惊人
1)compression=on 打开压缩功能
2)compressionMinSize=2048 启用压缩的输出内容大小, 这里面默认为 2KB
3)noCompressionUserAgents=gozilla, traviata 对于以下的浏览器, 不启用压缩
4)compressableMimeType=text/html,text/xml 压缩类型
最后不要忘了把 8443 端口的地方也加上同样的配置, 因为如果我们走 https 协议的话, 我们将会用到 8443 端口这个段的配置, 对吧?
- <!--enable tomcat ssl-->
- <Connector port="8443" protocol="HTTP/1.1"
- URIEncoding="UTF-8" minSpareThreads="25" maxSpareThreads="75"
- enableLookups="false" disableUploadTimeout="true" connectionTimeout="20000"
- acceptCount="300" maxThreads="300" maxProcessors="1000" minProcessors="5"
- useURIValidationHack="false"
- compression="on" compressionMinSize="2048"
- compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
- SSLEnabled="true"
- scheme="https" secure="true"
- clientAuth="false" sslProtocol="TLS"
- keystoreFile="d:/tomcat2/conf/shnlap93.jks" keystorePass="aaaaaa"
- />
举个真实的例子: 上一个项目, 经过 4 轮 performance testing, 第一轮进行了问题的定位, 第二轮就是进行了 apache+tomcat/weblogic 的优化, 第三轮是做集群优化, 第四轮是 sql 与 codes 的优化好了, 所有的 Tomcat 优化的地方都加上了结合第三天中的 Apache 的性能优化, 我们这个架构可以飞奔起来了, 当然这边把有提及任何关于数据库优化的步骤, 但仅凭这两步, 我们的系统已经有了很大的提升
在到达第二轮时, 我们的性能已经提升了多少倍呢? 我们来看一个 loaderrunner 的截图吧:
左边第一列是第一轮没有经过任何调优的压力测试报告
右边这一列是经过了 apache 优化, tomcat 优化后得到的压力测试报告
大家看看, 这就提高了多少倍? 这还只是在没有改动代码的情况下得到的改善, 现在明白了好好的调优一
个 apache 和 tomcat 其实是多么的重要了? 如果加上后面的代码 SQL 的调优数据库的调优所以我在上一个工程中有单笔交易性能 (无论是吞吐量响应时间) 提高了 80 倍这样的极端例子的存在
来源: https://juejin.im/entry/5aaa441cf265da238c3a679e