【J2EE性能分析篇】JVM參數對J2EE性能優化的影響

一切J2EE應用都是基於JVM的,那麼對於JVM的設置和監控,成爲J2EE應用程序性能分析和性能優化的必然手段。今天Sincky和你們交流該話題。這裏以Tomcat環境爲例,其它WEB服務器如Jboss、Weblogic、Websphere徹底一致。html

【認識JVM】java

首先咱們來看一張圖,這是目前JDK1.6版本自帶的JVM性能監控工具VisualVM的一個插件VisualGC的顯示狀況。讓咱們先來了解JVM的內存堆Heap管理模式,要調整JVM,天然要知道它的內部結構和運做,此乃「知己知彼,百戰不殆」!程序員

JVM的Heap包括三部分,分別是Permanent Generation(簡稱PermGen)、New Generation和Tenured Generation(又叫Old )web

PermGen是JVM自用的區域,是內存的永久保存區,用於存放反射代理和Class,Class在被裝載時就會被放到PermGen中;因此若是WEB服務器裏應用的Class至關多時,就能夠考慮將這一塊區域放大一些。算法

New和Old是Java應用的Heap區,用來存放類的實例Instance的;其中New Generation的目標就是儘量快速的收集掉那些生命週期短的對象;New Generation分爲Eden Space,From Space和To Space三塊,Eden Space用於存放新建立的對象,From區和To區都是救助空間Survivor Space(圖中的S0和S1);當Eden區滿時,JVM執行垃圾回收GC(Garbage Collection),垃圾收集器暫停應用程序,並會將Eden Space還存活的對象復 制到當前的From救助空間,一旦當前的From救助空間充滿,此區的存活對象將被複制到另一個To區,當To區也滿了的時候,從From區複製過來並 且依然存活的對象複製到Old區,從而From和To救助空間互換角色,維持活動的對象將在救助空間不斷複製,直到最終轉入Old域。須要注 意,Survivor的兩個區是對稱的,沒前後關係,因此同一個區中可能同時存在從Eden複製過來對象,和從前一個Survivor複製過來的對象,而 且,Survivor區總有一個是空的。同時,根據程序須要,Survivor區是能夠配置爲多個的(多於兩個),這樣能夠增長對象在年輕代中的存在時 間,減小被放到年老代的可能。每一次垃圾回收後Eden Space都會被清空。tomcat

Old區用 於存放長壽的對象,在New區中經歷了N次垃圾回收後仍然存活的對象,就會被放到Old區中;如那些與業務信息相關的對象,包括Http請求中的 Session對象、線程、Socket鏈接,這類對象跟業務直接掛鉤,所以生命週期比較長。當任何一個空間不夠用時,都會促使JVM執行垃圾回收而垃圾回收也須要消耗必定的時間,從而形成應用程序卡住;所以,合理的設置各個區的大小,能夠進行快速GC即Mimor GC,避免頻繁的Full GC性能優化

所謂Minor GC,通常狀況下,當新對象生成,而且在Eden申請空間失敗時,就會觸發Minor GC,對Eden區域進行GC,清除非存活對象,而且把尚且存活的對象移動到Survivor區,而後整理Survivor的兩個區。這種方式的GC是對 New區的Eden區進行,不會影響到Old區。由於大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,因此Eden區的Minor GC會頻繁進行。於是,通常在這裏須要使用速度快、效率高的算法,使Eden去能儘快空閒出來。服務器

而Full GC要對整個Heap區進行回收,包括New、Old和PermGen,因此比Minor GC要慢,所以應該儘量減小Full GC的次數。在對JVM性能調優的過程當中,很大一部分工做就是對於Full GC的調節。有以下緣由可能致使Full GC:
·Tenured區被寫滿
·PermGen區被寫滿
·System.gc()被顯示調用
·上一次GC以後Heap的各域分配策略動態變化
多線程

【JVM參數實例一】JVM PermGen溢出:併發

java.lang.OutOfMemoryError PermGen space

因爲GC不會在主程序運行期對PermGen進行清理,因此若是你的應用中有不少CLASS的話,就極可能出現PermGen space錯誤,這種錯誤常見在WEB服務器對JSP進行預編譯的時候發生。若是你的WEB APP下都用了大量的第三方JAR包, 其大小超過了JVM默認的大小(4M)那麼就會產生此錯誤信息了。

解決方法: 手動設置MaxPermSize大小。修改TOMCAT_HOME/bin/catalina.bat,在echo Using CATALINA_base:%CATALINA_base%上面加入如下行:

JAVA_OPTS=%JAVA_OPTS% -server -XX:PermSize=64M -XX:MaxPermSize=128m

建議:將相同的第三方jar文件移置到tomcat/shared/lib目錄下,這樣能夠達到減小jar 文檔重複佔用內存的目的。

【JVM參數實例二】JVM Heap溢出:

java.lang.OutOfMemoryError: Java heap space

JVM Heap堆的設置是指java程序運行過程當中JVM能夠調配使用的內存空間。JVM在啓動的時候會自動設置Heap size的值,其初始空間(即-Xms)是物理內存的1/64,最大空間(-Xmx)是物理內存的1/4。能夠利用JVM提供的-Xmn -Xms -Xmx等選項可進行設置。提示:在JVM中若是98%的時間是用於GC且可用的Heap大小不足2%的時候將拋出此異常信息。Heap最大不要超過可用 物理內存的80%,通常的要將-Xms和-Xmx選項設置爲相同,而-Xmn爲1/4的-Xmx值。
解決方法:手動設置Heap size,例如:

JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m

可是有的時候可能這樣的設置還會不生效,例如當JVM加載大量Class類時,永久域中的對象急劇增長,從而

使JVM不斷調整永久域大小。爲了不自動調整,可使用前面的參數結合設置,如:

JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:PermSize=64m -XX:MaxPermSize=128m

基於Sun 的Java的垃圾回收機制,容許分隔內存池以包含不一樣時效的對象,垃圾回收循環根據時效收集與其餘對象彼此獨立的對象。使用其餘參數,您能夠單獨設置內存 池的大小。爲了實現更好的性能,能夠對包含短時間存活對象的池的大小進行設置,以使該池中的對象的存活時間不會超過一個垃圾回收循環。新生成的池的大小由 NewSize和MaxNewSize參數肯定。如:

JAVA_OPTS=%JAVA_OPTS% -server -Xms800m -Xmx800m -XX:PermSize=64m -XX:MaxPermSize=128m -XX:NewSize=8m -XX:MaxNewSize=16m

【JVM參數實例三】垃圾回收時promotion failed:

這個問題通常由兩種緣由引發,第一個緣由是Survivor空間不夠,裏面的對象還不該該被移動到Old區,但New區又有不少對象須要放入 Survivor;第二個緣由是Old區沒有足夠的空間接納來自New區的對象;這兩種狀況都會轉向Full GC,形成系統卡住時間較長。第一個緣由能夠經過去掉救助空間解決,設置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0便可,第二個緣由能夠設置CMSInitiatingOccupancyFraction爲某個值 (假設70),這樣Old區的70%時就開始執行CMS,Old區有足夠的空間接納來自New的對象。可是無論怎樣,PermGen仍是會逐漸變滿,因此 隔三差五重起java服務器是必要的。須要注意的是,Old區用的是併發回收,能夠設置New區小一點,Old區大些,能夠減小系統的卡住。

解決方法:

$JAVA_ARGS .= " -Dresin.home=$SERVER_ROOT -server -Xms6000M -Xmx6000M -Xmn500M -XX:PermSize=500M -XX:MaxPermSize=500M -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0 -Xnoclassgc -XX:+DisableExplicitGC -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:-CMSParallelRemarkEnabled -XX:CMSInitiatingOccupancyFraction=90 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log"

-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0 就是去掉了救助空間
-Xnoclassgc 禁用類垃圾回收,性能會高一點
-XX:+DisableExplicitGC 禁止System.gc(),省得程序員誤調用gc方法影響性能
-XX:+UseParNewGC 對New採用多線程並行回收,這樣收得快
帶CMS參數的都是和併發回收相關的:
CMSInitiatingOccupancyFraction, 這個參數設置有很大技巧,基本上知足(Xmx-Xmn)*(100- CMSInitiatingOccupancyFraction)/100>=Xmn 就不會出現promotion failed。在這裏Xmx是6000,Xmn是500,那麼Xmx-Xmn是5500m,也就是Old有 5500m,CMSInitiatingOccupancyFraction=90說明Old到90%滿的時候開始執行對Old區的併發垃圾回收 (CMS),這時還剩10%的空間是5500*10%=550m,因此即便Xmn(也就是New共500m)裏全部對象都搬到Old裏,550兆的空間也 足夠了,因此只要知足上面的公式,就不會出現垃圾回收時的promotion failed。
SoftRefLRUPolicyMSPerMB這 個參數是softly reachable objects will remain alive for some amount of time after the last time they were referenced. The default value is one second of lifetime per free megabyte in the heap。

可是,這種設置方法由於沒有用到Survivor,因此Old區容易滿,CMS執行會比較頻繁。建議仍是用Survivor並把加大,這樣也不會有promotion failed錯誤。配置上32位 和64位不同,64位系統只要配置MaxTenuringThreshold參數,CMS仍是有暫停。爲了解決暫停問題和promotion failed問題,最後我設置-XX:SurvivorRatio=1 ,並把MaxTenuringThreshold去掉,這樣即沒有暫停又不會有promotoin failed,並且更重要的是,Old區和PermGen上升很是慢(由於好多對象到不了年老代就被回收了),因此CMS執行頻率很是低,好幾個小時才執 行一次,這樣,服務器都不用重啓了。下面是64位的配置,系統8G內存。

-Xmx4000M -Xms4000M -Xmn600M -XX:PermSize=500M -XX:MaxPermSize=500M -Xss256K -XX:+DisableExplicitGC -XX:SurvivorRatio=1 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSClassUnloadingEnabled -XX:LargePageSizeInBytes=128M -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintClassHistogram -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -Xloggc:log/gc.log

【JVM啓動參數介紹】

-Xmn -Eden Generation的Heap大小,通常設置爲Xmx的1/3或1/4

-Xmx -設置JVM Heap大小最大值,這裏的heap = New Generation + Old Generation,但不包括PermGen

-Xms -設置JVM Heap大小初始值

-XX:NewRatio -New/Old的大小比率

-XX:NewSize -New Generation Heap的大小

-XX:MaxNewSize -能夠經過NewRatio和-Xmx計算獲得

-XX:SurvivorRatio -Eden/Survivor Space大小比率

-XX:PermSize -PermGen的初始值

-XX:MaxPermSize -PermGen最大值

-Xss: -設置每一個線程的Stack大小

-XX:+UseParNewGC -表示多CPU下縮短Minor GC的時間

-XX:+UseParallelGC -設置後可使用並行清除收集器【多CPU】

-XX:+ParallelGCThreads -可用來增長並行度【多CPU】

-XX:+AggressiveOpts -是否激活最近的試驗性性能調整

-XX:-Xnoclassgc -是否容許類垃圾收集,默認設置是容許類 GC

-XX:+UseLargePages -是否支持大頁面堆

-XX:+UseFastAccessorMethods -在指定了這個參數後,JDK會將全部的get/set方法都轉爲本地代碼

-XX:+UseConcMarkSweepGC -縮短major收集的時間,此選項在Heap比較大並且Full GC時間較長的狀況下使用更合適

另外,JVM的一些參數能夠輸出有效的日誌文件:

-verbose:gc -輸出一些gc信息

-XX:+PrintGCDetails -輸出gc詳細信息

-XX:+PrintGCTimeStamps -包含時間戳信息

-XX:+PrintHeapAtGC -包括gc先後Heap情況

-XX:+PrintTenuringDistribution -輸出對象存活時間和Tenured Generation的其餘信息

-XX:+PrintHeapUsageOverTime -以時間戳輸出heap利用率和容量信息

-Xloggc:filename -輸出gc信息到日誌文件
例如:

set JAVA_OPTS=%JAVA_OPTS% -verbose:gc -Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC

若是輸出的日誌文件很是大,能夠後續採用其它工具分析;或者乾脆使用Jstat或Jconsole這些成熟的工具直接監控JVM的GC問題。固然,前邊圖示的VisualVM更是一款漂亮的、即時監控JVM GC的工具。

下面的命令把整個堆設置成128m,New/Old比率設置成3,即New與Old比例爲1:3,New Space爲Heap的1/4即32M, 缺省值爲NewSize=2m MaxNewSize=32m SurvivorRatio=2

java –Xms128m –Xmx128m –XX:NewRatio=3

可是,若是JVM的堆Heap大小大於1GB,則應該使用值:-XX:newSize=640m -XX:MaxNewSize=640m -XX:SurvivorRatio=16,或者將堆的總大小的50%到60%分配給新生成的池。

【JVM性能瓶頸】

在JAVA應用程序中,雖然不太容易出現內存泄漏的問題,由於JVM會不按期的進行垃圾回收。可是由於程序的不合理寫法,也會致使一些數據不能被收集。典 型的情況是在HashMap中放置大量不用的數據,而沒有及時的清理。在web應用中,不少人喜歡在Session放放置狀態數據,而沒有清理,也是內存 泄漏的一個緣由。在Session中存放數據還好,由於Session終究會有過時時間,可是若是在Class的Static變量中放置數據,那就怎麼樣 也沒辦法了。診斷應用中是否存在內存泄漏也有一些方法,經過分析JVM GC Log就是一個直觀的方式。經過分析GC After Heap的變化趨勢,若是GC After Heap穩步上升,馬上進行Full GC後,仍然不能降下來,一般就意味着存在內存泄漏了。固然也有狀況是,的確有一些數據是應用程序的需求,咱們要確認除了這些數據,是否還存在一些異常數 據一直佔據內存。能夠經過JProbe的Memory Views來觀察Class的對象數,在一段請求事後,若是還存在一些Class的Instance數目至關多,就能夠判斷這個Class可能會是問題的 根源。

相關文章
相關標籤/搜索