JVM 性能調(diào)優(yōu)
在高性能硬件上部署程序袖扛,目前主要有兩種方式:
- 通過 64 位 JDK 來使用大內(nèi)存;
- 使用若干個 32 位虛擬機(jī)建立邏輯集群來利用硬件資源。
使用 64 位 JDK 管理大內(nèi)存
堆內(nèi)存變大后,雖然垃圾收集的頻率減少了曙旭,但每次垃圾回收的時間變長。 如果堆內(nèi)存為 14 G晶府,那么每次 Full GC 將長達(dá)數(shù)十秒桂躏。如果 Full GC 頻繁發(fā)生,那么對于一個網(wǎng)站來說是無法忍受的川陆。
對于用戶交互性強(qiáng)剂习、對停頓時間敏感的系統(tǒng),可以給 Java 虛擬機(jī)分配超大堆的前提是有把握把應(yīng)用程序的 Full GC 頻率控制得足夠低书劝,至少要低到不會影響用戶使用进倍。
可能面臨的問題:
- 內(nèi)存回收導(dǎo)致的長時間停頓;
- 現(xiàn)階段购对,64 位 JDK 的性能普遍比 32 位 JDK 低猾昆;
- 需要保證程序足夠穩(wěn)定,因為這種應(yīng)用要是產(chǎn)生堆溢出幾乎就無法產(chǎn)生堆轉(zhuǎn)儲快照(因為要產(chǎn)生超過 10GB 的 Dump 文件)骡苞,哪怕產(chǎn)生了快照也幾乎無法進(jìn)行分析垂蜗;
- 相同程序在 64 位 JDK 消耗的內(nèi)存一般比 32 位 JDK 大楷扬,這是由于指針膨脹,以及數(shù)據(jù)類型對齊補(bǔ)白等因素導(dǎo)致的贴见。
使用 32 位 JVM 建立邏輯集群
在一臺物理機(jī)器上啟動多個應(yīng)用服務(wù)器進(jìn)程烘苹,每個服務(wù)器進(jìn)程分配不同端口, 然后在前端搭建一個負(fù)載均衡器片部,以反向代理的方式來分配訪問請求镣衡。
考慮到在一臺物理機(jī)器上建立邏輯集群的目的僅僅是為了盡可能利用硬件資源,并不需要關(guān)心狀態(tài)保留档悠、熱轉(zhuǎn)移之類的高可用性能需求廊鸥, 也不需要保證每個虛擬機(jī)進(jìn)程有絕對的均衡負(fù)載,因此使用無 Session 復(fù)制的親合式集群是一個不錯的選擇辖所。 我們僅僅需要保障集群具備親合性惰说,也就是均衡器按一定的規(guī)則算法(一般根據(jù) SessionID 分配) 將一個固定的用戶請求永遠(yuǎn)分配到固定的一個集群節(jié)點進(jìn)行處理即可。
可能遇到的問題:
- 盡量避免節(jié)點競爭全局資源缘回,如磁盤競爭吆视,各個節(jié)點如果同時訪問某個磁盤文件的話,很可能導(dǎo)致 IO 異常酥宴;
- 很難高效利用資源池啦吧,如連接池,一般都是在節(jié)點建立自己獨(dú)立的連接池幅虑,這樣有可能導(dǎo)致一些節(jié)點池滿了而另外一些節(jié)點仍有較多空余丰滑;
- 各個節(jié)點受到 32 位的內(nèi)存限制;
- 大量使用本地緩存的應(yīng)用倒庵,在邏輯集群中會造成較大的內(nèi)存浪費(fèi),因為每個邏輯節(jié)點都有一份緩存炫刷,這時候可以考慮把本地緩存改成集中式緩存擎宝。
調(diào)優(yōu)案例分析與實戰(zhàn)
場景描述
一個小型系統(tǒng),使用 32 位 JDK浑玛,4G 內(nèi)存绍申,測試期間發(fā)現(xiàn)服務(wù)端不定時拋出內(nèi)存溢出異常。 加入 -XX:+HeapDumpOnOutOfMemoryError(添加這個參數(shù)后顾彰,堆內(nèi)存溢出時就會輸出異常日志)极阅, 但再次發(fā)生內(nèi)存溢出時,沒有生成相關(guān)異常日志涨享。
分析
在 32 位 JDK 上筋搏,1.6G 分配給堆,還有一部分分配給 JVM 的其他內(nèi)存厕隧,直接內(nèi)存最大也只能在剩余的 0.4G 空間中分出一部分奔脐, 如果使用了 NIO俄周,JVM 會在 JVM 內(nèi)存之外分配內(nèi)存空間,那么就要小心“直接內(nèi)存”不足時發(fā)生內(nèi)存溢出異常了髓迎。
直接內(nèi)存的回收過程
直接內(nèi)存雖然不是 JVM 內(nèi)存空間峦朗,但它的垃圾回收也由 JVM 負(fù)責(zé)。
垃圾收集進(jìn)行時排龄,虛擬機(jī)雖然會對直接內(nèi)存進(jìn)行回收波势, 但是直接內(nèi)存卻不能像新生代、老年代那樣橄维,發(fā)現(xiàn)空間不足了就通知收集器進(jìn)行垃圾回收艰亮, 它只能等老年代滿了后 Full GC,然后“順便”幫它清理掉內(nèi)存的廢棄對象挣郭。 否則只能一直等到拋出內(nèi)存溢出異常時迄埃,先 catch 掉,再在 catch 塊里大喊 “System.gc()
”兑障。 要是虛擬機(jī)還是不聽侄非,那就只能眼睜睜看著堆中還有許多空閑內(nèi)存,自己卻不得不拋出內(nèi)存溢出異常了流译。
JVM 性能調(diào)優(yōu)
在高性能硬件上部署程序逞怨,目前主要有兩種方式:
- 通過 64 位 JDK 來使用大內(nèi)存;
- 使用若干個 32 位虛擬機(jī)建立邏輯集群來利用硬件資源福澡。
使用 64 位 JDK 管理大內(nèi)存
堆內(nèi)存變大后叠赦,雖然垃圾收集的頻率減少了,但每次垃圾回收的時間變長革砸。 如果堆內(nèi)存為 14 G除秀,那么每次 Full GC 將長達(dá)數(shù)十秒。如果 Full GC 頻繁發(fā)生算利,那么對于一個網(wǎng)站來說是無法忍受的册踩。
對于用戶交互性強(qiáng)、對停頓時間敏感的系統(tǒng)效拭,可以給 Java 虛擬機(jī)分配超大堆的前提是有把握把應(yīng)用程序的 Full GC 頻率控制得足夠低暂吉,至少要低到不會影響用戶使用。
可能面臨的問題:
- 內(nèi)存回收導(dǎo)致的長時間停頓缎患;
- 現(xiàn)階段慕的,64 位 JDK 的性能普遍比 32 位 JDK 低;
- 需要保證程序足夠穩(wěn)定挤渔,因為這種應(yīng)用要是產(chǎn)生堆溢出幾乎就無法產(chǎn)生堆轉(zhuǎn)儲快照(因為要產(chǎn)生超過 10GB 的 Dump 文件)肮街,哪怕產(chǎn)生了快照也幾乎無法進(jìn)行分析;
- 相同程序在 64 位 JDK 消耗的內(nèi)存一般比 32 位 JDK 大蚂蕴,這是由于指針膨脹低散,以及數(shù)據(jù)類型對齊補(bǔ)白等因素導(dǎo)致的俯邓。
使用 32 位 JVM 建立邏輯集群
在一臺物理機(jī)器上啟動多個應(yīng)用服務(wù)器進(jìn)程,每個服務(wù)器進(jìn)程分配不同端口熔号, 然后在前端搭建一個負(fù)載均衡器稽鞭,以反向代理的方式來分配訪問請求。
考慮到在一臺物理機(jī)器上建立邏輯集群的目的僅僅是為了盡可能利用硬件資源引镊,并不需要關(guān)心狀態(tài)保留朦蕴、熱轉(zhuǎn)移之類的高可用性能需求, 也不需要保證每個虛擬機(jī)進(jìn)程有絕對的均衡負(fù)載弟头,因此使用無 Session 復(fù)制的親合式集群是一個不錯的選擇吩抓。 我們僅僅需要保障集群具備親合性,也就是均衡器按一定的規(guī)則算法(一般根據(jù) SessionID 分配) 將一個固定的用戶請求永遠(yuǎn)分配到固定的一個集群節(jié)點進(jìn)行處理即可赴恨。
可能遇到的問題:
- 盡量避免節(jié)點競爭全局資源疹娶,如磁盤競爭,各個節(jié)點如果同時訪問某個磁盤文件的話伦连,很可能導(dǎo)致 IO 異常雨饺;
- 很難高效利用資源池,如連接池惑淳,一般都是在節(jié)點建立自己獨(dú)立的連接池额港,這樣有可能導(dǎo)致一些節(jié)點池滿了而另外一些節(jié)點仍有較多空余;
- 各個節(jié)點受到 32 位的內(nèi)存限制歧焦;
- 大量使用本地緩存的應(yīng)用移斩,在邏輯集群中會造成較大的內(nèi)存浪費(fèi),因為每個邏輯節(jié)點都有一份緩存绢馍,這時候可以考慮把本地緩存改成集中式緩存向瓷。
資源分享:
JAVA開發(fā)2T電子書分享:JAVA開發(fā)2T電子書分享提取碼:knPG
調(diào)優(yōu)案例分析與實戰(zhàn)
場景描述
一個小型系統(tǒng),使用 32 位 JDK痕貌,4G 內(nèi)存风罩,測試期間發(fā)現(xiàn)服務(wù)端不定時拋出內(nèi)存溢出異常。 加入 -XX:+HeapDumpOnOutOfMemoryError(添加這個參數(shù)后舵稠,堆內(nèi)存溢出時就會輸出異常日志), 但再次發(fā)生內(nèi)存溢出時入宦,沒有生成相關(guān)異常日志哺徊。
分析
在 32 位 JDK 上,1.6G 分配給堆乾闰,還有一部分分配給 JVM 的其他內(nèi)存落追,直接內(nèi)存最大也只能在剩余的 0.4G 空間中分出一部分, 如果使用了 NIO涯肩,JVM 會在 JVM 內(nèi)存之外分配內(nèi)存空間轿钠,那么就要小心“直接內(nèi)存”不足時發(fā)生內(nèi)存溢出異常了巢钓。
直接內(nèi)存的回收過程
直接內(nèi)存雖然不是 JVM 內(nèi)存空間,但它的垃圾回收也由 JVM 負(fù)責(zé)疗垛。
垃圾收集進(jìn)行時症汹,虛擬機(jī)雖然會對直接內(nèi)存進(jìn)行回收, 但是直接內(nèi)存卻不能像新生代贷腕、老年代那樣背镇,發(fā)現(xiàn)空間不足了就通知收集器進(jìn)行垃圾回收, 它只能等老年代滿了后 Full GC泽裳,然后“順便”幫它清理掉內(nèi)存的廢棄對象瞒斩。 否則只能一直等到拋出內(nèi)存溢出異常時,先 catch 掉涮总,再在 catch 塊里大喊 “System.gc()
”胸囱。 要是虛擬機(jī)還是不聽,那就只能眼睜睜看著堆中還有許多空閑內(nèi)存瀑梗,自己卻不得不拋出內(nèi)存溢出異常了烹笔。
本文由mdnice多平臺發(fā)布