JVM參數對J2EE性能優化的影響

    一切應用都是基于的,那么對于JVM的設置和監控,成為J2EE應用程序性能分析和的必然手段。今天Sincky和大家交流該話題。這里以Tomcat環境為例,其它WEB服務器如Jboss、Weblogic、Websphere完全一致。

 

【認識JVM

 

    首先我們來看一張圖,這是目前JDK1.6自帶的JVM性能監控工具VisualVM的一個插件VisualGC的顯示情況。讓我們先來了解JVM的堆Heap管理模式,要調整JVM,自然要知道它的內部結構和,此乃“知己知彼,百戰不殆”!

 

    JVMHeap包括三部分,分別是Permanent Generation(簡稱PermGen)、New Generationured Generation(又叫Old )。

 

    PermGenJVM自用的區域,是內存的永久保存區,用于存放反射代理和Class,Class在被裝載時就會被放到PermGen中;所以如果WEB服務器里應用的Class相當多時,就可以考慮將這一塊區域放大一些。

 

    NewOldJava應用的Heap區,用來存放類的實例Instance的;其中New Generation的目標就是盡可能快速的收集掉那些生命周期短的對象;New Generation又分為Eden Space,From SpaceTo Space三塊,Eden Space用于存放新創建的對象,From區和To區都是救助空間Survivor Space(圖中的S0S1);當Eden區滿時,JVM執行垃圾回收GCGarbage Collection),垃圾收集器暫停應用程序,并會將Eden Space還存活的對象復制到當前的From救助空間,一旦當前的From救助空間充滿,此區的存活對象將被復制到另外一個To區,當To區也滿了的時候,從From區復制過來并且依然存活的對象復制到Old區,從而FromTo救助空間互換角色,維持活動的對象將在救助空間不斷復制,直到最終轉入Old域。需要注意,Survivor的兩個區是對稱的,沒先后關系,所以同一個區中可能同時存在從Eden復制過來對象,和從前一個Survivor復制過來的對象,而且,Survivor區總有一個是空的。同時,根據程序需要,Survivor區是可以配置為多個的(多于兩個),這樣可以增加對象在年輕代中的存在時間,減少被放到年老代的可能。每一次垃圾回收后,Eden Space都會被清空。

 

    Old區用于存放長壽的對象,在New區中經歷了N次垃圾回收后仍然存活的對象,就會被放到Old區中;如那些與業務信息相關的對象,包括Http請求中的Session對象、、Socket連接,這類對象跟業務直接掛鉤,因此生命周期比較長。當任何一個空間不夠用時,都會促使JVM執行垃圾回收,而垃圾回收也需要消耗一定的時間,從而造成應用程序卡??;因此,的設置各個區的大小,可以進行快速GCMimor 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、OldPermGen,所以比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服務器對P進行預編譯的時候發生。如果你的WEB 下都用了大量的第三方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??梢岳?/span>JVM提供的-Xmn -Xms -Xmx等選項可進行設置。提示:在JVM中如果98%的時間是用于GC且可用的Heap大小不足2%的時候將拋出此信息。Heap最大不要超過可用物理內存的80%,一般的要將-Xms-Xmx為相同,而-Xmn1/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的垃圾回收機制,允許分隔內存池以包含不同時效的對象,垃圾回收循環根據時效收集與其他對象彼此獨立的對象。使用其他參數,您可以單獨設置內存池的大小。為了實現更好的性能,可以對包含短期存活對象的池的大小進行設置,以使該池中的對象的存活時間不會超過一個垃圾回收循環。新生成的池的大小由 NewSizeMaxNewSize參數確定。如:

 

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

 

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

 

    這個問題一般由兩種原因引起,第一個原因是Survivor空間不夠,里面的對象還不應該被移動到Old區,但New區又有很多對象需要放入Survivor;第二個原因是Old區沒有足夠的空間接納來自New區的對象;這兩種情況都會轉向Full GC,造成系統卡住時間較長。第一個原因可以通過去掉救助空間解決,設置-XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0即可,第二個原因可以設置InitiatingOccupancyFraction為某個值(假設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。在這里Xmx6000,Xmn500,那么Xmx-Xmn5500m,也就是Old5500m,CMSInitiatingOccupancyFraction=90說明Old90%滿的時候開始執行對Old區的并發垃圾回收(CMS),這時還剩10%的空間是5500*10%=550m,所以即使Xmn(也就是New500m)里所有對象都搬到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 GenerationHeap大小,一般設置為Xmx1/31/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:+PrintHeapOverTime  -以時間戳輸出heap利用率和容量信息

 

-Xloggc:filename  -輸出gc信息到日志文件

    例如:

 

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

 

     如果輸出的日志文件非常大,可以后續采用其它工具分析;或者干脆使用JstatJconsole這些成熟的工具直接監控JVMGC問題。當然,前邊圖示的VisualVM更是一款漂亮的、即時監控JVM GC的工具。

 

    下面的命令把整個堆設置成128m,New/Old比率設置成3,即NewOld比例為13,New SpaceHeap1/432M, 缺省值為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終究會有過期時間,但是如果在ClassStatic變量中放置數據,那就怎么樣也沒辦法了。診斷應用中是否存在內存泄漏也有一些方法,通過分析JVM GC Log就是一個直觀的方式。通過分析GC After Heap的變化趨勢,如果GC After Heap穩步上升,立刻進行Full GC后,仍然不能降下來,通常就意味著存在內存泄漏了。當然也有情況是,的確有一些數據是應用程序的需求,我們要確認除了這些數據,是否還存在一些異常數據一直占據內存??梢醞ü?/span>JProbeMemory Views來觀察Class的對象數,在一段請求過后,如果還存在一些ClassInstance數目相當多,就可以判斷這個Class可能會是問題的根源。


來源:博客園

上一篇: Java中的值傳遞和引用傳遞

下一篇: Oracle中實現分頁的SQL語句和在JSP頁面實現分頁技術

分享到: 更多
牌九技巧口诀推 中国vs尼日利亚 二人斗地主吉祥棋牌 17年重庆时时彩骗局 北京时时规律破解教程 极速pk10技巧 极 哪个平台有秒速时时 排列三组三全包会赔吗 二分pk拾精准计划 mg4377登录地址 可以投注天津时时的 11选5算法计划软件 重庆时时彩开奖 欢乐生肖开奖官网走势图 pk10软件计划 pk10大小单双十大技巧