《深入理解Java虛擬機器》第5章 調優案例分析與實戰
5.2.1高效能硬體上的程式部署策略
監控伺服器執行狀況發現網站沒有響應是由GC停頓導致的,虛擬機器執行在Server模式,預設使用吞吐量優先收集器,回收12GB的堆,一次Full GC的停頓時間高達14秒。訪問文件把其從磁碟提取到記憶體中,導致記憶體中出現很多由文件序列化產生的大物件。這些大物件很多進入了老年代,沒有在Minor GC中被清理掉。
因此記憶體很快被消耗殆盡。
在高效能硬體上部署程式,目前主要有兩種方式:
- 通過64位JDK使用大記憶體。
使用若干個32位虛擬機器建立邏輯叢集利用硬體資源:
在一臺物理機器上啟動多個應用伺服器程序,給每個伺服器程序分配不同的埠,然後在前端搭建一個負載均衡器
仍受到32位記憶體限制:32位WINDOWS每個程序限制2GB記憶體,堆一般最多開到1.5G記憶體。
- 避免節點競爭全域性的資源,IO異常。
大量用本地快取,在邏輯叢集中會造成較大的記憶體浪費。考慮改為集中式快取。
控制Full GC頻率:大多數物件的生存時間不能太長,保障老年代空間的穩定。
系統的CPU資源敏感度較低時,改為CMS收集器進行垃圾回收。5.2.2 叢集同步導致的記憶體溢位
JBossCache和MIS(管理資訊系統)實現的缺陷。能多讀不能多寫。服務過程中,一個頁面會產生數次乃至數十次的請求,因此這個過濾器導致叢集各個節點間的網路互動非常頻繁。網路情況不能滿足傳輸要求時,重發資料在記憶體中不斷堆積,記憶體溢位。
5.2.3 堆外記憶體導致的溢位錯誤
為了實現客戶端能實時地從服務端接受資料,使用了AJAX技術,伺服器是Jetty。
溢位關鍵:GC時,VM雖然會對直接記憶體進行回收,但直接記憶體不像新生代和老年代那樣,發現空間不足了就通知收集器進行垃圾回收。它只能等待丟擲記憶體溢位異常時,在cathch欄位裡請求System.gc()。如果VM不聽,我們只能眼睜睜堆還有記憶體,卻丟擲溢位。
本案例用到的CometD框架,正好有大量NIO操作用到Direct Memory。除了Java堆和永久代外,還有
Direct Memory
- 執行緒堆疊
- Socket快取區
- JNI
- VM和GC
佔用了較多記憶體
5.2.4 外部命令導致系統緩慢
CPU佔用很高,佔用多的不是應用本身。是”fork“系統呼叫(Linux用來產生新程序的)。
應該去掉這個shell指令碼執行的語句,改為Java的API去獲取這些資訊。
5.2.5 伺服器JVM程序崩潰
OA(辦公自動化門戶)
頻繁叢集節點的VM程序自動關閉的現象。
利用SoapUI兩邊服務不對等,等待的執行緒和Socket連線越來越多。超過VM的承受能力。
非同步呼叫改成生產者/消費者模式的訊息佇列實現
5.3 實戰:eclipse執行速度調優
VM的執行資料通過VisualVM及其擴充套件外掛VisualGC進行採集。
得出是永久代的容量問題。
JDK1.5有4個引數,多出XXMaxPernSize這個引數。
JDK1.6的程式提供商由Sun變為Oracle。把預設值64MB改為最大256MB即可。
JDK1.6的類載入速度比1.5慢,但總體仍有優勢。
關於虛擬機器執行模式
client模式使用的是代號C1的輕量級編輯器,server模式使用C2編譯器。
5.3.4 調整記憶體設定控制垃圾收集頻率
每當發生一次GC,所有的使用者執行緒都必須跑到最近的一個安全點,然後掛起執行緒等待垃圾回收。過於頻繁的GC會導致很多沒有必要的安全點檢測、執行緒掛起以及恢復操作。
新生代的Minor GC頻繁發生,因為VM分配給新生代的空間太小。
Full GC次數少,但佔了絕大部分的GC時間。大多數由老年代容量擴充套件導致的。為了避免,可以把-Xms和-XX:PermSize引數值分別設定為-Xmx和-XX:PermSizeMax引數值。強制VM把老年代和永久代的容量固定下來。避免執行時自動擴充套件。
5.3.5 選擇收集器降低延遲
新生代用ParNew,老年代用CMS(預設老年代使用68%就收集)。避免總體吞吐量下降厲害,用-XX:CMSInitiatingOccupancyFraction=85將GC臨界值提升到85%
服務端的調優還有資料庫,資源池,磁碟I/O等。
下面是書中的Eclipse配置:
希望這篇能對讀者有所啟發。