優化Java堆大小的5個技巧

55bd 13年前發布 | 35K 次閱讀 Java Java開發
摘要:Java堆容量不足可以對性能造成很大影響,這樣無疑就給程序帶來不可必要的麻煩,本文總結了影響Java堆容量不足的五大原因以及巧妙地去優化?

本文作者Pierre是一名有10多年經驗的高級系統架構師,他的主要專業領域是Java EE、中間件和JVM技術。根據他多年的工作實踐經驗,他發現許多性能問題都是由Java堆容量不足和調優引起的。下面他將和大家分享非常實用的5個Java堆優化技巧。

1.JVM:對難以理解的東西產生恐懼感

千萬不要以為,通過配置,調優,就可以排除那些你所不明白的問題。有些人認為Java程序員不需要知道內部JVM內存管理。毫無疑問,這種觀點明顯是錯誤的,如果想拓寬知識面和提升排除故障能力,你就必須要了解和學習一下JVM內存管理。

對于Java或者是Java EE新手來說,Java Heap調優和故障排除是一項非常有挑戰的工作。下面會提供一些典型的案例場景:

客戶端環境面臨著有規律的OutOfMemoryError錯誤并且對業務造成了很大的影響。

你的開發團隊要在如此大的壓力下去解決這個問題,通常會怎么做?

  1. 用谷歌搜索引擎找到類似的問題并且你會相信(或假設)你也面臨同樣的問題。
  2. 你會抓住JVM-Xms和存在OutOfMemoryError異常這幾個關鍵字的例子,然后希望通過這樣的案例來快速解決客戶端問題。
  3. 最后你會在你環境中使用相同的調優方法。兩天后,問題仍然發生(甚至更糟或者稍微好點)……

到底是哪里錯了呢?

首先,沒有摸清問題根源所在?對開發環境沒有正確地進行深層面(規格、負載情況等)理解。網絡搜索是一個非常優秀的學習方法和知識分享工具,但是你必須結合自己的實際項目,從根本上進行分析解決。

可能缺乏基本的JVM和JVM內存管理技能,阻止你把所有的點給連接起來。

今天講的第一條技巧是幫助你理解基本的JVM原則及其與眾不同的內存空間。這些知識都是相當重要的,它可以幫助你做出有效的調優策略、更加正確合理的預測將來會產生的影響、提前知道未來需要做哪些調優工作。下面來看一下JVM參考指南:

JVM內存分為3個內存空間

  • Java Heap:適用于所有的JVM廠商,通常用來拆分YoungGen(幼苗)和OldGen(終身享用)空間。
  • PermGen(永久代):適用于Sun HotSpot VM((PermGen空間在Java7或者Java8更新中將會被刪除)
  • Native Heap(C-Heap):適用于所有的JVM廠商。

建議把下面的文章都能看一遍,最好把Sun的Java內存管理白皮書和OpenJDKS實現下載下來并仔細閱讀。

正如你所看到的,JVM內存管理比使用Xmx設置最大值更為復雜。你需要查看每個角度,包括本地和PermGen需求以及從主機上查看物理內存可用性(CPU core)。

在較大的Java Heap和較小的本地Heap比賽中,32位虛擬機可能會變得相當棘手。試圖在一個32位VM如2.5GB+上設置一個大型堆,根據應用程序占用和線程數量等因素會增加OutOfMemoryError這個異常拋出。64位JVM可以解決這個問題,但物理資源可用性和垃圾回收成本仍然是有限制的(成本主要集中在GC大小收集上)。最大并不表示是最好的,所以請不要假設在一個16GB的64位虛擬機上可以運行20個Java EE應用程序。

2.數據和應用程序為王:回顧靜態占用需求

應用程序以及相關數據將決定Java堆空間占用需求。通過靜態內存,可“預測”下面的內存需求:

  • 確定將會有多少不同的應用程序部署到預先計劃的一個單獨的JVM進程上,例如有多少個ear文件、war文件、jar文件等。在一個JVM上部署的應用程序越多,對本機堆的需求就越多。
  • 確定有多少個類需要在運行時加載:包括第三方API。越多的類加載器和類在運行時被加載,在HotSpot VM PermGen空間和內部JIT相關優化對象上的需求就越高。
  • 確定數據緩存占用,如應用程序加載內部緩存數據結構(和第三方API),例如數據庫中的數據緩存,從文件中讀取數據等。數據緩存使用越多,Java Heap OldGen空間需求就越高。
  • 確定允許建立的中間件線程數量。這是非常重要的,因為Java線程需要足夠的本機內存,否則會拋OutOfMemoryError異常。

在JVM進程上部署的應用程序越多,對本地內存和PermGen空間的要求就越高。數據緩存并不是序列化為一個磁盤或數據庫,它將從OldGen空間里面需要額外的內存。

設法對靜態內存占用進行合理的評估,在真正進行數據測試之前,設置一些JVM能力起點是非常有用的。對于32位JVM,通常不推薦一個Java堆大小超過 2 GB(-Xms2048m,-Xmx2048m),對于Java EE應用程序和線程來說這樣將需要足夠的內存和本機堆PermGen。

這個評估是非常重要因為太多的應用程序部署在一個32位JVM進程上很容易導致本機堆耗盡;尤其是在多重線程環境。

對于64位JVM, 一個3GB或者4GB的Java堆/JVM進程是推薦的起點。

3.業務流量設置規則:審查動態內存占用需求

業務流量通常會決定動態內存占用。通過觀察各種監控工具可以發現并發用戶與請求生成的JVM GC“心跳”,這是由于頻繁的創建和垃圾回收短期或者長期對象。

一個典型的32位JVM,Java堆大小設置在2 GB(使用分代&并發收集器)通常為500 MB YoungGen分配空間和1.5 GB的OldGen空間。

最大限度地減少重大GC收集的頻率是獲得最佳性能的關鍵因素,所以在高峰的時候理解和評估需要多少內存是非常重要的。

再次聲明,應用程序類型和數據將決定內存需求。購物車的應用程序類型(長期居住的對象)涉及大型和非序列化會話數據,這個通常需要大型Java堆和很多 OldGen空間。無狀態和XML處理(很多短命的對象)繁重的應用程序需要適當YoungGen空間,以盡量減少頻率主要集合。

例如:

你有5個ear應用程序(2000多個Java類)要部署(包含中間件代碼)

  1. 本地堆需求估計為1GB(必須足夠大以處理線程創建等等。)PermGen空間大約是512 MB。
  2. 內部靜態緩存大約500MB
  3. 在高峰時間,總預測流量是5000個并發用戶
  4. 每個用戶的會話數據大約500K
  5. 在高峰期間,總流量會話要求是2.5GB。

正如你所看到的一樣,在如此情況下,32位JVM進程就無法滿足。一個典型的解決方案是進行流量拆分,在幾個JVM進程或物理主機(假設有足夠的硬件和CPU core可用)上。

大多數時候,業務流量將推動內存占用。除非你需要大量的數據緩存來實現適當的性能,典型的門戶應用網站(媒體)繁重的應用程序需求。數據緩存太多的時候應該用一個黃色的標志標注一下,最好早點去重新審視一下一些設計元素。

4.量體裁衣

這一條,你應該做到:

  1. 理解基本的JVM原則和內存空間。
  2. 對所有應用程序有深入的了解及其它們的特點(大小、類型、動態流量、無狀態對象VS有狀態對象、內部內存緩存等)。
  3. 對預測業務流量(并發用戶)給每一個應用程序能提出很好的觀點—如果你需要一個64位的虛擬內存,那么將設置哪個作為開始。

如果需要多個JVM(中間件)過程。

等一下,這樣做并不足夠。雖然上面的信息是至關重要的,并且關于Java堆的設置進行了“最佳猜測”,對應用程序的行為進行模擬并且進行適當的分析、負載和性能測試來驗證Java堆內存要求。

推薦Jprofiler工具給大家,學習如何使用一個分析器的最好方法是正確理解應用程序的內存占用。另一個方法是使用Eclipse MAT工具根據現有的環境進行堆轉儲分析。堆轉儲非常強大,它可以允許你查看和理解Java堆的整個內存占用,包含類加載器相關數據和在內存占用分析中必須要做的,特別是內存泄漏。

Java分析器和堆轉儲分析工具允許你理解和驗證應用程序內存足跡,包含內存泄漏的檢測和解決方案。負載測試和性能測試是必不可少的,通過模擬并發用戶來驗證早期評估是否正確,它也會把應用程序瓶頸暴露出來并且允許你進行微調。推薦一個非常容易上手的工具:Apache Jmeter。

最后將看一下這樣的情況,應用程序在Java EE環境非常正常,直到有一天完全正常的設備啟動失敗,例如硬件問題。突然的環境運行能力下降和整體環境下降,到底發生了什么?

引起“多米諾效應”的原因有很多,但缺少JVM調優和處理故障轉移的能力(短期額外負荷)是很常見的。如果JVM進程運行在80% + OldGen空間容量和頻繁的垃圾收集,你如何預期故障轉移場景?

前面模擬的負載和性能測試應該模擬這樣的場景,調整你的調優設置使您的Java堆有足夠的緩沖來處理額外的負載(額外的對象)在短期內。這主要適用于動態內存占用,由于故障轉移意味著將重定向一些固定的并發用戶給可利用的JVM進程(中間件實例)。

5.分而治之

這一條的前提是你已經完成了幾十個負載測試。JVM已經不存在泄露,你的應用程序內存不能再進行任何減少。你已經嘗試了幾個調優策略,例如使用一個64位的Java堆空間在10GB以上。多個GC策略,盡管這樣,仍然沒有找到合適的可以接受的性能水平?

與當前的JVM規范相比,適當的垂直和水平伸縮,包括在每個物理主機和跨多個主機上建立JVM進程來滿足整個吞吐量和容量。如果在幾個邏輯倉、自身的JVM進程、線程和調優值里打破應用程序列表那么IT環境的容錯能力將更強大。

“分而治之”策略包括拆分應用程序流量到多個JVM進程,下面提供一些拆分技巧:

  1. 減少每個JVM進程的Java堆大小(靜態和動態的占用)
  2. 降低JVM調優復雜度。
  3. 減少GC流失和暫停每個JVM進程
  4. 增加冗余和故障切換功能
  5. 排列最新的Cloud和IT虛擬化戰略

當你發現已經花費了大量的時間在64位JVM進程調優上,是時候該好好審視一下你的中間件和JVM部署策略并且利用垂直和水平縮放。這條策略的實現需要更多的硬件支持,但是從長遠角度來看,是非常有效和有益的。(張紅月/編譯)

 本文由用戶 55bd 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!