Java之JVM调优案例分析与实战(1) - 高性能硬件上的程序部署策略
本JVM系列均來源于《深入理解Java虛擬機》一書中,版權歸該書作者所有。
環境:一個15萬PV/天左右的在線文檔類型網站最近更換了硬件系統,新系統硬件為4個CPU、16GB物理內存、OS為64位CentOS5.4、Resin作為Web服務器。
說明:整個服務暫時沒有部署別的應用,所有硬件資源都可以提供給訪問量并不算太大的網站使用。管理員為了盡量利用硬件資源選用了64位的JDK1.5,并通過-Xmx和-Xms參數將java堆固定在12GB。
問題:使用一段時間后發現使用效果并不理想,網站經常不定期出現長時間沒有響應的現象。
排查:監控服務器運行狀況后發現網站沒有響應是由于GC停頓導致的,虛擬機運行在Server模式,默認使用吞吐量優先收集器,回收12GB的堆,一次Full GC的停頓時間高達14s。并且由于程序設計的關系,訪問文檔時要把文檔從磁盤提取到內存中,
??????? 導致內存中出現很多由文檔序列化產生的大對象,這些大對象很多都進入了老年代,沒有在Minor GC中清理掉。這種情況下即使有12GB的堆,內存也很塊被消耗殆盡,由此導致每隔十幾分鐘出現十幾秒的停頓。
分析:先不延伸討論程序代碼問題,程序部署上主要問題顯然是過大的堆內存進行回收時帶來的長時間停頓。硬件升級前使用32位系統1.5GB的堆,用戶只感到訪問網站比較緩慢,但不會發生十分明顯的停頓,因此才考慮升級硬件來提升程序效能,如果重新
??????? 縮小給java堆分配的內存,那么硬件上的投資就浪費了。
??????? 在高性能硬件上部署程序,目前主要有兩種方式:1.通過64為JDK來使用大內存 2.使用若干個32位虛擬機建立邏輯集群來利用硬件資源
??????? 此案例中管理員采用了第一種部署方式。對于用戶交互性強、對停頓時間敏感的系統,可以給Java虛擬機分配超大堆的前提是有把握把應用程序的Full GC頻率控制得足夠低,至少要低到不會影響用戶使用,譬如十幾個小事乃至一天才出現一次Full GC,這樣可以通過在深夜執行定時任務的方式觸發Full GC甚至自動重啟服務器來將內存可用空間保持在一個穩定的水平。
??????? 控制Full GC頻率的關鍵是看應用中絕大多數對象能否符合“招生夕滅”的原則,即大多數對象的生存時間不應太長,尤其是不能產生批量的、長生存時間的大對象,這樣才能保證老年代空間的穩定。
??????? 在大多數網站形式的應用里,主要對象的生存周期都應該是請求級或頁面級的,回話級和全局級的長生命對象相對減少。只要代碼寫得合理,應當都能實現在超大堆中正常使用而沒有Full GC,這樣的話,使用超大堆內存時,網站響應的速度才比較有保證。除此之外,如果讀者計劃使用64位JDK來管理大內存,還需要考慮下面可能面臨的問題:
??????? 1.內存回收導致的長時間停頓
??????? 2.現階段,64位JDK的性能測試結果普遍低于32位JDK。
??????? 3.需要保證程序足夠穩定,因為這種應用要是產生堆溢出幾乎無法產生堆轉儲快照(因為要產生十幾GB乃至更大的dump文件),哪怕產生了快照也幾乎無法進行分析。
??????? 4.相同的程序在64位JDK中消耗的內存一般比32位JDK大,這是由于指針膨脹及數據型對齊補白等因素導致的。
??????? 上面的問題聽起來有點嚇人,所以現價段不少管理員還是選擇了第二種方式:使用若干個32位虛擬建立邏輯集群來利用硬件資源。具體做法是在一臺物理機器上啟動多個應用服務器進程,給每個服務器進行分配不同的端口,然后在前端搭建一個負載均衡器,以反向代理的方式來分配訪問請求。讀者不需要太在意均衡器轉發所消耗的性能,即使使用64位JDK,許多應用也不止有一臺服務器,因此許多應用中前段的均衡器總是要存在的。
?????? 考慮到一臺物理機器上建立邏輯集群的目的僅僅是盡可能地利用硬件資源,并不需要關心狀態保留、熱轉移之類的高可用性需求,也不需要保證每個虛擬機進程有絕對準確的均衡負載,因此使用無Session復制的親合式集群是一個相當不錯的選擇。我們僅僅需呀保障集群具備親和性,也就是均衡器按一定的規則算法(一般根據SessionID分配)將一個固定的用戶請求永遠分配到固定的一個集群節點進行處理即可,這樣程序開發階段就基本不用為集群環境做什么特別的考慮。
????? 當然,很少有沒有缺點的方案,如果讀者計劃使用邏輯集群的方式來部署程序,可能會遇到下面的一些問題。
????? 1.盡量避免節點競爭全局資源,最典型的就是磁盤競爭,各個節點如果同時訪問某個磁盤文件的話(尤其是并發寫操作容易出現問題),很容易導致IO異常。
????? 2.很難最高效率地利用某些資源池,如連接池,一般都是在各個節點建立自己獨立的連接池,這樣有可能導致一些節點池滿了而另外一些節點仍有較多空余。盡管可以使用集中式的JNDI,但這有一定的復雜性且可能帶來額外的性能代價。
????? 3.各個節點仍然不可避免地受到32位的內存限制,在32位Windows平臺中每個進程只能使用2GB的內存,考慮到堆以外的內存開銷,堆一般最多只能開到1.5GB。在某些Linux,Unix系統(如Solaris)中,可以提升到3GB乃至接近4GB的內存,但32位
???????? 中仍然受最高4GB(2^32)內存的限制。
????? 4.大量使用本地緩存(如大量使用HashMap所謂K/V緩存)的應用,在邏輯集群中會造成較大的內存浪費,因為每個邏輯節點上都有一份緩存,這時可以考慮把本地緩存改成集中式緩存
????? 介紹完這兩種部署方式,再重新回到這個案例中,最后的部署方案調整為建立5個32位JDK的邏輯集群,每個進程按2GB內存計算(其中堆固定為1.5GB),占用了10GB的內存。另外建立一個Apache服務作為前端均衡器代理訪問門戶。考慮到用戶對響應較低,因此改為CMS收集器進行垃圾回收。部署方式調整后,服務再沒有出現長時間停頓,速度比硬件升級前有較多提升。
總結
以上是生活随笔為你收集整理的Java之JVM调优案例分析与实战(1) - 高性能硬件上的程序部署策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Devexpress之dxErrorPr
- 下一篇: 转: ajax跨域之JSONP