如何在高性能服務器上進行JVM調優?後端
爲了充分利用高性能服務器的硬件資源,有兩種JVM調優方案,它們都有各自的優缺點,須要根據具體的狀況進行選擇。服務器
一、採用64位操做系統,併爲JVM分配大內存網絡
咱們知道,若是JVM中堆內存過小,那麼就會頻繁地發生垃圾回收,而垃圾回收都會伴隨不一樣程度的程序停頓,所以,若是擴大堆內存的話能夠減小垃圾回收的頻率,從而避免程序的停頓。數據結構
所以,人們天然而然想到擴大內存容量。而32位操做系統理論上最大隻支持4G內存,64位操做系統最大能支持128G內存,所以咱們可使用64位操做系統,並使用64位JVM,併爲JVM分配更大的堆內存。但問題也隨之而來。負載均衡
堆內存變大後,雖然垃圾收集的頻率減小了,但每次垃圾回收的時間變長。若是對內存爲14G,那麼每次Full GC將長達數十秒。若是Full GC頻繁發生,那麼對於一個網站來講是沒法忍受的。異步
所以,對於使用大內存的程序來講,必定要減小Full GC的頻率,若是天天只有一兩次Full GC,並且發生在半夜, 那徹底能夠接受。性能
要減小Full GC的頻率,就要儘可能避免太多對象進入老年代,能夠有如下作法:網站
確保對象都是「朝生夕死」的 操作系統
一個對象使用完後應儘快讓他失效,而後儘快在新生代中被Minor GC回收掉,儘可能避免對象在新生代中停留太長時間。線程
提升大對象直接進入老年代的門檻
經過設置參數-XX:PretrnureSizeThreshold來提升大對象的門檻,儘可能讓對象都先進入新生代,而後儘快被Minor GC回收掉,而不要直接進入老年代。
注意:使用64位JDK的注意點
64位JDK支持更大的堆內存,但更大的堆內存會致使一次垃圾回收時間過長。
現階段,64位JDK的性能廣泛比32位JDK低。
堆內存過大沒法在發生內存溢出時生成內存快照
若將堆內存設爲10G,那麼當堆內存溢出時就要生成10G的大文件,這基本上是不可能的。
相同程序,64位JDK要比32位JDK消耗更大的內存
2. 使用32位JVM集羣
針對於64位JDK種種弊端,咱們更多選擇使用32位JDK集羣來充分利用高性能機器的硬件資源。
如何實現?
在一臺服務器上運行多個服務器程序,這些程序都運行在32位的JDK上。而後再運行個服務器做爲反向代理服務器,由它來實現負載均衡。
因爲32位JDK最多支持2G內存,所以每一個虛擬結點的堆內存能夠分配1.6G,一共運行10個虛擬結點的話,這臺物理服務器能夠擁有16G的堆內存。
有啥弊端?
多個虛擬節點競爭共享資源時容易出現問題
如多個虛擬節點共同競爭IO操做,極可能會引發IO異常。
很難高效地使用資源池
若是每一個虛擬節點使用各自的資源池,那麼沒法實現各個資源池的負載均衡。若是使用集中式資源池,那麼又存在競爭的問題。
每一個虛擬節點最大內存爲2G
別忘了直接內存也可能致使內存溢出!
問題描述
有個小型網站,使用32位JDK,堆1.6G。運行期間發現總是出現內存溢出。爲了判斷是不是堆內存溢出,在程序運行前添加參數:-XX:+HeapDumpOnOutOfMemeryError(添加這個參數後當堆內存溢出時就會輸出異常日至)。但當再次發生內存溢出時,沒有生成相關異常日誌。從而能夠斷定,不是堆內存發生溢出。
問題分析
咱們能夠發現,在32位JDK中,將1.6G分配給了堆,還有一部分分配給了JVM的其它內存,只有少於0.4G的內存爲非JVM內存。咱們知道,若是使用了NIO,那麼JVM會在JVM內存以外分配內存空間,這部份內存也叫「直接內存」。所以,若是程序中使用了NIO,那麼就要當心「直接內存」不足時發生內存溢出異常了!
直接內存的垃圾回收過程
直接內存雖然不是JVM內存空間,但它的垃圾回收也有JVM負責。直接內存的垃圾回收發生在Full GC時,只有當老年代內存滿時,垃圾收集器纔會順便收集一下直接內存中的垃圾。
若是直接內存已滿,但老年代沒滿,這時直接內存先是拋出異常,相應的catch塊中調用System.gc()。因爲System.gc()只是建議JVM回收,JVM可能不立刻回收內存,那麼這時直接內存就拋出內存溢出異常,使得程序終止。
JVM崩潰的緣由
當內存溢出時,JVM僅僅會終止當前運行的程序,那麼何時JVM會崩潰呢?
什麼是異步請求?
咱們知道,Web服務器和客戶端採用HTTP通訊,而HTTP底層採用TCP通訊。異步通訊就是當客戶端向服務器發送一個HTTP請求後,將這個請求的TCP鏈接委託給其它線程,而後它轉而作別的事,那條被委託的線程保持TCP鏈接,等待服務器的回信。當收到服務器回信後,再將收到的數據轉交給剛纔的線程。這個過程就是異步通訊過程。
異步請求如何形成JVM崩潰?
若是一個Web應用使用了較多的異步請求(AJAX),每次主線程發送完請求後都將TCP鏈接交給一條新的線程去等待服務器回信,那麼若是網絡不流暢時,這些受委託的線程遲遲等不到服務器的回信,所以保持着TCP鏈接。當TCP鏈接過多時,超過JVM的承受能力,JVM就發生崩潰。
如何處理大對象?
大對象對於JVM來講是個噩耗。若是對象過大,當前新生代的剩餘空間裝不下它,那麼就須要使用分配擔保機制,將當前新生代的對象都複製到老年代中,給大對象騰出空間。分配擔保涉及到大量的複製,所以效率很低。
那麼,若是將大對象直接放入老年代,雖然避免了分配擔保過程,但該對象只有當Full GC時才能被回收,而Full GC的代價是高昂的。若是大對象過多時,老年代很快就裝滿了,這時就須要進行Full GC,若是Full GC頻率太高,程序就會變得很卡。
所以,對於大對象,有以下幾種處理方法:
1. 在寫程序的時候儘可能避免大對象
從源頭下降大對象的出現,儘可能選擇空間利用率較高的數據結構存儲。
2. 儘可能縮短大對象的有效時間
對象用完後儘快讓它失效,好讓垃圾收集器儘快將他回收,避免因在新生代呆的時間過長而進入老年代。
關注Java後端進階公衆號,給你帶來更多有用、有趣的優質文章!