1. 程式人生 > >深入理解JVM(五)——JVM調優 Eclipse調優

深入理解JVM(五)——JVM調優 Eclipse調優

在開發,測試環境,我們可以通過JConsole或者VisualVM去監控Java程式的執行時,但是生產環境是不會給你安裝這些應用的。JDK1.6之後,JMX管理預設都是開啟的,所以你也可以通過JMX管理達到監控和調優的目的。這也是我下一階段的工作一部分。

大體的需求包括:

  1. 顯示虛擬機器程序以及程序的配置,環境資訊(jps,jinfo)
  2. 監視應用程式的CPU,GC,堆,方法區以及執行緒資訊(jstat,jstack)
  3. dump以及分析堆轉儲快照(jmap,jhat)
  4. 方法級的程式執行分析,找出被呼叫最多的,執行時間最長的方法
  5. 離執行緒序快照:收集程式的執行時配置,執行緒dump,記憶體dump等資訊建立一個快照,可以將離線發給開發者進行BUG反饋

下面介紹JVM優化案例

高效能硬體上的程式部署策略

背景:日PV量15萬左右的線上文件型別網站,硬體升級前使用32位系統1.5GB的堆,使用者剛到網站比較緩慢,不會停頓。

現狀:最近更換硬體,新的硬體為4個CPU,16GB實體記憶體,作業系統64位的JDK1.5,Resin作為Web伺服器。設定-Xmx和-Xms引數將Java堆固定在12GB。

問題:使用一段時間後並不理想,網站經常出現不定期長時間失去響應的情況。

原因:監控伺服器執行情況發現網站失去響應是由GC停頓導致的,虛擬機器執行在Server模式,預設吞吐量優先收集器,回收12GB的堆,一次Full GC的停頓時間高達14秒。由於程式設計是文件訪問,很多大的物件都進入老年代,即使有12G的堆,也會被很快耗盡。

方案:選擇多個32位虛擬機器建立邏輯叢集來利用硬體資源(部署多個應用服務程序,在前端搭建一個負載均衡器,以反向代理的方式來分配請求)

實施:建立了5個32位JDK的邏輯叢集,每個程序按照2GB記憶體計算(堆固定在1.5GB),佔用了10GB記憶體。建立Apache伺服器作為前端均衡代理訪問門戶。考慮使用者對響應速度關心,並且文件伺服器壓力集中在磁碟和記憶體訪問,CPU資源敏感較低,改為CMS收集器。

使用64為管理大記憶體有以下問題:

  • 記憶體回收導致的長時間停頓
  • 現階段,64位JDK的效能測試結果普遍低於32位JDK
  • 需要保證程式足夠穩定,因為這種應用堆溢位幾乎無法產生的堆轉儲快照,因為太大了(十幾GB甚至更大的Dump檔案)
  • 相同程式在64位消耗的記憶體比32位大,由於指標膨脹,以及資料型別對齊補白等

叢集間同步導致的記憶體溢位

背景:基於B/S的MIS系統,硬體為兩臺2個CPU,8GB記憶體的HP小型機,伺服器為WebLogic9.2,每臺機器有3個例項。節點之間沒有session同步,但有需求需要實現部分資料在節點之間共享。

現狀:採用JBoosCache構建了一個全域性快取,全域性快取啟動後,伺服器正常使用了一段時間,但最近不定期出現記憶體溢位的問題。

問題:記憶體溢位

原因:加上引數-XX:+HeapDumpOnOutOfMemoryError執行一段時間,最近一次溢位,分析heapdump檔案,發現jgroups異常。JBoosCache是通過jgroups進行叢集間通訊的,使用協議棧的方式收發資料包,資訊在傳輸失敗需要重發,在收到確認訊息之前,所有的訊息必須在記憶體中保留。而MIS中有一個全域性的Filter,每次收到請求會更新最後操作時間,並且同步到每個節點,導致叢集之間互動頻繁,重發資料在記憶體中不斷堆積。

方案:JBoosCache官方討論過記憶體溢位的問題並在後續版本中改進。MIS在同步最後操作時間,實現方案上的缺陷改進。

堆外記憶體導致的溢位錯誤

背景:學校的小型專案,基於B/S的電子考試系統,實現客戶端能從服務端接受資料,逆向Ajax技術,Jetty7.1.4 普通PC機i5 CPU 4GB記憶體 32位Windows作業系統。

現狀:伺服器不定時丟擲記憶體溢位異常

問題:記憶體溢位

原因:加上引數-XX:+HeapDumpOnOutOfMemoryError執行一段時間,沒有任何反應,GC也並不頻繁,Eden,Survivor,老年代都很穩定。記憶體溢位後,從系統日誌找到堆疊。
作業系統對每個程序管理的記憶體時有限制的,32位的系統,最大使用的記憶體時2GB,JVM使用了1.6GB,剩下的只有0.4GB。雖然收集器也會堆Direct Memory區域進行回收,但是隻是在Full GC後順便回收。而且Direct Memory也不會通知收集器,直到異常。
本系統恰好有大量的NIO操作使用到Direct Memory記憶體

外部命令導致系統緩慢

背景:一個數字校園應用系統,大併發測試時發現請求響應比較慢。

問題:請求響應比較慢

原因:通過mpstat工具發現CPU使用率很高,並且絕大數資源消耗並不是系統本身。DTrace執行後發現消耗CPU最多的時fork系統呼叫,而fork時Linux用來產生新程序的,JVM只有執行緒,不應該有程序。
系統設計時每個使用者請求都需要執行一個外部shell指令碼,通過Java的Runtime.getRuntime().exec()方法呼叫,這種方法能達到目的,但是非常消耗資源。

Runtime.getRuntime().exec()是首先克隆一個和當前虛擬機器擁有一樣環境標量的程序,再用這個新的程序去執行外部命令,最後再退出這個程序。不僅CPU資源消耗大,記憶體負擔也很重

伺服器JVM程序崩潰

等待的執行緒和Socket連線數太多,超過虛擬機器的承受能力,導致虛擬機器崩潰。

不恰當的資料結構導致記憶體佔用過大

背景:一臺RPC的伺服器,平時地外服務,GC在30毫秒左右,完全可以接受。

問題:業務上需要每10分鐘載入一個約80MB的資料檔案到記憶體中,會有超過100萬個HashMap,鍵值對為Long,Long,這段時間GC時間會超過500ms.

原因:複製演算法對在對付朝生夕滅的物件時很高效。但是在分析800MB資料期間,Eden空間很容易就滿了,而物件還都存活,複製大量的物件到Survivor並且更新引用,導致GC暫停明顯。

方案:從 GC優化的角度,將Survivor空間去掉。(-XX:SurvivorRatio-65535,-XX:MaxTenuringThreshold=0或者-XX:AlawaysTenure);從程式設計的角度,只有HashMap存放的key和value有效,不需要使用裝箱類。本來只有2*8Byte的,裝箱之後多了8Byte的MarkWord,8Byte的Class指標,再加上8Byte的資料。組裝成Entry,有多了16Byte的頭,8Byte的next,4Byte的hash欄位,4Byte的空白填充。空間利用率是16Byte/88Byte=18%,實在太低了。

由Windows虛擬記憶體導致的長時間停頓

問題:Java的GUI程式在程式最小化時,它的工作記憶體被自動交換到磁碟的頁面檔案中了,這樣GC時就因為恢復頁面檔案的操作而導致不正常GC停頓

方案:Java的GUI程式要避免這種情況發生,可以加入引數“-Dsun.awt.keepWorkingSetOnMinimize=true”,這個引數很多桌面程式都會用到,如VisualVM,用來保證程式在恢復最小化時能夠立即響應。

Eclipse調優

JVM調優不僅限於服務端應用,也可以自己調優測試,如Eclipse應用。

執行平臺32位Windows系統,i5 CPU,4GB記憶體。

初始配置檔案在eclipse.ini中,作者改了JDK路徑,設定最大堆512MB,開啟了JMX管理,其它未做改動。

由於安裝了很多外掛,啟動較慢。

eclipse程式碼規模較大,增加永久代記憶體,預設時64MB
-Dcom.sun.management.jmxremote
-Dosgi.requiredJavaVersion=1.5
-Xmx512MB
-XX:MaxPermSize=256MB
去掉類載入時的位元組碼驗證,如果覺得eclipse編譯是的位元組碼是可靠的話
-Xverify:none
通過VisualVM發現啟動eclipse時,頻繁GC,由於新生代記憶體分配不足導致的

新增的配置如下:
-Xverify:none
-Xmx512m
-Xms512m
-Xmn128m
-XX:PermSize=96m
-XX:MaxPermSize=96m

最後還可以按照之前的文章,為eclipse配置垃圾收集器,為了減少響應時間,可以選擇CMS收集器,當然優化的時候可以配合VisualVM工具,來觀察優化的結構,進而調整優化引數

最終新增的配置如下:
-Xverify:none
-Xmx512m
-Xms512m
-Xmn128m
-XX:PermSize=96m
-XX:MaxPermSize=96m
-XX:+DisableExplicitGC
-Xnoclassgc
-XX:+UseParNewGC
-XX:+UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=85