深刻理解JVM(八)——java堆分析

上一節介紹了針對JVM的監控工具,包括JPS能夠查看當前全部的java進程,jstack查看線程棧能夠幫助分析是否有死鎖等狀況,jmap能夠導出java堆文件在MAT工具上進行分析等等。這些工具都很是有用,但要用好他們須要不斷的進行實踐分析。本文將介紹使用MAT工具進行java堆分析的案例。java

內存溢出(OOM)的緣由

常見的OOM(OutOfMemoryError)發生的緣由不僅是堆內存溢出,堆內存溢出只是OOM其中一種狀況,OOM還可能發生在元空間、線程棧、直接內存。數組

下面演示在各個區發生OOM的狀況:dom

堆OOM

public static void main(String[] args)
    {
        List<Byte[]> list=new ArrayList<Byte[]>();
        for(int i=0;i<100;i++){
            //構造1M大小的byte數值
            Byte[] bytes=new Byte[1024*1024];
            //將byte數組添加到list列表中,由於存在引用關係因此bytes數組不會被GC回收
            list.add(bytes);
        }
    }

以上程序設置最大堆內存50M,執行:工具

](D6G~9X}D5B$JE@ALE`4Q3

顯然程序經過循環將佔用100M的堆空間,超過了設置的50M,因此發生了堆內存的OOM。spa

針對這種OOM,解決辦法是增長堆內存空間,在實際開發中必要的時候去掉引用關係,使垃圾回收器儘快對無用對象進行回收。.net

元空間OOM

public static void main(String[] args) throws Exception
    {
        for(int i=0;i<1000;i++){
            //動態建立類
            Map<Object,Object> propertyMap = new HashMap<Object, Object>();  
            propertyMap.put("id", Class.forName("java.lang.Integer"));    
            CglibBean bean=new CglibBean(propertyMap);
            //給 Bean 設置值    
            bean.setValue("id", new Random().nextInt(100));  
            //打印 Bean的屬性id
            System.out.println("id=" + bean.getValue("id"));    
        }
    }

以上代碼經過Cglib動態建立class,設置元數據區大小爲4M:線程

%ORZ7@WET_R$MKU8~A$CA%7

因爲代碼循環建立class,大量的class元數據,存放在元數據區超過了設置的4M空間,所以報元數據區OOM:3d

8OJX})A2Z(7T)%_W$4Q`@HE

解決該OOM的辦法是增大MaxMetaspaceSize參數值,或者乾脆不設置該參數,在默認狀況元空間可以使用的內存會受到本地內存的限制。code

棧OOM

當建立新的線程時JVM會給每一個線程分配棧內存,當建立線程過多,佔用的內存也就越多,這種狀況下有可能發生OOM:對象

public static void main(String[] args) throws Exception {
        //循環建立線程
        for (int i = 0; i < 1000000; i++) {
            new Thread(new Runnable() {
                    public void run() {
                        try {
                            //線程sleep時間足夠長,保證線程不銷燬
                            Thread.sleep(200000000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            System.out.println("created " + i + "threads");
        }
    }

L$Y1U~O[ZWD6@~CIK5GA_~I

很明顯解決此OOM的辦法是減少線程數。

直接內存OOM

public static void main(String[] args) throws Exception {
        
        for (int i = 0; i < 1000000; i++) {
            //申請堆外內存,這個內存是本地的直接內存,並不是java堆內存
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*1024);
            System.out.println("created "+i+" byteBuffer");
        }
    }

ByteBuffer的allocateDirect方法能夠申請直接內存,當申請的內存超過的本地可用內存時,會報OOM:

K4Q7WUYB6Q8}]V7Q55$CCC9

解決該OOM的辦法是適當使用堆外內存,若有必要可顯式執行垃圾回收。(即在代碼中執行System.gc();)

MAT工具使用

當java應用出現故障時,可能須要使用MAT分析問題,找出問題出現的緣由,下面經過一個案例介紹MAT的使用方法:

準備:

事先從程序運行環境上使用jmap工具或者jvisualvm導出一個堆快照文件出來。

使用MAT工具打開:

FW)8Z)LWY@$6COXYT]L@S)5

發現佔用內存最大的對象是AppClassLoader,咱們知道AppClassLoader是用來加載應用的類,所以咱們進一步查看它引用的對象。

PQGEE[C2%3}58X8[IPZ1A98

下圖顯示了AppClassLoader引用的對象空間使用狀況,「Shallow Heap」表示淺堆的大小,淺堆就是類自身所佔用的空間大小,也就是類自己元數據的大小。「Retained Heap」表示深堆的大小,深堆表示該類以及它引用的其餘類所佔用空間的總和,也表示該類被垃圾回收後,所可以釋放的空間大小。(若是該類被回收了,他引用的對象會變成不可達對象所以也會被回收)

@(}%4RVBY%V]4EU[{0_2S%2

隨藤摸瓜,繼續查看深堆佔用最大的對象。

{{7I%O]JG5SMRPDK5AS7ZV0

從上圖能夠看出形成深堆比較大的緣由是程序當中包含了一個ArrayList,他裏面包含有大量的String對象,而且每一個String對象有80216字節大小。

所以針對這個堆的分析基本清楚了,由於程序中包括大量的String對象,而他們又在ArrayList當中,引用關係一直存在,所以沒法被垃圾回收,形成OOM。

MAT其餘功能說明

除了上述使用到的MAT功能外,還有一些功能也是常常用到的。

Histogram:顯示每一個類使用狀況以及佔用空間大小。

@{`LL)UT5](BIZLTYH9O_GR

上圖能夠看到char[]類,有1026個對象,佔用5967480字節的空間,經過上面的分析得出結論是String對象佔用了大部分的空間,而Stirng對象內部存放字符使用char[]來存放的,因此這裏顯示char[]的淺堆大小爲5967480字節也是能夠理解的。

Thread_overview:顯示線程相關的信息。

QOI~NWMMXI1R%N9J5EWSVR7

OQL:經過相似SQL語句的表達式查詢對象信息。

V7GA8FV@HY538J207_~DI%6

上圖經過OQL語句查詢字符串中匹配123的String對象。

結語

本文首先介紹了java程序中出現OOM的幾種狀況,而後經過簡單的案例介紹了MAT的基本用法。

相關文章
相關標籤/搜索