jvm內存溢出分析

ekec1153 8年前發布 | 8K 次閱讀 JVM Java開發

概述

jvm中除了程序計數器,其他的區域都有可能會發生內存溢出

內存溢出是什么?

當程序需要申請內存的時候,由于沒有足夠的內存,此時就會拋出OutOfMemoryError,這就是內存溢出

內存溢出和內存泄漏有什么區別?

內存泄漏是由于使用不當,把一部分內存“丟掉了”,導致這部分內存不可用。

當在堆中創建了對象,后來沒有使用這個對象了,又沒有把整個對象的相關引用設為 null 。此時垃圾收集器會認為這個對象是 需要 的,就不會清理這部分內存。這就會導致這部分內存不可用。

所以 內存泄漏 會導致可用的內存減少,進而會導致 內存溢出

用到的jvm參數

下面為了說明溢出的情景,會執行一些實例代碼,同時需要給jvm指定參數

  • -Xms 堆最小容量(heap min size)
  • -Xmx 堆最大容量(heap max size)
  • -Xss 棧容量(stack size)
  • -XX:PermSize=size 永生代最小容量

-XX:MaxPermSize=size 永生代最大容量

堆溢出

堆是存放對象的地方,那么只要在堆中瘋狂的創建對象,那么堆就會發生內存溢出。

下面做一個堆溢出的實驗

執行這段代碼的時候,要給jvm指定參數

//jvm參數:-Xms20m -Xmx20m
public class HeapOOMTest {
    public static void main(String[] args){
        LinkedList<HeapOOMTest> l=new LinkedList<HeapOOMTest>();//作為GC Root
        while(true){
            l.add(new HeapOOMTest());//瘋狂創建對象
        }
    }
}

-Xms20m -Xmx20m 作用是將jvm的最小堆容量和最大堆容量都設定為20m,這樣就 不會動態擴展jvm堆

這段代碼瘋狂的創建對象,雖然對象沒有聲明變量名引用,但是將對象添加到隊列l中,這樣 隊列l就持有了一份對象的引用

通過可達性算法(jvm判斷對象是否可被收集的算法)分析,隊列l作為GC Root, 每一個對象都是l的一個可達的節點 ,所以瘋狂創建的對象 不會被收集 ,這就是內存泄漏,這樣總有一天堆就溢出了。

運行結果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.util.LinkedList.linkLast(Unknown Source)
    at java.util.LinkedList.add(Unknown Source)
    at test.HeapOOMTest.main(HeapOOMTest.java:23)

程序發生內存溢出,并提示發生在 Java heap space

分析解決方法

思路

用 visualVM 工具分析堆快照

如果發生 內存泄漏

step1:找出泄漏的對象

step2:找到泄漏對象的GC Root

step3:根據泄漏對象和GC Root找到導致內存泄漏的代碼

step4:想法設法解除泄漏對象與GCRoot的連接

如果 不存在泄漏 :

  1. 看下是否能增大jvm堆的最大容量

優化程序,減小對象的生命周期

前期準備

當發生堆溢出的時候,可以讓程序在崩潰時產生一份 堆內存快照

產生堆內存快照的方法:

給jvm加上參數 XX:+HeapDumpOnOutofMemoryError ,這樣就會在程序崩潰的時候,產生一份堆內存快照

分析堆內存快照我建議用jdk自帶的可視化監視工具visualVM,位置在jdk安裝目錄下的bin,如果是在linux環境的話,可以把快照傳到window。因為分析工具會占用很大的內存,不建議在服務端進行分析。

實戰

下面對剛才程序產生的堆內存快照進行分析。

打開visualVM,裝入剛剛生成的快照,打開類標簽頁

隊列和瘋狂創建的對象幾乎占滿了整個棧,想要讓垃圾收集器回收這些對象,要讓他們 與GC Root斷開連接

雙擊HeapOOMTest類,跳轉到實例標簽頁,可以查看這個類的所有實例

在實例上右鍵——顯示最近的垃圾回收根節點,可以 看到這個對象與根節點的連接

只要斷開HeapOOMTest對象與LinkedList的連接,這些瘋狂創建的對象就會被收集了

棧溢出

調用方法的時候,會在棧中入棧一個棧幀,如果當前棧的容量不足,就會發生棧溢出 StackOverFlowError

那么只要瘋狂的調用方法,并且有意的不讓棧幀出棧就可以導致棧溢出了。

下面來一次棧溢出

//jvm參數:-Xss128k
public class StackSOFTest {
    public void stackLeak(){
        stackLeak();//遞歸,瘋狂的入棧,有意不讓出棧
    } 
    public static void main(String[] args){
        StackSOFTest s=new StackSOFTest();
        s.stackLeak();
    }
}

jvm設置參數 -Xss128k ,目的是縮小棧的空間,這樣棧溢出“來的快一點”

程序中用了遞歸,讓棧幀瘋狂的入棧,又不讓棧幀出棧,這樣就會棧溢出了。

運行結果:

Exception in thread "main" java.lang.StackOverflowError
    at test.StackSOFTest.stackLeak(StackSOFTest.java:17)
    at test.StackSOFTest.stackLeak(StackSOFTest.java:17)

運行時常量池溢出

這里儲存的是一些常量、字面量。如果運行時常量池內存不足,就會發生內存溢出。從jdk1.7開始,運行時常量池移動到了堆中,所以如果堆的內存不足,也會導致運行時常量池內存溢出。

下面來一次運行時常量池溢出,環境是jdk8

只要創建足夠多的常量,就會發生溢出

/**

  • jvm參數:
  • jdk6以前:-XX:PermSize=10M -XX:MaxPermSize=10M
  • jdk7開始:-Xms10m -Xmx10m
  • */ public class RuntimePoolOOM { public static void main(String[] args){
     int i=1;
     LinkedList<String> l=new LinkedList<String>();//保持常量的引用,防止被fullgc收集
     while(true){
         l.add(String.valueOf(i++).intern());//將常量添加到常量池
     }
    
    } }</code></pre>

    因為jdk6以前,運行時常量池是在方法區(永生代)中的,所以要限制永生代的容量,讓內存溢出來的更快。

    從jdk7開始,運行時常量池是在堆中的,那么固定堆的容量就好了

    這里用了鏈表去保存常量的引用,是因為防止被fullgc清理,因為fullgc會清理掉方法區和老年代

    intern()方法是將常量添加到常量池中去,這樣運行時常量池一直都在增長,然后內存溢出

    運行結果:

    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
     at java.lang.Integer.toString(Unknown Source)
     at java.lang.String.valueOf(Unknown Source)
     at test.RuntimePoolOOM.main(RuntimePoolOOM.java:30)

    提示在heap區域發生內存溢出,果然運行時常量池被移到了堆中

    方法區溢出

    方法區是存放類的信息,而且很難被gc,只要加載了大量類,就有可能引起方法區溢出

    這里將不做演示了,想試試的可以用cglib創建大量的代理類

    分析

    工作中也有可能會遇上方法區溢出:

    當多個項目都有 相同jar包 的時候,又都存放在WEB-INF\lib\下,這樣每個項目都會加載一遍jar包。會導致方法區中有 大量相同類 (被不同的類加載器所加載),又不會被gc掉。

    解決方案:

    1. 在應用服務器中建立一個 共享lib庫 ,把項目中常用重復的jar包存放在這里,項目從這里加載jar包,這樣就會大大減少類加載的數量,方法區也“瘦身”了

    2. 如果實在不能瘦身類的話,那可以擴大方法區的容量,給jvm指定參數 -XX:MaxPermSize=xxxM

       

    來自:http://www.cnblogs.com/wewill/p/6038528.html

     

 本文由用戶 ekec1153 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!