一、JVM 堆溢出
在 jvm 運行 java 程序時,如果程序運行所需要的內存大於系統的堆最大內存(-Xmx),就會出現堆溢出問題。創建對象時如果沒有可以分配的堆內存,JVM就會拋出OutOfMemoryError:java heap space異常。
// 執行該段代碼需要大於10m內存空間
public class HeadOverflow { public static void main(String[] args) { List<Object> listObj = new ArrayList<Object>(); for(int i=0; i<10; i++){ Byte[] bytes = new Byte[1*1024*1024]; listObj.add(bytes); } System.out.println("添加success"); } } // 設置該程序的jvm參數信息
-Xms1m -Xmx10m -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError 初始堆內存和最大可以堆內存 Gc詳細日志信息
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid2464.hprof ... Heap dump file created [16991068 bytes in 0.047 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at com.ghs.test.OOMTest.main(OOMTest.java:16)
在正式項目部署環境程序默認讀取的是系統的內存,一般設置程序的堆初始內存(-Xms) == 堆最大可用內存(-Xmx)。
二、JVM 棧溢出
1、棧溢出介紹:棧空間不足時,需要分下面兩種情況處理:
(1)線程請求的棧深度大於虛擬機允許的最大深度 - StackOverflowError
(2)虛擬機在擴展棧深度時,無法申請到足夠的內存空間 - OutOfMemoryError
理解:每次方法調用都會有一個棧幀壓入虛擬機棧,操作系統給JVM分配的內存是有限的,JVM分配給“虛擬機棧”的內存是有限的。如果方法調用過多,導致虛擬機棧滿了就會溢出。這里棧深度就是指棧幀的數量。
2、案例
// 循環遞歸調用,一直達到jvm的最大深度
public class StackOverflow { private static int count; public static void count(){ try { count++; count(); } catch (Throwable e) { System.out.println("最大深度:"+count); e.printStackTrace(); } } public static void main(String[] args) { count(); } }
3、調整 jvm 棧大小:設置-Xss5m設置最大調用深度后調用
每個計算機都會有一個極限最大調用深度,避免遞歸在代碼中無限循環。局部變量表內容越多,棧幀越大,棧深度越小。
這里有一篇文章講的是各自溢出的問題,可以看看:《寫代碼實現堆溢出、棧溢出、永久代溢出、直接內存溢出 - https://blog.csdn.net/u011983531/article/details/63250882》
(1)棧內存溢出:程序所要求的棧深度過大。
(2)堆內存溢出: 分清內存泄露還是 內存容量不足。泄露則看對象如何被 GC Root 引用,不足則通過調大-Xms,-Xmx參數。
(3)永久代溢出:Class對象未被釋放,Class對象占用信息過多,有過多的Class對象。
(4)直接內存溢出:系統哪些地方會使用直接內存。
三、內存溢出的原因是什么
1、內存溢出與內存泄漏:
內存溢出:申請內存空間,超出最大堆內存空間。
內存泄露:其實包含了內存溢出,堆內存空間被無用對象占用沒有及時釋放,導致占用內存,最終導致內存泄露。
2、內存溢出的可能原因排查
內存溢出是由於沒被引用的對象(垃圾)過多造成JVM沒有及時回收,造成的內存溢出。如果出現這種現象可進行代碼排查:
(1)是否應用中的類中和引用變量過多使用了Static修飾,如 public staitc Students;
在類中的屬性中使用 static 修飾的最好只用基本類型或字符串。如:public static int i = 0; public static String str;
(2)是否 應用 中使用了大量的遞歸或無限遞歸(遞歸中用到了大量的建新的對象)
(3)是否App中使用了大量循環或死循環(循環中用到了大量的新建的對象)
(4)檢查 應用 中是否使用了向數據庫查詢所有記錄的方法。即一次性全部查詢的方法,如果數據量超過10萬多條了,就可能會造成內存溢出。所以在查詢時應采用“分頁查詢”。
(5)檢查是否有數組,List,Map中存放的是對象的引用而不是對象,因為這些引用會讓對應的對象不能被釋放,會大量存儲在內存中。
(6)檢查是否使用了“非字面量字符串進行+”的操作。
因為String類的內容是不可變的,每次運行"+"就會產生新的對象,如果過多會造成新String對象過多,從而導致JVM沒有及時回收而出現內存溢出。
// 如
String s1 = "My name"; String s2 = "is"; String s3 = "xuwei"; String str = s1 + s2 + s3 +.........; // 這是會容易造成內存溢出的 // 但是String str = "My name" + " is " + " xuwei" + " nice " + " to " + " meet you";
// 這種就不會造成內存溢出。因為這是”字面量字符串“,在運行"+"時就會在編譯期間運行好。不會按照JVM來執行的。
在使用 String、StringBuffer、StringBuilder 時,如果是字面量字符串進行"+"時,應選用String性能更好;如果是String類進行"+"時,在不考慮線程安全時,應選用 StringBuilder 性能更好。
再比如下面這些錯誤的示例:
public class Test { public void testHeap(){ for(;;){ //死循環一直創建對象,堆溢出
ArrayList list = new ArrayList (2000); } } int num=1; public void testStack(){ //無出口的遞歸調用,棧溢出
num++; this.testStack(); } public static void main(String[] args){ Test t = new Test (); t.testHeap(); t.testStack(); } }
3、棧溢出的原因
(1)是否有遞歸調用
(2)是否有大量循環或死循環
(3)全局變量是否過多
(4)數組、List、map數據是否過大
(5)使用DDMS工具進行查找大概出現棧溢出的位置