1. 程式人生 > >Tomcat調優及JVM引數優化

Tomcat調優及JVM引數優化

        為了提升效能,首先就要對程式碼進行動靜分離,讓 Tomcat 只負責 jsp 檔案的解析工作。如採用 Apache 和 Tomcat 的整合方式,他們之間的連線方案有三種選擇,JK、http_proxy 和 ajp_proxy。相對於 JK 的連線方式,後兩種在配置上比較簡單的,靈活性方面也一點都不遜色。但就穩定性而言不像JK 這樣久經考驗,所以建議採用 JK 的連線方式。 
        從3個方面進行優化:工作方式選擇,Connector 聯結器的配置,配置檔案優化
        1、工作方式選擇
             為了提升效能,首先就要對程式碼進行動靜分離,讓 Tomcat 只負責 jsp 檔案的解析工作。如採用 Apache 和 Tomcat 的整合方式,他們之間的連線方案有三種選擇,JK、http_proxy 和 ajp_proxy。相對於 JK 的連線方式,後兩種在配置上比較簡單的,靈活性方面也一點都不遜色。但就穩定性而言不像JK 這樣久經考驗,所以建議採用 JK 的連線方式。 
         2、Connector 聯結器的配置
             之前檔案介紹過的 Tomcat 聯結器的三種方式: bio、nio 和 apr,三種方式效能差別很大,apr 的效能最優, bio 的效能最差。而 Tomcat 7 使用的 Connector  預設就啟用的 Apr 協議,但需要系統安裝 Apr 庫,否則就會使用 bio 方式。
         3、配置檔案優化
             配置檔案優化其實就是對 server.xml 優化,可以提大大提高 Tomcat 的處理請求的能力,下面我們來看 Tomcat 容器內的優化。
          1),搜尋【<Executor name="tomcatThreadPool"】,開啟並調整為
     <Executor name="tomcatThreadPool" namePrefix= "catalina-exec"  maxThreads="500" minSpareThreads="20"  maxSpareThreads="50" maxIdleTime="60000"/>
       2),修改<Connector …>節點,增加 executor 屬性,通過executor指定剛剛配置好的tomcat執行緒池,搜尋【port="8080"】,調        整為:
        <Connector executor="tomcatThreadPool"
               port="8080" protocol="HTTP/1.1"
               URIEncoding="UTF-8"
               connectionTimeout="30000"
               enableLookups="false"
               disableUploadTimeout="false"
               connectionUploadTimeout="150000"
               acceptCount="300"
               keepAliveTimeout="120000"
               maxKeepAliveRequests="1"
               compression="on"
               compressionMinSize="2048"
               compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,image/gif,image/jpg,image/png" 
               redirectPort="8443" />
      這樣當客戶端請求tomcat時,會使用我們配置好的連結物件那Connector(我們又指定了執行緒池),大大的提升系統性能。上邊配置的引數,最大執行緒 500(一般伺服器足以),要根據自己的實際情況合理設定,設定越大會耗費記憶體和 CPU,因為 CPU 疲於執行緒上下文切換,沒有精力提供請求服務了,最小空閒執行緒數 20,執行緒最大空閒時間 60 秒,當然允許的最大執行緒連線數還受制於作業系統的核心引數設定,設定多大要根據自己的需求與環境。當然執行緒可以配置在“tomcatThreadPool”中,也可以直接配置在“Connector”中,但不可以重複配置。
         server.xml配置檔案中相關的屬性介紹:
         maxThreads :Tomcat 使用執行緒來處理接收的每個請求,這個值表示 Tomcat 可建立的最大的執行緒數,預設值是 200
         minSpareThreads:最小空閒執行緒數,Tomcat 啟動時的初始化的執行緒數表示即使沒有人使用也開這麼多空執行緒等待預設值是 10。
         maxSpareThreads:最大備用執行緒數,一旦建立的執行緒超過這個值,Tomcat 就會關閉不再需要的 socket 執行緒。
         URIEncoding:指定 Tomcat 容器的 URL 編碼格式,語言編碼格式這塊倒不如其它 WEB 伺服器軟體配置方便,需要分別指定。
         connnectionTimeout: 網路連線超時,單位:毫秒,設定為 0 表示永不超時,這樣設定有隱患的。通常可設定為 30000 毫秒,可          根據檢測實際情況,適當修改。
         enableLookups: 是否反查域名,以返回遠端主機的主機名,取值為:true 或 false,如果設定為false,則直接返回IP地址,為了           提高處理能力,應設定為 false。
         disableUploadTimeout:上傳時是否使用超時機制。
         connectionUploadTimeout:上傳超時時間,畢竟檔案上傳可能需要消耗更多的時間,這個根據你自己的業務需要自己調,以使             Servlet有較長的時間來完成它的執行,需要與上一個引數一起配合使用才會生效。
         acceptCount:指定當所有可以使用的處理請求的執行緒數都被使用時,可傳入連線請求的最大佇列長度,超過這個數的請求將不予處          理,預設為100個。
         keepAliveTimeout:長連線最大保持時間(毫秒),表示在下次請求過來之前,Tomcat 保持該連線多久,預設是使用                           connectionTimeout 時間,-1 為不限制超時。
         maxKeepAliveRequests:表示在伺服器關閉之前,該連線最大支援的請求數。超過該請求數的連線也將被關閉,1表示禁用,-1表           示不限制個數,預設100個,一般設定在100~200之間。
         compression:是否對響應的資料進行 GZIP 壓縮,off:表示禁止壓縮;on:表示允許壓縮(文字將被壓縮)、force:表示所有情          況下都進行壓縮,預設值為off,壓縮資料後可以有效的減少頁面的大小,一般可以減小1/3左右,節省頻寬。
         compressionMinSize:表示壓縮響應的最小值,只有當響應報文大小大於這個值的時候才會對報文進行壓縮,如果開啟了壓縮功             能,預設值就是2048。
         compressableMimeType:壓縮型別,指定對哪些型別的檔案進行資料壓縮。
         noCompressionUserAgents="gozilla, traviata": 對於以下的瀏覽器,不啟用壓縮。
         如果已經對程式碼進行了動靜分離,靜態頁面和圖片等資料就不需要 Tomcat 處理了,那麼也就不需要配置在 Tomcat 中配置壓縮了。以上是一些常用的配置引數屬性,當然還有好多其它的引數設定,還可以繼續深入的優化,HTTP Connector 與 AJP Connector 的引數屬性值,可以參考官方文件的詳細說明:
https://tomcat.apache.org/tomcat-7.0-doc/config/http.html
https://tomcat.apache.org/tomcat-7.0-doc/config/ajp.html


二、jvm調優
     32 位系統下 JVM 對記憶體的限制:不能突破 2GB ,那麼這時你的 Tomcat 要優化,就要講究點技巧了,而在 64 位作業系統上無論是系統記憶體還是 JVM 都沒有受到 2GB 這樣的限制,針對於 JMX 遠端監控也是在這裡設定。
     1、JVM 引數配置方法
     如果是linux作業系統,則配置tomcat/bin/catalina.sh檔案;如果是window作業系統,則修改tomcat/bin/catalina.bat檔案
     CATALINA_OPTS:用於當 Java 執行時選項“start”或“run”命令執行。
     2、在catalina.sh中新增變數以及變數的值來優化jvm:
     CATALINA_OPTS="
     -server 
     -Xms6000M 
     -Xmx6000M 
     -Xss512k 
     -XX:NewSize=2250M 
     -XX:MaxNewSize=2250M 
     -XX:PermSize=128M
     -XX:MaxPermSize=256M  
     -XX:+AggressiveOpts 
     -XX:+UseBiasedLocking 
     -XX:+DisableExplicitGC 
     -XX:+UseParNewGC 
     -XX:+UseConcMarkSweepGC 
    -XX:MaxTenuringThreshold=31 
     -XX:+CMSParallelRemarkEnabled 
-XX:+UseCMSCompactAtFullCollection 
-XX:LargePageSizeInBytes=128m 
-XX:+UseFastAccessorMethods 
-XX:+UseCMSInitiatingOccupancyOnly
-Duser.timezone=Asia/Shanghai 
-Djava.awt.headless=true"




上述這樣的配置,基本上可以達到:系統響應時間增快;JVM回收速度增快同時又不影響系統的響應率;JVM記憶體最大化利用;執行緒阻塞情況最小化。


JVM 常用引數詳解:
-server:一定要作為第一個引數,在多個 CPU 時效能佳,還有一種叫 -client 的模式,特點是啟動速度比較快,但執行時效能和記憶體管理效率不高,通常用於客戶端應用程式或開發除錯,在 32 位環境下直接執行 Java 程式預設啟用該模式。Server 模式的特點是啟動速度比較慢,但執行時效能和記憶體管理效率很高,適用於生產環境,在具有 64 位能力的 JDK 環境下預設啟用該模式,可以不配置該引數。


-Xms:表示 Java 初始化堆的大小,-Xms 與-Xmx 設成一樣的值,避免 JVM 反覆重新申請記憶體,導致效能大起大落,預設值為實體記憶體的 1/64,預設(MinHeapFreeRatio引數可以調整)空餘堆記憶體小於 40% 時,JVM 就會增大堆直到 -Xmx 的最大限制。


-Xmx:表示最大 Java 堆大小,當應用程式需要的記憶體超出堆的最大值時虛擬機器就會提示記憶體溢位,並且導致應用服務崩潰,因此一般建議堆的最大值設定為可用記憶體的最大值的80%。如何知道我的 JVM 能夠使用最大值,使用 java -Xmx512M -version 命令來進行測試,然後逐漸的增大 512 的值,如果執行正常就表示指定的記憶體大小可用,否則會列印錯誤資訊,預設值為實體記憶體的 1/4,預設(MinHeapFreeRatio引數可以調整)空餘堆記憶體大於 70% 時,JVM 會減少堆直到-Xms 的最小限制。


-Xss:表示每個 Java 執行緒堆疊大小,JDK 5.0 以後每個執行緒堆疊大小為 1M,以前每個執行緒堆疊大小為 256K。根據應用的執行緒所需記憶體大小進行調整,在相同實體記憶體下,減小這個值能生成更多的執行緒,但是作業系統對一個程序內的執行緒數還是有限制的,不能無限生成,經驗值在 3000~5000 左右。一般小的應用, 如果棧不是很深, 應該是128k 夠用的,大的應用建議使用 256k 或 512K,一般不易設定超過 1M,要不然容易出現out ofmemory。這個選項對效能影響比較大,需要嚴格的測試。


-XX:NewSize:設定新生代記憶體大小。


-XX:MaxNewSize:設定最大新生代新生代記憶體大小


-XX:PermSize:設定持久代記憶體大小


-XX:MaxPermSize:設定最大值持久代記憶體大小,永久代不屬於堆記憶體,堆記憶體只包含新生代和老年代。


-XX:+AggressiveOpts:作用如其名(aggressive),啟用這個引數,則每當 JDK 版本升級時,你的 JVM 都會使用最新加入的優化技術(如果有的話)。


-XX:+UseBiasedLocking:啟用一個優化了的執行緒鎖,我們知道在我們的appserver,每個http請求就是一個執行緒,有的請求短有的請求長,就會有請求排隊的現象,甚至還會出現執行緒阻塞,這個優化了的執行緒鎖使得你的appserver內對執行緒處理自動進行最優調配。


-XX:+DisableExplicitGC:在 程式程式碼中不允許有顯示的呼叫“System.gc()”。每次在到操作結束時手動呼叫 System.gc() 一下,付出的代價就是系統響應時間嚴重降低,就和關於 Xms,Xmx 裡的解釋的原理一樣,這樣去呼叫 GC 導致系統的 JVM 大起大落。


-XX:+UseConcMarkSweepGC:設定年老代為併發收集,即 CMS gc,這一特性只有 jdk1.5後續版本才具有的功能,它使用的是 gc 估算觸發和 heap 佔用觸發。我們知道頻頻繁的 GC 會造面 JVM的大起大落從而影響到系統的效率,因此使用了 CMS GC 後可以在 GC 次數增多的情況下,每次 GC 的響應時間卻很短,比如說使用了 CMS,GC 後經過 jprofiler 的觀察,GC 被觸發次數非常多,而每次 GC 耗時僅為幾毫秒。


-XX:+UseParNewGC:對新生代採用多執行緒並行回收,這樣收得快,注意最新的 JVM 版本,當使用 -XX:+UseConcMarkSweepGC 時,-XX:UseParNewGC 會自動開啟。因此,如果年輕代的並行 GC 不想開啟,可以通過設定 -XX:-UseParNewGC 來關掉。


-XX:MaxTenuringThreshold:設定垃圾最大年齡。如果設定為0的話,則新生代物件不經過 Survivor 區,直接進入老年代。對於老年代比較多的應用(需要大量常駐記憶體的應用),可以提高效率。如果將此值設定為一 個較大值,則新生代物件會在 Survivor 區進行多次複製,這樣可以增加物件在新生代的存活時間,增加在新生代即被回收的概率,減少Full GC的頻率,這樣做可以在某種程度上提高服務穩定性。該引數只有在序列 GC 時才有效,這個值的設定是根據本地的 jprofiler 監控後得到的一個理想的值,不能一概而論原搬照抄。


-XX:+CMSParallelRemarkEnabled:在使用 UseParNewGC 的情況下,儘量減少 mark 的時間。


-XX:+UseCMSCompactAtFullCollection:在使用 concurrent gc 的情況下,防止 memoryfragmention,對 live object 進行整理,使 memory 碎片減少。


-XX:LargePageSizeInBytes:指定 Java heap 的分頁頁面大小,記憶體頁的大小不可設定過大, 會影響 Perm 的大小。


-XX:+UseFastAccessorMethods:使用 get,set 方法轉成原生代碼,原始型別的快速優化。


-XX:+UseCMSInitiatingOccupancyOnly:只有在 oldgeneration 在使用了初始化的比例後 concurrent collector 啟動收集。


-Duser.timezone=Asia/Shanghai:設定使用者所在時區。


-Djava.awt.headless=true:這個引數一般我們都是放在最後使用的,這全引數的作用是這樣的,有時我們會在我們的 J2EE 工程中使用一些圖表工具如:jfreechart,用於在 web 網頁輸出 GIF/JPG 等流,在 winodws 環境下,一般我們的 app server 在輸出圖形時不會碰到什麼問題,但是在linux/unix 環境下經常會碰到一個 exception 導致你在 winodws 開發環境下圖片顯示的好好可是在 linux/unix 下卻顯示不出來,因此加上這個引數以免避這樣的情況出現。


-Xmn:新生代的記憶體空間大小,注意:此處的大小是(eden+ 2 survivor space)。與 jmap -heap 中顯示的 New gen 是不同的。整個堆大小 = 新生代大小 + 老生代大小 + 永久代大小。在保證堆大小不變的情況下,增大新生代後,將會減小老生代大小。此值對系統性能影響較大,Sun官方推薦配置為整個堆的 3/8。


-XX:CMSInitiatingOccupancyFraction:當堆滿之後,並行收集器便開始進行垃圾收集,例如,當沒有足夠的空間來容納新分配或提升的物件。對於 CMS 收集器,長時間等待是不可取的,因為在併發垃圾收集期間應用持續在執行(並且分配物件)。因此,為了在應用程式使用完記憶體之前完成垃圾收集週期,CMS 收集器要比並行收集器更先啟動。因為不同的應用會有不同物件分配模式,JVM 會收集實際的物件分配(和釋放)的執行時資料,並且分析這些資料,來決定什麼時候啟動一次 CMS 垃圾收集週期。這個引數設定有很大技巧,基本上滿足(Xmx-Xmn)*(100-CMSInitiatingOccupancyFraction)/100 >= Xmn 就不會出現 promotion failed。例如在應用中 Xmx 是6000,Xmn 是 512,那麼 Xmx-Xmn 是 5488M,也就是老年代有 5488M,CMSInitiatingOccupancyFraction=90 說明老年代到 90% 滿的時候開始執行對老年代的併發垃圾回收(CMS),這時還 剩 10% 的空間是 5488*10% = 548M,所以即使 Xmn(也就是新生代共512M)裡所有物件都搬到老年代裡,548M 的空間也足夠了,所以只要滿足上面的公式,就不會出現垃圾回收時的 promotion failed,因此這個引數的設定必須與 Xmn 關聯在一起。


-XX:+CMSIncrementalMode:該標誌將開啟 CMS 收集器的增量模式。增量模式經常暫停 CMS 過程,以便對應用程式執行緒作出完全的讓步。因此,收集器將花更長的時間完成整個收集週期。因此,只有通過測試後發現正常 CMS 週期對應用程式執行緒干擾太大時,才應該使用增量模式。由於現代伺服器有足夠的處理器來適應併發的垃圾收集,所以這種情況發生得很少,用於但 CPU情況。


-XX:NewRatio:年輕代(包括 Eden 和兩個 Survivor 區)與年老代的比值(除去持久代),-XX:NewRatio=4 表示年輕代與年老代所佔比值為 1:4,年輕代佔整個堆疊的 1/5,Xms=Xmx 並且設定了 Xmn 的情況下,該引數不需要進行設定。


-XX:SurvivorRatio:Eden 區與 Survivor 區的大小比值,設定為 8,表示 2 個 Survivor 區(JVM 堆記憶體年輕代中預設有 2 個大小相等的 Survivor 區)與 1 個 Eden 區的比值為 2:8,即 1 個 Survivor 區佔整個年輕代大小的 1/10。


-XX:+UseSerialGC:設定序列收集器。


-XX:+UseParallelGC:設定為並行收集器。此配置僅對年輕代有效。即年輕代使用並行收集,而年老代仍使用序列收集。


-XX:+UseParallelOldGC:配置年老代垃圾收集方式為並行收集,JDK6.0 開始支援對年老代並行收集。


-XX:ConcGCThreads:早期 JVM 版本也叫-XX:ParallelCMSThreads,定義併發 CMS 過程執行時的執行緒數。比如 value=4 意味著 CMS 週期的所有階段都以 4 個執行緒來執行。儘管更多的執行緒會加快併發 CMS 過程,但其也會帶來額外的同步開銷。因此,對於特定的應用程式,應該通過測試來判斷增加 CMS 執行緒數是否真的能夠帶來效能的提升。如果還標誌未設定,JVM 會根據並行收集器中的


 -XX:ParallelGCThreads :引數的值來計算出預設的並行 CMS 執行緒數。


-XX:ParallelGCThreads:配置並行收集器的執行緒數,即:同時有多少個執行緒一起進行垃圾回收,此值建議配置與 CPU 數目相等。


-XX:OldSize:設定 JVM 啟動分配的老年代記憶體大小,類似於新生代記憶體的初始大小 -XX:NewSize。
以上就是一些常用的配置引數,有些引數是可以被替代的,配置思路需要考慮的是 Java 提供的垃圾回收機制。虛擬機器的堆大小決定了虛擬機器花費在收集垃圾上的時間和頻度。收集垃圾能夠接受的速度和應用有關,應該通過分析實際的垃圾收集的時間和頻率來調整。假如堆的大小很大,那麼完全垃圾收集就會很慢,但是頻度會降低。假如您把堆的大小和記憶體的需要一致,完全收集就很快,但是會更加頻繁。調整堆大小的的目的是最小化垃圾收集的時間,以在特定的時間內最大化處理客戶的請求。在基準測試的時候,為確保最好的效能,要把堆的大小設大,確保垃圾收集不在整個基準測試的過程中出現。
假如系統花費很多的時間收集垃圾,請減小堆大小。一次完全的垃圾收集應該不超過 3-5 秒。假如垃圾收整合為瓶頸,那麼需要指定代的大小,檢查垃圾收集的周詳輸出,研究垃圾收集引數對效能的影響。當增加處理器時,記得增加記憶體,因為分配能夠並行進行,而垃圾收集不是並行的。


3、設定系統屬性
之前說過,Tomcat 的語言編碼,配置起來很慢,要經過多次設定才可以了,否則中文很有可能出現亂碼情況。譬如漢字“中”,以 UTF-8 編碼後得到的是 3 位元組的值 %E4%B8%AD,然後通過 GET 或者 POST 方式把這 3 個位元組提交到 Tomcat 容器,如果你不告訴 Tomcat 我的引數是用 UTF-8編碼的,那麼 Tomcat 就認為你是用 ISO-8859-1 來編碼的,而 ISO8859-1(相容 URI 中的標準字符集 US-ASCII)是相容 ASCII 的單位元組編碼並且使用了單位元組內的所有空間,因此 Tomcat 就以為你傳遞的用 ISO-8859-1 字符集編碼過的 3 個字元,然後它就用 ISO-8859-1 來解碼。
設定起來不難使用“ -D<名稱>=<值> ”來設定系統屬性:
-Djavax.servlet.request.encoding=UTF-8
-Djavax.servlet.response.encoding=UTF-8 
-Dfile.encoding=UTF-8 
-Duser.country=CN 
-Duser.language=zh


4、常見的 Java 記憶體溢位有以下三種
(1) java.lang.OutOfMemoryError: Java heap space —-JVM Heap(堆)溢位
JVM 在啟動的時候會自動設定 JVM Heap 的值,其初始空間(即-Xms)是實體記憶體的1/64,最大空間(-Xmx)不可超過實體記憶體。可以利用 JVM提供的 -Xmn -Xms -Xmx 等選項可進行設定。Heap 的大小是 Young Generation 和 Tenured Generaion 之和。在 JVM 中如果 98% 的時間是用於 GC,且可用的 Heap size 不足 2% 的時候將丟擲此異常資訊。
解決方法:手動設定 JVM Heap(堆)的大小。  
(2) java.lang.OutOfMemoryError: PermGen space  —- PermGen space溢位。
PermGen space 的全稱是 Permanent Generation space,是指記憶體的永久儲存區域。為什麼會記憶體溢位,這是由於這塊記憶體主要是被 JVM 存放Class 和 Meta 資訊的,Class 在被 Load 的時候被放入 PermGen space 區域,它和存放 Instance 的 Heap 區域不同,sun 的 GC 不會在主程式執行期對 PermGen space 進行清理,所以如果你的 APP 會載入很多 CLASS 的話,就很可能出現 PermGen space 溢位。
解決方法: 手動設定 MaxPermSize 大小
(3) java.lang.StackOverflowError   —- 棧溢位
棧溢位了,JVM 依然是採用棧式的虛擬機器,這個和 C 與 Pascal 都是一樣的。函式的呼叫過程都體現在堆疊和退棧上了。呼叫建構函式的 “層”太多了,以致於把棧區溢位了。通常來講,一般棧區遠遠小於堆區的,因為函式呼叫過程往往不會多於上千層,而即便每個函式呼叫需要 1K 的空間(這個大約相當於在一個 C 函式內聲明瞭 256 個 int 型別的變數),那麼棧區也不過是需要 1MB 的空間。通常棧的大小是 1-2MB 的。
通常遞迴也不要遞迴的層次過多,很容易溢位。
解決方法:修改程式。