如何在高性能服務器上進行JVM調優;以便充分利用高性能服務器的硬件資源,有兩種JVM調優方案。
一、 采用64位操作系統,並為JVM分配大內存
分析:如果JVM中堆內存太小,那么就會頻繁地發生垃圾回收,而垃圾回收都會伴隨不同程度的程序停頓。
a) 優點:擴大堆內存的話可以減少垃圾回收的頻率,從而避免程序的停頓。因此,人們自然而然想到擴大內存容量。而32位操作系統理論上最大只支持4G內存,64位操作系統最大能支持128G內存,因此我們可以使用64位操作系統,並使用64位JVM,並為JVM分配更大的堆內存
b) 缺點:堆內存變大后,雖然垃圾收集的頻率減少了,但每次垃圾回收的時間變長。如果Full GC頻繁發生,那么對於一個網站來說是無法忍受的。
c) 應對辦法:因此,對於使用大內存的程序來說,一定要減少Full GC的頻率,如果每天只有一兩次Full GC,而且發生在半夜, 那完全可以接受。要減少Full GC的頻率,就要盡量避免太多對象進入老年代,可以有以下做法:(1)確保對象都是“朝生夕死”的,一個對象使用完后應盡快讓他失效,然后盡快在新生代中被Minor GC回收掉,盡量避免對象在新生代中停留太長時間。(2)提高大對象直接進入老年代的門檻
d) 注意:
1) 64位JDK支持更大的堆內存,但更大的堆內存會導致一次垃圾回收時間過長。
2) 現階段,64位JDK的性能普遍比32位JDK低。
3) 堆內存過大無法在發生內存溢出時生成內存快照
4) 相同程序,64位JDK要比32位JDK消耗更大的內存
二、 采用32位JVM集群:針對64位JDK的種種弊端,我們更多的選擇32為JDK集群來充分利用高性能機器的硬件資源。
a) 如何實現:在一台服務器上運行多個服務器程序,這些程序都運行在32位的JDK上。然后再運行個服務器作為反向代理服務器,由它來實現負載均衡。由於32位JDK最多支持2G內存,因此每個虛擬結點的堆內存可以分配1.6G,一共運行10個虛擬結點的話,這台物理服務器可以擁有16G的堆內存。
b) 有啥弊端:
1) 多個虛擬節點競爭共享資源時容易出現問題
2) 很難高效地使用資源池
3) 每個虛擬節點最大內存為2G
三、 直接內存也會導致內存溢出?
a) 問題分析:在32位JDK中,將1.6G分配給了堆,還有一部分分配給了JVM的其它內存,只有少於0.4G的內存為非JVM內存。我們知道,如果使用了NIO,那么JVM會在JVM內存之外分配內存空間,這部分內存也叫“直接內存”。因此,如果程序中使用了NIO,那么就要小心“直接內存”不足時發生內存溢出異常了!
b) 直接內存的垃圾回收過程:直接內存雖然不是JVM內存空間,但它的垃圾回收也有JVM負責。直接內存的垃圾回收發生在Full GC時,只有當老年代內存滿時,垃圾收集器才會順便收集一下直接內存中的垃圾。如果直接內存已滿,但老年代沒滿,這時直接內存先是拋出異常,相應的catch塊中調用System.gc()。由於System.gc()只是建議JVM回收,JVM可能不馬上回收內存,那么這時直接內存就拋出內存溢出異常,使得程序終止。
四、 JVM崩潰的原因:當內存溢出時,JVM僅僅會終止當前運行的程序,那么什么時候JVM會崩潰呢?
五、 異步請求如何造成JVM崩潰:如果一個Web應用使用了較多的異步請求(AJAX),每次主線程發送完請求后都將TCP連接交給一條新的線程去等待服務器回信,那么如果網絡不流暢時,這些受委托的線程遲遲等不到服務器的回信,因此保持着TCP連接。當TCP連接過多時,超過JVM的承受能力,JVM就發生崩潰。
六、 如何處理大對象:
a) 在寫程序的時候盡量避免大對象
b) 盡量縮短大對象的有效時間。對象使用完之后盡快讓他失效,以便於垃圾收集器盡快對它進行回收以免在新生代待的時間過長而進入到老年代。
