Tomcat 8.0 的 JVM、GC 調優(基於Oracle JDK 8)


Tomcat容器是運行在JVM上的, 其默認內存一般都很小(物理內存的1/64), 在實際生產環境中, 若不配置則會極大浪費服務器資源, 影像系統的性能. 可以通過調整JVM啟動參數, 使得Tomcat擁有更好的性能.

對於JVM的優化主要有兩個方面: JVM內存調優垃圾收集策略調優, 本片博文是在Oracle JDK 8之上的測試, 特此說明.

1 JVM內存調優

1.1 Tomcat占用的內存

Tomcat 的運行內存 = Xmx(初始內存大小) + Perm Generation(JDK 7中的永久代大小) + Java應用創建的線程數 * 1MB.

Java 應用每創建一個線程, JVM 進程的內存中就會創建一個 Thread 對象, 同時也會在操作系統中創建一個真正的物理線程(參考JVM規范), 操作系統會在 Tomcat 的空閑內存中創建這個物理線程, 而不是在 JVM 的 Xmx 堆內存中創建.

在 JDK 1.4中, 默認的棧大小是256KB/線程, 但自 JDK 5(為了推廣的方便, JDK 后續版本不再是1.x命名)開始, 默認的棧大小變為1M/線程. 舉例: 如果系統剩余內存為400M, 則Java應用最多能創建400個可用線程.

**結論: 要想創建更多的線程, 必須減少分配給JVM的最大內存. **

1.2 內存配置相關參數

-server 
# JVM的server模式, 在多CPU服務器中性能可以得到更好地發揮. 默認為client. 配置server模式時要將其作為第一個參數. 

-Xmx4g 
# Java Heap的最大可用內存, 默認為物理內存的1/4(已在JDK 7下驗證, 最大值為30638MB, 總內存126GB的23.75%). 
# 在只運行Tomcar容器的服務器中, 建議設置為物理內存的50%~80%. 

-Xms4g 
# Java Heap的初始大小, 默認值為物理內存的1/64(已在JDK 7下驗證, 內存126GB, 初始值為2GB). 

-Xss128k 
# 每個線程的Stack大小. 在相同物理內存下, 減小這個值能生成更多的線程, 
# 但是操作系統對一個進程內的線程數是有限制的, 經驗范圍是3000~5000.

-XX:NewRatio=4 
# 設置新生代(包括Eden和兩個Survivor區)與老年代的比值(除去持久代), 默認為2, 即新生代與老年代所占比值為1:2, 新生代占整個堆棧的1/3. 

-XX:SurvivorRatio=4 
# 設置新生代中Eden區與1個Survivor區的大小比值. 默認為8, 即Eden區占新生代的80%, 2個Survivor分別占新生代的10%. 
# 設置為4, 則兩個Survivor區與一個Eden區的比值為2:4, 一個Survivor區占整個新生代的1/6. 

-Xmn1024m      
# 設置Young Generation所占用的Java Heap大小為1g. 此值對系統性能影響較大, Sun官方推薦配置為整個堆的3/8(或Xmx的1/4~1/3左右). 
# 也可使用-XX:NewSize和-XX:MaxNewsize設置新生代的初始值和最大值. 
# 注意: -Xmn 與 -XX:NewSize、-XX:MaxNewSize 的優先級: -XX:NewRatio的值會被忽略. 
# 1. 高優先級: -XX:NewSize/-XX:MaxNewSize 
# 2. 中優先級: -Xmn, 等效於同時設置 -Xmn = -XX:NewSize = -XX:MaxNewSize 三者的值 
# 3. 低優先級: -XX:NewRatio 
# -Xmn參數是在JDK 1.4 開始支持, 推薦使用之. 

-XX:NewSize=1g 
# 設置新生代的大小, 默認為1.25MB(已在JDK 7下驗證). 若顯示設置此值, 將使得NewRatio選項失效. 

-XX:OldSize=2g 
# 設置老年代的大小, 默認為5.1875MB(已在JDK 7下驗證). 

-XX:PermSize=128m 
# JDK 7及以下版本適用: 設置Java Heap中永久代的初始大小, 默認為20.75MB(已在JDK 7下驗證, client、server模式下均相同). 

-XX:MaxPermSize=256m 
# JDK 7及以下版本適用: 設置Java Heap中永久代的最大值. 默認為82.0MB(已在JDK 7下驗證, client、server模式下均相同).  

-XX:MetaspaceSize=128m 
# JDK 8及以上版本適用: 初始元空間的大小, 默認為21MB(實際為20.79MB左右, 已驗證).  

-XX:MaxMetaspaceSize=256m 
# JDK 8及以上版本適用: 最大元空間的大小. 默認無上限(在126GB物理內存的服務器中, 默認值為(2^44-1)MB, 已驗證). 

總結:

① 如果不指定Xmx、Xms和NewSize、OldSize, 則系統將基於Xms=1/64總內存大小, 對各個Space按照NewRatio=2進行空間的分配, 此時NewSize與OldSize的默認大小將失效.

② 如果指定了Xmx、Xms, 未指定NewSize、OldSize, 則系統將優先滿足 NewRatio=2, 且OldSize+NewSize=Xms, 此時NewSize與OldSize的默認大小將失效.

結論: 除非顯式指定NewSize與OldSize的值, 否則它們的默認配置一般都不會得到滿足. 顯式指定其中任一個, 另一個就會基於默認值, 並根據應用程序的消耗動態分配空間大小.

1.3 內存調優實踐

JVM內存方面的調優, 需要在${TOMCAT_HOME}/bin/catalina.sh文件中調整, 配置 JAVA_OPTS 變量即可. 在啟動Tomcat時, 會執行catalina.sh中的腳本, 將 JAVA_OPTS 作為JVM的啟動參數進行處理.

具體可參考如下配置(示例服務器配置: 126g的物理內存, 2個10核心20線程的物理CPU):

# 在文件最前面(即cygwin=false之前)設置, $JAVA_OPTS 的作用是保留原有的設置, 防止此次修改覆蓋之前的設置
JAVA_OPTS="$JAVA_OPTS -Xmx96g -Xms96g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m"

(1) 說明:

① 若不指定Xmx, 即Java Heap的最大值, 程序啟動時, JVM會調整其大小以滿足程序運行的需要. 每次調整時, 都會對堆進行一次完全垃圾收集(即Full GC), 比較影響性能. 因此推薦明確指定Xmx的大小.

② 令Xmx=Xms會有更好地性能表現: 能避免JVM在每次垃圾收集后重新動態調節堆空間, 因為頻繁伸縮堆大小將帶來額外的性能消耗.

③ JVM有2種模式: client客戶端模式server服務端模式 , 平時開發中使用的多是默認的client模式. 可通過命令行參數-server強制開啟server模式. 兩者之間的最大區別是, JVM對server模式做了大量優化: 雖然server模式下應用程序啟動較慢, 但在長時間運行下, 程序運行效率會明顯高於client模式, 即 client模式不適合需要長時間運行的項目 .

(2) 元空間的調優:

元空間的大小將受限於機器的內存的限制. 限制類的元數據的內存大小, 以避免出現虛擬內存切換以及本地內存分配失敗.

如果可能出現類加載器泄漏, 應當配置此參數指定大小. 32位機器上, 如果地址空間可能會被耗盡, 也應當配置此參數.

元空間的初始大小是21M——這是GC的初始高水位線, 超過這個大小會進行Full GC來進行類的收集.

如果啟動后GC過於頻繁, 請將該值設置得大一些, 以便推遲GC的執行時間.

1.4 驗證配置效果

(1) jps工具查看Java進程:

# 由於配置了環境變量, 在任意目錄下使用jps及jmap命令, 即可調用相關工具: 
# 使用 jps 查看 Java 的所有進程, 其中 Bootstrap 進程就是 Tomcat, 其進程號為14308.
[root@localhost ~]# jps
14308 Bootstrap
15620 Jps

(2) jmap工具查看JVM內存配置和使用情況:

# 通過 jmap 工具查看其內存相關配置
[root@localhost ~]# jmap -heap 14308

# 顯示以下結果
Attaching to process ID 24980, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.151-b12

using thread-local object allocation.
Parallel GC with 28 thread(s)           # 默認使用並發GC策略

Heap Configuration:                     # 堆的配置參數
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 103079215104 (98304.0MB) # 最大堆內存
   NewSize                  = 34359738368 (32768.0MB)  # 新生代大小, 由於默認的NewRatio=2, 所以是1/3的堆大小
   MaxNewSize               = 34359738368 (32768.0MB)
   OldSize                  = 68719476736 (65536.0MB)  # 老年代大小, 由於默認的NewRatio=2, 所以是2/3的堆大小
   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:          # 新生代的伊甸區, 由於默認的SurvivorRatio=8, 所以是6/8的NewSize
   capacity = 25769803776 (24576.0MB)
   used     = 9742123824 (9290.813278198242MB)
   free     = 16027679952 (15285.186721801758MB)
   37.80441600829363% used
From Space:          # 新生代的From區, 由於默認的SurvivorRatio=8, 所以是1/8的NewSize
   capacity = 4294967296 (4096.0MB)
   used     = 0 (0.0MB)
   free     = 4294967296 (4096.0MB)
   0.0% used
To Space:            # 新生代的To區, 由於默認的SurvivorRatio=8, 所以是1/8的NewSize
   capacity = 4294967296 (4096.0MB)
   used     = 0 (0.0MB)
   free     = 4294967296 (4096.0MB)
   0.0% used
PS Old Generation    # 老年代, 由於默認的NewRatio=2, 所以是2/3的堆大小
   capacity = 68719476736 (65536.0MB)
   used     = 254298704 (242.5181427001953MB)
   free     = 68465178032 (65293.481857299805MB)
   0.37005331832915545% used

23928 interned Strings occupying 3198312 bytes.

2 GC策略調優實踐

Tomcat的GC策略一般都是與其內存參數一起配置的, 與應用復雜度相匹配的GC策略、與服務器性能相適應的內存比例, 都將使得系統性能得到大幅提升.

GC策略方面的調優, 也是在 ${TOMCAT_HOME}/bin/catalina.sh文件中調整 -- 同樣是配置 JAVA_OPTS 變量即可.

以JDK 8、Tomcat 8為例, 服務器內存為128GB, 作出如下配置:

通過Solr集群大批量導入數據的應用中, Parallel GC策略的暫停時間太長, 所以選擇CMS收集器.

# 下述配置各自獨占一行, 要置於"cygwin=false"之前. 
# 配置內存
JAVA_OPTS="-server -Xmx96g -Xms96g -Xmn35g -XX:OldSize=55g -Xss128k -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m"

# 配置GC策略
JAVA_OPTS="$JAVA_OPTS -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15 -XX:CMSInitiatingOccupancyFraction=40  -XX:CMSFullGCsBeforeCompaction=0 -XX:+ExplicitGCInvokesConcurrent -XX:SoftRefLRUPolicyMSPerMB=0 -XX:MaxGCPauseMillis=100 -Xnoclassgc "

其中Java Heap的初始大小和最大大小均設置為96g, 76%的物理內存(以不超過80%為宜).

后續看到資料, 建議在使用Lucene的場合下要少為JVM分配內存、保留更多的物理內存用於Lucene對於索引文件的處理, 尚未探究此思路, 寫在此處留待參考.


參考資料

Tomcat中Java垃圾收集調優

版權聲明

作者: 馬瘦風(https://healchow.com)

出處: 博客園 馬瘦風的博客(https://www.cnblogs.com/shoufeng)

感謝閱讀, 如果文章有幫助或啟發到你, 點個[好文要頂👆] 或 [推薦👍] 吧😜

本文版權歸博主所有, 歡迎轉載, 但 [必須在文章頁面明顯位置標明原文鏈接], 否則博主保留追究相關人員法律責任的權利.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM