OOM的三種類型:
堆OOM
/** * -Xmx1g -XX:+PrintGCDetails -XX:MaxDirectMemorySize=100m * * @param args */ public static void main(String[] args) { ArrayList<byte[]> arrayList = new ArrayList<>(); for (int i = 0; i < 1024; i++) { arrayList.add(new byte[1024 * 1024 * 2]); } }
運行結果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.Test.main(Test.java:15)
解決方法: 增大Xmx值 使用MAT,JProfile等工具進行代碼分析與優化
直接內存OOM
/** * -Xmx1g -XX:+PrintGCDetails -XX:MaxDirectMemorySize=100m * @param args */ public static void main(String[] args) { for (int i = 0; i < 2048; i++) { ByteBuffer.allocateDirect(1024 * 1024*2); System.out.println(i); //System.gc(); } }
運行結果: 直接內存設置為2M,超過100M時,進行回收
解決方法: 增大MaxDirectMemorySize的值
過多線程導致的OOM
/** * -Xmx1g -XX:+PrintGCDetails -XX:MaxDirectMemorySize=100m -Xss:指定棧空間大小 * * @param args */ public static void main(String[] args) { for (int i = 0; i <1500 ; i++) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
運行結果: OOM unable to create native thread
解決方法: 減少線程數、或減少堆空間與線程空間
永久(Pern)溢出
永久區是存儲類元數據的區域
/** * -Xmx1g -XX:+PrintGCDetails * * @param args */ public static void main(String[] args) { for (int i = 0; i < 1000000000; i++) { Integer integer = new Integer("1" + new StringBuffer(1)); System.out.println(new ArrayList<>()); } }
運行結果: OOM Permgen sace
解決方法:
- 增大MaxPremSize的值
- 減少系統需要的類的數量
- 使用ClassLoader合理裝載類,並進行回收
GC效率低下引起的OOM
判斷GC引起OOM的條件
- 花在GC上的時間是否超過了98%
- 老年代釋放的內存是否小於2%
- Eden區釋放的內存是否小於2%
- 是否連續5次GC都出現了上述幾種情況
關閉OOM可以通過參數:-XX:-UseGCOverheadLimit 進行設置
String常量池因其的OOM
/** * -Xmx5m -XX:MaxPermSize5m【JDK<1.6】 * * @param args */ public static void main(String[] args) { List<String> list = new ArrayList<>(); int i = 0; while (true) { list.add(String.valueOf(i++).intern()); } }
運行結果: JDK1.6
:Perm space JDK1.7
:heap space
【注】
/** *String.intern()方法返回字符串常量池引用,但不一定每時每刻都返回一樣,可能垃圾回收后重新放入常量池 * * @param args */ public static void main(String[] args) { System.out.println(System.identityHashCode((args[0] + Integer.toString(0))));//字符本身hash System.out.println(System.identityHashCode((args[0] + Integer.toString(0)).intern()));//常量池引用 System.gc();//未gc前常量池引用相同 System.out.println(System.identityHashCode((args[0] + Integer.toString(0)).intern()));//常量池引用 }
淺堆和深堆
淺堆(shallow heap):一個對象結構所占的內存大小,對象包括屬性和對象頭、對象引用等。
如String對象,其結構包括:
類型 | 值 | 大小 |
---|---|---|
int | hash32 | 0 |
int | hash | 0 |
ref | value | c://... |
其中2個int8字節 對象引用4字節 對象頭8字節 按照8字節對齊,則對象淺堆共24字節
深堆(retained heap):一個對象被GC回收后,可以真實釋放的內存大小。即對象所持有的對象和包括對象自身的所有的淺堆的大小之和。
從圖中可以看出對象A的淺堆大小為A本身,對象A的實際大小為對象A、對象B、對象C、對象D的淺堆大小之和,對象A的深堆大小為對象A和對象C,因為對象B和對象D同時也被對象E所引用