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