本文檔是身邊一些朋友、技術大佬之前分享的一些筆記,記錄了 Tomcat 優(yōu)化方法,筆記較多而且比較雜亂,經過整理、分類我個人覺得大致可以從以下幾個方面優(yōu)化 Tomcat:
- Tomcat 運行模式
- Tomcat 配置優(yōu)化
- JVM 優(yōu)化
- 內核參數優(yōu)化
一、修改 Tomcat 運行模式
Tomcat Connector有三種運行模式:
bio:阻塞 IO bio 是三種運行模式中性能最低第一種。
nio:是一個基于緩沖區(qū),并能提供非阻塞 I/O 操作的 JAVA API 因此 NIO 也成為非阻塞 I/O,比 bio 擁有更好的并發(fā)性能(默認是 nio)。
apr:調用 httpd 核心鏈接庫來讀取或文件傳輸,從而提高 tomcat 對靜態(tài)文件的處理性能。Tomcat APR 模式也是 Tomcat 在高并發(fā)下的首選運行模式。
APR 模式文檔鏈接:http://tomcat.apache.org/tomcat-8.5-doc/apr.html
1.1 安裝 apr 和 apr-util
Apache Portable Runtime 是一個高度可移植的庫,是 Apache HTTP Server 2.x 的核心。APR 有許多用途,包括訪問高級 IO 功能(如sendfile,epoll 和 OpenSSL),操作系統級功能(隨機數生成,系統狀態(tài)等)和本機進程處理(共享內存,NT 管道和 Unix 套接字)。
$ cd /usr/local/src/
$ wget http://mirrors.tuna.tsinghua.edu.cn/apache//apr/apr-1.6.5.tar.gz
$ wget http://mirrors.tuna.tsinghua.edu.cn/apache//apr/apr-util-1.6.1.tar.gz
$ tar xf apr-1.6.5.tar.gz
$ cd apr-1.6.5
$ /configure --prefix=/usr/local/apr
$ make -j 2 && make install
$ cd ../
$ tar xf apr-util-1.6.1.tar.gz
$ cd apr-util-1.6.1
$ ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr/
1.2 安裝 tomcat-native
tomcat-native 不用單獨下載,解壓縮 tomcat 程序包后再 bin/ 目錄下存在該程序的源碼包。
$ tar xf apache-tomcat-8.5.38.tar.gz -C /usr/local/
$ ln -sv /usr/local/apache-tomcat-8.5.38/ /usr/local/tomcat
$ cp /usr/local/tomcat/bin/tomcat-native.tar.gz /usr/local/src/
$ tar xf tomcat-native.tar.gz
$ cd tomcat-native-1.2.21-src/
$ cd native/
$ ./configure --prefix=/usr/local/apr --with-java-home=/usr/local/jdk1.8.0_121/
$ make -j 2 && make install
$ vim /etc/profile
export LD_LIBRARY_PATH=/usr/local/apr/lib ##添加apr path
$ source /etc/profile
1.3 修改 Tomcat 配置文件
1、修改 protocol 值
Tomcat 默認是 "HTTP/1.1" ,如果運行 apr 模式需要把 protocol 值修改成 apr 模式:org.apache.coyote.http11.Http11AprProtocol 參考官方文檔中 protocol 說明:http://tomcat.apache.org/tomcat-8.5-doc/config/http.html#HTTP/1.1_and_HTTP/1.0_Support
$ cd /usr/local/tomcat/conf/
$ vim server.xml
<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol"
connectionTimeout="20000"
redirectPort="8443" />
2、修改 SSLEngine
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off" />
1.4 啟動 Tomcat 驗證
查看 catalina.out 日志中最下面三行

二、Tomcat 配置優(yōu)化
2.1 增大隨機數熵池
你是否遇到過當你執(zhí)行了 ./startup.sh 或者 ./catalina.sh start 后,訪問你的服務會一直轉啊轉啊,可能要幾分鐘才能正常提供服務。
原因:
在apache-tomcat 官方文檔:如何讓 tomcat 啟動更快里面提到了一些啟動時的優(yōu)化項,其中一項是關于隨機數生成時,采用的 "熵源"(entropy source)的策略。提到 tomcat7 的 session id 的生成主要通過 java.security.SecureRandom 生成隨機數來實現,隨機數算法使用的是 ”SHA1PRNG” 文章具體內容見下圖

在 http://wiki.apache.org/tomcat/HowTo/FasterStartUp (Entropy Source 部分)有一段解釋。stackoverflow 上面也有一大批這方面的說明,所以這里就不再多做介紹。
2.1.1 使用偽隨機函數生成器
通過修改 Tomcat 啟動文件 -Djava.security.egd=file:/dev/urandom
通過修改 JRE 中的 java.security 文件 securerandom.source=file:/dev/urand
2.1.2 增大/dev/random的熵池(推薦)
安裝熵池服務 rngd
$ yum -y install rng-tools
$ systemctl start rngd
$ systemctl enable rngd
啟動服務后觀察 cat /proc/sys/kernel/random/entropy_avail 基本在三千左右。
2.2 隱藏 Tomcat 版本號
隱藏 Tomcat 版本號,不在優(yōu)化內容范圍內,出于安全角度建議大家隱藏。
修改前如下:

$ cd /usr/local/tomcat/lib/
$ jar xf catalina.jar
$ cd org/apache/catalina/util/
$ vim ServerInfo.properties
# 修改下面信息
server.info=Apache Tomcat/8.5.38
server.number=8.5.38.0
server.built=Feb 5 2019 11:42:42 UTC
修改后為
server.info=Apache Tomcat
server.number=0.0.0.0
server.built=Feb 5 2019 11:42:42 UTC
將修改后的信息壓縮回 jar 包:
$ cd /usr/local/tomcat/lib
$ jar uvf catalina.jar org/apache/catalina/util/ServerInfo.properties
正在添加: org/apache/catalina/util/ServerInfo.properties(輸入 = 885) (輸出 = 512)(壓縮了 42%)
# 重啟 Tomcat
修改后再查看如下:

2.3 server.xml 配置文件優(yōu)化
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="40000"
maxHttpHeaderSize="32768"
redirectPort="8443"
maxThreads="1000"
minSpareThreads="100"
maxSpareThreads="1000"
acceptorThreadCount="2"
acceptCount="2000"
minProcessors="100"
maxProcessors="2000"
enableLookups="false"
maxKeepAliveRequests="-1"
keepAliveTimeout="-1"
disableUploadTimeout="false"
connectionUploadTimeout="150000"
useSendfile="false"
URIEncoding="UTF-8" />
配置參數說明
protocol="HTTP/1.1"
maxHttpHeaderSize="8192"
maxThreads="1000" //最大線程數,默認200
minSpareThreads="100" //Tomcat初始化時創(chuàng)建的socket線程數,線程的最小運行數目,這些始終保持運行,如果未指定,默認值為10
enableLookups="false"http://關閉DNS反向查詢,若設為true,則支持域名解析,可把ip地址解析為主機名
compression="on"http://打開壓縮功能
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain"
connectionTimeout="20000"http://代表連接超時時間,單位為毫秒,默認值為60000。通常情況下設置為30000
URIEncoding="utf-8"http://URL統一編碼
acceptCount="1000"http://監(jiān)聽端口隊列最大數,滿了之后客戶請求會被拒絕(不能小于maxSpareThreads),如果未指定,默認值為100
redirectPort="8443"http://在需要基于安全通道的場合,把客戶請求轉發(fā)到基于SSL的redirectPort端口
disableUploadTimeout="true"/>//這個標志允許servlet[Container](http://lib.csdn.net/base/4)在一個servlet執(zhí)行的時候,使用一個不同的,更長的連接超時。最終的結果是給servlet更長的時間以便完成其執(zhí)行,或者在數據上載的時候更長的超時時間。如果沒有指定,設為false
禁用 AJP(如果你服務器沒有使用 Apache)
<!-- <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> -->
Tomcat 壓縮配置,建議在前端 nginx 上開啟壓縮。Tomcat 作為應用服務器本身就很繁忙了。
compression="on"
compressionMinSize="2048"
compressableMimeType="text/html,text/xml,application/javascript,application/json,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png"
三、JVM 優(yōu)化
Java 程序調優(yōu),主要就是調『堆內存』和『垃圾回收機制』。
3.1 JVM 調優(yōu)常用參數說明
| 參數 | 作用 |
|---|---|
| -Xms | 堆初始內存大小 |
| -Xmx | 堆最大內存大小 |
| -Xmn | 初始新生代內存大小,一般設置為整個堆的 1/3到 1/4 左右 |
| -XX:PrintGC | 每次觸發(fā)GC的時候打印相關日志 |
| -XX:+PrintGCDetails | 更詳細的GC日志 |
| -XX:SurvivorRatio=2 | 用來設置新生代中 Eden 空間和 from/to 空間比例,默認是 8 (Edem : from : to = 8 : 1 : 1) |
| -Dfile.encoding=UTF-8 | 設置字符集避免日志中出現亂碼 |
| -Dsun.jnu.encoding=UTF-8 | 設置字符集避免日志中出現亂碼 |
| -Duser.timezone=GMT+08 | 時區(qū)設置 |
| -XX:+HeapDumpOnOutOfMemoryError | 堆異常報錯輸出 |
| -XX:HeapDumpPath=path | 設置在dump heap時將文件dump到哪里。默認是當前目錄下 java_pidpid.hprof這樣形式的文件。 |
| -XX:+UseParallelGC | 選擇垃圾收集器為并行收集器。此配置僅對年輕代有效。即上述配置下,年輕代使用并發(fā)收集,而年老代仍舊使用串行收集 |
| -XX:+UseParallelOldGC | 配置年老代垃圾收集方式為并行收集。 |
| -XX:ParallelGCThreads=8 | 設置支持并發(fā)GC的線程數。 |
3.2 調優(yōu)總結
在實際工作中,我們可以直接將初始堆大小(-Xms)與最大堆大小(-Xmx) 配置相等,這樣的好處就是可以減少程序運行時垃圾回收的次數,從而提高效率。
新生代與老年代的設置比例:1:3 或者 1:4
四、內核參數優(yōu)化
以下內核參數是工作環(huán)境中大家經常會看到的一些:
net.core.netdev_max_backlog = 32768
net.core.somaxconn = 32768
net.core.rmem_default = 8388608
net.core.wmem_default = 8388608
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.route.gc_timeout = 100
net.ipv4.tcp_fin_timeout = 30
net.ipv4.tcp_keepalive_time = 1200
net.ipv4.tcp_timestamps = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_syn_retries = 1
net.ipv4.tcp_synack_retries = 1
net.ipv4.tcp_mem = 94500000 915000000 927000000
net.ipv4.tcp_max_orphans = 3276800
net.ipv4.tcp_max_syn_backlog = 65535
以上內核參數作用說明:
# cat /proc/sys/net/core/netdev_max_backlog
# 默認值:1000
# 作用:網卡設備將請求放入隊列的長度
net.core.netdev_max_backlog = 32768
# cat /proc/sys/net/core/somaxconn
# 默認值:128
# 作用:已經成功建立連接的套接字將要進入隊列的長度
net.core.somaxconn = 32768
# cat /proc/sys/net/core/rmem_default
# 默認值:212992
# 作用:默認的TCP數據接收窗口大?。ㄗ止?jié))
net.core.rmem_default = 8388608
# cat /proc/sys/net/core/wmem_default
# 默認值:212992
# 作用:默認的TCP數據發(fā)送窗口大?。ㄗ止?jié))
net.core.wmem_default = 8388608
# cat /proc/sys/net/core/rmem_max
# 默認值:212992
# 作用:最大的TCP數據接收窗口大?。ㄗ止?jié))
net.core.rmem_max = 16777216
# cat /proc/sys/net/core/wmem_max
# 默認值:212992
# 作用:最大的TCP數據發(fā)送窗口大小(字節(jié))
net.core.wmem_max = 16777216
# cat /proc/sys/net/ipv4/ip_local_port_range
# 默認值:32768 61000
# 作用:可用端口的范圍
net.ipv4.ip_local_port_range = 1024 65535
# cat /proc/sys/net/ipv4/route/gc_timeout
# 默認值 300
# 作用:路由緩存刷新頻率,當一個路由失敗后多長時間跳到另一個路由
net.ipv4.route.gc_timeout = 100
# cat /proc/sys/net/ipv4/tcp_fin_timeout
# 默認 60
# 作用:表示如果套接字由本端要求關閉,這個參數決定了它保持在FIN-WAIT-2狀態(tài)的時間
net.ipv4.tcp_fin_timeout = 30
# cat /proc/sys/net/ipv4/tcp_keepalive_time
# 默認值:7200
# 作用:間隔多久發(fā)送1次keepalive探測包
net.ipv4.tcp_keepalive_time = 1200
# cat /proc/sys/net/ipv4/tcp_timestamps
# 默認值:1
# 作用:TCP時間戳,時間戳必須要開啟,否則下面的 TIME_WAIT 重用和快速回收無效
net.ipv4.tcp_timestamps = 1
# cat /proc/sys/net/ipv4/tcp_tw_recycle
# 默認值:0
# 作用:表示開啟TCP連接中TIME-WAIT sockets的快速回收,默認為0,表示關閉。
net.ipv4.tcp_tw_recycle = 1
######################## cat /proc/sys/net/ipv4/tcp_tw_reuse
# 默認值:0
# 作用:針對 TIME-WAIT,做為客戶端可以啟用(例如,作為nginx-proxy前端代理,要訪問后端的服務)
net.ipv4.tcp_tw_reuse = 1
# cat /proc/sys/net/ipv4/tcp_syn_retries
# 默認值 2
# 作用:在內核放棄建立連接之前發(fā)送SYN包的數量。
net.ipv4.tcp_syn_retries = 1
# cat /proc/sys/net/ipv4/tcp_synack_retries
# 默認值 2
# 作用:為了打開對端的連接,內核需要發(fā)送一個SYN并附帶一個回應前面一個SYN的ACK。也就是所謂三次握手中的第二次握手。這個設置決定了內核放棄連接之前發(fā)送SYN+ACK包的數量。
net.ipv4.tcp_synack_retries = 1
# cat /proc/sys/net/ipv4/tcp_mem
#確定 TCP 棧應該如何反映內存使用;每個值的單位都是內存頁(通常是 4KB)。
#第一個值是內存使用的下限。
#第二個值是內存壓力模式開始對緩沖區(qū)使用應用壓力的上限。
#第三個值是內存上限。在這個層次上可以將報文丟棄,從而減少對內存的使用。對于較大的 BDP 可以增大這些值(但是要記住,其單位是內存頁,而不是字節(jié))。
net.ipv4.tcp_mem = 94500000 915000000 927000000
# cat /proc/sys/net/ipv4/tcp_max_orphans
# 默認值 16384
# 作用:系統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。如果超過這個數字,孤兒連接將即刻被復位并打印出警告信息。這個限制僅僅是為了防止簡單 的DoS攻擊,你絕對不能過分依靠它或者人為地減小這個值,更應該增加這個值(如果增加了內存之后)。
net.ipv4.tcp_max_orphans = 3276800
# cat /proc/sys/net/ipv4/tcp_max_syn_backlog
# 默認值:128
# 作用:增大SYN隊列的長度,容納更多連接
net.ipv4.tcp_max_syn_backlog = 65535
五、JVM 調優(yōu)常用工具
5.1 為 Tomcat 配置 JMX 連接
開啟 JMX 遠程連接以后,就可以通過 jconsole 和 jvisualvm 兩個 JDK 內置工具連接 JMX 從而查看當前主機的 JVM 相關信息。
$ cd /usr/local/tomcat/bin/
# 默認情況下,$CATALINA_HOME/bin 目錄下是沒有 setenv.sh,可以自己新建此文件
$ vim setenv.sh
CATALINA_OPTS="-Djava.rmi.server.hostname=10.100.4.169
-Dcom.sun.management.jmxremote.port=8686
-Dcom.sun.management.jmxremote.authenticate=false
-Dcom.sun.management.jmxremote.ssl=false"
:wq
JMX 配置說明:
# 通過哪個 IP 訪問本機的 JMX
-Djava.rmi.server.hostname=10.100.4.169
# JMX 監(jiān)聽的端口,默認 8686
-Dcom.sun.management.jmxremote.port=8686
# 是否需要用戶名密碼認證,默認 false
-Dcom.sun.management.jmxremote.authenticate=false
# 是否啟用 SSL
-Dcom.sun.management.jmxremote.ssl=false"
5.2 使用 jconsole 工具連接 JMX
Jconsole 是 JDK 自帶的監(jiān)控工具,在 JDK/bin 目錄下可以找到。它用于連接正在運行的本地或者遠程的 JVM,對運行在 java 應用程序的資源消耗和性能進行監(jiān)控,并畫出大量的圖表,提供強大的可視化界面。
當你在 Windows 或者 Mac 下運行 jconsole 后就會在桌面彈出 jconsole 的控制臺,選擇遠程連接,輸入IP:端口 點擊連接

我這里在配置 JMX 的時候沒有啟用 SSL 所以會提示不安全,點擊『不安全的連接』


5.3 使用 jvisualvm 工具連接 JMX
jvisualvm 同樣是 JDK 內置的工具,使用方法與 jconsole 類似。功能上要比 jconsole 強大一些。推薦使用。
運行 jvisualvm 后同樣在桌面彈出一個控制臺窗口

遠程 --> 添加主機 --> 添加 JMX 連接

5.4 jmap 工具
jmap 可以輸出 Java 進程 內存中對象的工具。jmap 一般和 jhat 或者 MAT 配合使用,以圖像的形式直觀的展示當前內存是否有問題。
5.4.1 參數說明
-dump:[live,]format=b,file=<filename>
以hprof二進制格式轉儲Java堆到指定filename的文件中。
live子選項是可選的,如果指定了live子選項,堆中只有活動的對象會被轉儲。
想要瀏覽heap dump,你可以使用 jhat(Java堆分析工具) 或者 MAT 讀取生成的文件。
-finalizerinfo
打印等待終結的對象信息。
-heap
打印一個堆的摘要信息,包括使用的GC算法、堆配置信息和generation wise heap usage。
-histo[:live]
打印每個Java類、對象數量、內存大小(單位:字節(jié))、完全限定的類名。
打印的虛擬機內部的類名稱將會帶有一個'*'前綴。
如果指定了live子選項,則只計算活動的對象。
-permstat
打印Java堆內存的永久保存區(qū)域的類加載器的智能統計信息。
對于每個類加載器而言,它的名稱、活躍度、地址、父類加載器、它所加載的類的數量和大小都會被打印。
此外,包含的字符串數量和大小也會被打印。
-F
強制模式。如果指定的pid沒有響應,請使用jmap -dump或jmap -histo選項。此模式下,不支持live子選項。
-h | -help
打印幫助信息。
-J<flag>
指定傳遞給運行jmap的JVM的參數。
5.4.2 例子
示例 1、jmap -histo <pid> 打印每個Java類、對象數量、內存大小(單位:字節(jié))、完全限定的類名。
$ jmap -histo 14110
num #instances #bytes class name
----------------------------------------------
1: 50994 7789848 [C
2: 22160 6263680 [B
3: 8527 1437176 [I
4: 49099 1178376 java.lang.String
5: 63 936248 [J
6: 19118 611776 java.util.HashMap$Node
7: 6243 549384 java.lang.reflect.Method
8: 8948 509464 [Ljava.lang.Object;
9: 10588 423520 java.util.TreeMap$Entry
10: 3696 417952 java.lang.Class
11: 1372 230544 [Ljava.util.HashMap$Node;
12: 4760 228480 java.util.HashMap
13: 4789 191560 java.util.HashMap$ValueIterator
14: 5851 187232 java.io.File
B byte
C char
D double
F float
I int
J long
Z boolean
[ 數組,如[I表示int[]
[L+類名 其他對象
示例 2、jmap -heap <pid> 查看進程堆內存使用情況,包括使用的GC算法、堆配置參數和各代中堆內存使用情況。
$ jmap -heap 14110
Attaching to process ID 14110, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 134217728 (128.0MB)
NewSize = 44564480 (42.5MB)
MaxNewSize = 44564480 (42.5MB)
OldSize = 89653248 (85.5MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 19398656 (18.5MB)
used = 6316256 (6.023651123046875MB)
free = 13082400 (12.476348876953125MB)
32.56027634079392% used
From Space:
capacity = 12582912 (12.0MB)
used = 98304 (0.09375MB)
free = 12484608 (11.90625MB)
0.78125% used
To Space:
capacity = 12582912 (12.0MB)
used = 0 (0.0MB)
free = 12582912 (12.0MB)
0.0% used
PS Old Generation
capacity = 89653248 (85.5MB)
used = 20189088 (19.253814697265625MB)
free = 69464160 (66.24618530273438MB)
22.519081517269736% used
13984 interned Strings occupying 1890552 bytes.
示例 3、jmap -dump:format=b,file=<dumpFileName> <pid> 用jmap把進程內存使用情況dump到文件中
$ jmap -dump:format=b,file=/tmp/123.hprof 14110
Dumping heap to /tmp/123.hprof ...
Heap dump file created
5.5 jhat 工具
jhat 可以對 dump 出來的堆信息進行處理,以 html 頁面的形式展示出來。
執(zhí)行 jhat /tmp/123.hprof即可,默認端口是 7000,訪問 http://localhost:7000 即可查看結果。通過 -port 指定端口。

以上就是我整理出的 Tomcat 基礎優(yōu)化方法。