淺析JVM堆溢出和棧溢出以及其產生可能原因的排查要點


一、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工具進行查找大概出現棧溢出的位置


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM