萬盛學電腦網

 萬盛學電腦網 >> 腳本專題 >> javascript >> JVM性能優化,Java的伸縮性

JVM性能優化,Java的伸縮性

  很多程序員在解決JVM性能問題的時候,花開了很多時間去調優應用程序級別的性能瓶頸,當你讀完這本系列文章之後你會發現我可能更加系統地看待這類的問題。我說過JVM的自身技術限制了Java企業級應用的伸縮性。首先我們先列舉一些主導因素。

  l 主流的硬件服務器提供了大量的內存

  l 分布式系統有大量內存的需求,而且該需求在持續增長

  l 一個普通Java應用程序所持有的對空間大概在1GB~4GB,這遠遠低於一個硬件服務器的內存管理能力以及一個分布式應用程序的內存需求量。這被稱之為Java內存牆,如下圖所示(圖中表述Java應用服務器和常規Java應用的內存使用量的演變史)。

  圖 1 Java內存牆(1980~2010)

Java內存牆

  Java內存牆

  這給我們帶來了如下JVM性能課題:

  1) 如果分配給應用程序的內存太小,將導致內存不足。JVM 不能及時釋放內存空間給應用程序,最終將引發內存不足,或者JVM完全關閉。所以你必須提供更多的內存給應用程序。

  2) 如果給對響應時間敏感的應用增加內存,如果不重啟你的系統或者優化你的應用,Java堆最終會碎片化。當碎片發生時,可能會導致應用中斷100毫秒~100秒,這取決與你的Java應用,Java堆的大小以及其他的JVM調優參數。

  關於停頓的討論大部分都集中在平均停頓或者目標停頓,很少涉及到堆壓縮時的最壞停頓時間,在生產環境中堆中每千兆字節的有效數據的都將會發生大約1秒的停頓。

  2~4秒的停頓對大多數企業應用來說都是不能接受的,所以盡管實際的Java應用實例可能需要更多的內存空間,但實際只分配2~4GB的內存。在一 些64位系統中帶有很多關於伸縮性的JVM調優項,使得這些系統可以運行16GB乃至20GB的堆空間,並能滿足典型響應時間的SLA。但是這些離現實較 遠,JVM目前的技術無法在進行堆壓縮時避免停頓應用程序。Java應用開發人員苦於處理這兩個為我們大多數人所抱怨的任務。

  l 架構/建模在大量的實例池之上,隨之而來的是復雜的監控和管理操作。

  l 反復的JVM和應用程序調優以避免“stop the world“引起的停頓。大多數程序員希望停頓不要發生在系統峰值負載期間。我稱之為不可能的目標。

  現在讓我們深入一點Java的可伸縮性問題。

  過度供給或過度實例化Java部署

  為了充分利用內存資源,普通的做法是將Java應用部署在多個應用服務器實例上而不是一個或者少數應用服務器實例上。當一台Server上運行16 個應用服務器實例可以充分利用所有的內存資源,但如此無法解決的是多實例的監控以及管理所帶來的成本,尤其是當你的應用部署在多個Server上。

  另一個問題來了,峰值負載時的內存資源不是每天都需要的,這樣就形成了巨大的浪費。有些情況下,一台物理機上可能只不是不超過3個“大應用服務器實例”,這樣的部署更加不夠經濟也不夠環保,尤其在非峰值負載期間。

  讓我們來比較一下這兩種部署架構,下圖中左邊是多而小的應用服務器實例部署模式,右邊是少而大的應用服務器實例部署模式。兩種模式處理同樣的負載,究竟哪一種部署架構更具經濟性。

  圖2 大應用服務器部署場景

jvmperf5-fig2

  上圖源自:Azul Systems

  如我之前說過的,並發壓縮使得大應用服務器部署模式變得可行,而且可以突破JVM可伸縮性的限制。目前只有Azul的Zing JVM可以提供並發壓縮的技術,另外Zing是Server側的JVM,我們很樂意看到越來越多的開發者在JVM層面去挑戰Java可伸縮性的問題。

  由於性能調優仍然是我們解決Java可伸縮性問題的主要手段,我們先來看有哪些主要的調優參數以及通過它們能達到什麼樣的效果。

  調優參數:一些事例

  最著名的調優參數莫過於”-Xmx”了,通過該參數可以指定Java的堆空間大小,實際上可能不同的JVM執行結果不太一樣。

  有的JVM包含了內部結構(如編譯器線程,垃圾回收器結構,代碼緩存等等)所需要的內存在“-Xmx”的設定中,而有的則不包含。因此用戶Java進程的大小不一定跟“-Xmx”的設定相吻合。

  如果你的應用程序分配對象的速率,對象的生命周期,或者對象的大小超過了JVM內存相關配置,一旦達到最大可使用內存的阈值將會發生內存溢出,用戶進程則會停止。

  當你的應用程序糾結於內存的可用性時,最有效的方法就是通過”-Xmx”指定更大的內存去重啟當前應用進程。為了避免頻繁的重啟,大多數企業生產環境都傾向於指定峰值負載時所需要的內存,造成過度配置優化。

  提示:生產環境負載的調整

  Java開發人員易犯的常見錯誤是在實驗下的做的堆內存設置,在移植到生產環境是忘記重新調整。生產環境和實驗室環境是不一樣的,謹記根據生產環境的負載重新調整堆內存設置。

  分代垃圾回收器調優

  還有一些其他的優化選項”-Xns”和”-XX: NewSize”,用來調整年輕代的大小,用來指定堆中專門負責新對象分配的空間大小。

  大多數開發者都試圖基於實驗室環境調整年輕代的大小,這意味著在生產負載下存在失敗的風險。一般新生代的大小設置為堆大小的三分之一至二分之一左 右,但這不是一個准則,畢竟實際還要視應用程序邏輯而定。因此最好先調查清楚年輕代到年老代的蛻變率以及年老代對象的大小,在此基礎上(確保年老代的大 小,年老代過小會頻繁促發GC導致內存溢出錯誤)盡可能地調大年輕代的空間。

  還有一個與年輕代相關的調優項”-XX:SurvivorRatio”,該選項用來指定年輕代中對象的生命周期,超過指定時長相關對象將被移至年老 代。為了”正確”地設定該值,你需要知道年輕代空間回收的頻率,能夠估算到新對象在應用程序進程中被引用的時長,同時也取決於分配率。

  並發垃圾回收調優

  針對對停頓敏感的應用,建議使用並發垃圾回收,雖然並行的辦法能夠帶來非常好的吞吐量基准測試分數,但是並行GC不利於縮短響應時間。並發 GC 是目前唯一有效的實現一致性和最少“stop the world”中斷的方法。不同的JVM提供不同的並發GC的設定,Oracle JVM(hotspot)提供”-XX:+UseConcMarkSweepGC”,今後G1將成為Oracle JVM默認的並發垃圾回收器。

  性能調優並不是真正的解決辦法

  或許你已經注意到上文中在討論如何“正確“地設定調優此參數時,我刻意在”正確“二字上加了雙引號。那是因為就我個人經驗而言一旦涉及到性能參數調 優,就沒有嚴格意義上的正確設定。每一個設定值都是針對特定的場景。考慮到應用場景會發生變化,JVM 性能調整充其量是一個權宜之計。

  以堆的設置為例:如果2GB的堆可以應對20萬並發用戶,但是可能不能應付40萬的並發用戶。

  我們再以”-XX:SurvivorRatio”為例:當設定符合一個負載持續增長最高至每毫秒10000個交易的場景,當壓力到達每毫秒50000個交易時又會發生什麼呢?

  大多數企業級應用負載都是動態的,Java語言的動態內存管理以及動態編譯等技術使得Java更加適合企業級應用。我們來看看一下兩個配置清單。

  清單1. 應用程序(1)的啟動選項

  >java -Xmx12g -XX:MaxPermSize=64M -XX:PermSize=32M -XX:MaxNewSize=2g -XX:NewSize=1g -XX:SurvivorRatio=16 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=0 -XX:CMSInitiatingOccupancyFraction=60 -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:ParallelGCThreads=12 -XX:LargePageSizeInBytes=256m …

  清單 2. 應用程序(2)的啟動選項

  >java –Xms8g –Xmx8g –Xmn2g -XX:PermSize=64M -XX:MaxPermSize=256M -XX:-OmitStackTraceInFastThrow -XX:SurvivorRatio=2 -XX:-UseAdaptiveSizePolicy -XX:+UseConcMarkSweepGC -XX:+CMSConcurrentMTEnabled -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelSurvivorRemarkEnabled -XX:CMSMaxAbortablePrecleanTime=10000 -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=63 -XX:+UseParNewGC –Xnoclassgc …

  兩者的配置區別很大,因為他們是兩個不同應用程序。感覺根據各自的應用特設都做了”正確“的配置與調優。在實驗室環境下都運行良好,但在生產環境中 最終會表現出疲態。清單1由於沒有考慮到動態負載,到了生產環境即表現不良。清單2沒有考慮到應用程序在生產環境中的特性變化。這兩種情況應該歸咎於開發 團隊,但是該歸咎於何處呢?

  變通辦法可行嗎?

  有些企業通過精確測量交易對象的大小定義極致的對象回收空間並”精簡“其架構來適配該空間。這也許是辦法來削減碎片以應對一整天的交易(在不做堆壓 縮的情況下)。還有一個辦法就是通過程序設計確保對象被引用的時間在一個比較短的時間內從而阻止其在SurvivorRatio時間之後不被遷往年老代而 直接被回收,避免內存壓縮的場景。這兩種辦法都可以,但是對應用開發人員和設計人員有一定的挑戰。

  誰保障應用程序的性能?

  一個門戶應用可能會在其活動負載峰值點出現故障;一個交易應用可能會在每次市場下跌和上

copyright © 萬盛學電腦網 all rights reserved