【JVM】關於OOM的二三事

組織架構

嚴格來說,StackOverflowError和OutOfMemoryError都屬於錯誤,而不是異常。

java.lang.StackOverflowError

1 public class StackOverflowErrorDemo {
2     public static void method(){
3         method();
4     }
5     public static void main(String[] args) {
6         method();
7     }
8 }

在上例中,方法的深度調用,導致棧溢出。

java.lang.OutOfMemoryError

java.lang.OutOfMemoryError:Java heap space

 1 public class JavaHeapSpaceDemo {
 2     private static List<String> list = new ArrayList<>();
 3     public static void main(String[] args) {
 4         String str = "";
 5         while(true){
 6             str += str + new Random().nextInt(111111);
 7             list.add(str);
 8         }
 9     }
10 }

Java堆用於存儲對象實例,不斷創建對象,保證這些對象到GC Roots有可達路徑,可以避免對象被垃圾回收,很快對象數量就會達到最大堆的容量限制,產生內存溢出異常。

java.lang.OutOfMemoryError:GC overhead limit exceeded

jdk1.6新增的錯誤類型,GC回收時間過長時會拋出OOM。超過98%的時間用來做GC,但是隻回收了2%的堆內存,連續多次垃圾回收,只回收了不到2%的極端情況纔會拋出。

經過垃圾回收釋放的2%可用內存空間會快速的被填滿,迫使GC再次執行,出現頻繁的執行GC操作, 服務器會因爲頻繁的執行GC垃圾回收操作而達到100%的使用率,服務器運行變慢,應用系統會出現卡死現象,平常只需幾毫秒就可以執行的操作,現在需要更長時間,甚至是好幾分鐘纔可以完成。

如果不拋出GC overhead limit exceeded,GC會頻繁的執行,但是被佔用的內存,經過多次長時間的GC操作都無法回收,導致可用內存越來越少,俗稱內存泄露。

1 public class GCOverheadDemo {
2     public static void main(String[] args) {
3         int i =0;
4         List<String> list = new ArrayList<>();
5         while (true){
6             list.add(String.valueOf(++i).intern());
7         }
8     }
9 }

String.intern()是一個Native方法,底層調用C++的 StringTable::intern方法實現。

當通過語句str.intern()調用intern()方法後,JVM 就會在當前類的常量池中查找是否存在與str等值的String,若存在則直接返回常量池中相應Strnig的引用;若不存在,則會在常量池中創建一個等值的String,然後返回這個String在常量池中的引用。

在此爲了快速產生這種異常,首先配置VM options爲【-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m】,再通過intern()循環創建字符串。從GC的打印日誌可以看出,雖然一直在JVM執行GC,但是回收沒有效果,最終拋出異常。

 

java.lang.OutOfMemoryError:Direct buffer memory

產生這種異常的原因是,寫NIO程序經常使用ByteBuffer來讀取或寫入,NIO是一種基於通道和緩衝區的IO方式,它可以使用本地函數庫直接分配堆外內存,通過存儲在Java堆中的DirectByteBuffer對象作爲這塊內存的引用進行操作,這樣可以在一些情況下顯著提升性能,它避免了在Java堆和Native堆中來回複製數據。

ByteBuffer有兩種分配內存的方式:

  1. allocate:分配JVM堆內存,屬於GC範疇,需要拷貝數據,速度慢。
  2. allocateDirect:分配OS本地內存,不屬於GC範疇,不需要內存拷貝,速度較快。

如果不斷分配本地內存,堆內存很少使用,JVM也就不需要GC,DirectByteBuffer對象就不會被回收,如果本地內存快被用完了,之後再分配本地內存就會拋出OOM。

java.lang.OutOfMemoryError:unable to create new native thread

高併發請求服務器的時候,經常會出現該異常,該異常與平臺有關,導致的原因是應用創建了太多線程,一個應用進程創建多個線程,超過系統承載的極限,服務器不允許創建這麼多線程,linux默認允許單個進程創建的線程數是1024個。如果超過允許的值,就會拋出異常。

解決的方法是減少創建線程的數量,儘可能少的創建線程。或者修改服務器配置,擴大限制的線程數量。

java.lang.OutOfMemoryError:Metaspace

Java8之後元空間取代了永久代,元空間的本質和永久代類似,都是JVM規範中方法區的實現,其區別在於元空間不虛擬機中,而是在本地內存中,默認情況下,元空間大小僅受內存限制。主要存放:虛擬機加載的類信息、常量池、靜態變量、即時編譯後的代碼。

解決的方法是通過配置-XX:MaxMetaspaceSize=512m參數,增大Metaspace的空間。

還可以直接去掉 Metaspace 的大小限制。 但是,如果不限制Metaspace內存的大小,當物理內存過載的時候,有可能會引起內存交換,嚴重拖累系統性能。此外,還可能造成native內存分配失敗等問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章