應用JConsole學習Java GC

jopen 9年前發布 | 39K 次閱讀 JConsole Java開發

關于Java GC的知識,好多地方都講了很多,今天我用JConsole來學習一下Java GC的原理。

GC原理

在我的上一篇中介紹了Java運行時數據區,在了解這些的基礎上,對Java GC的理解能更清晰一些。

簡單來講,Java的內存分為堆和棧,其中堆是程序員用的內存,棧是系統用的內存。(這句話不一定正確,但可以這么理解)Java的內存管理主要是管理對象的分配和釋放,或者說內存的分配和回收。在C或C++語言里面,內存是要自己控制的,new之后要delete掉,否則很容易出現內存泄漏。(還記得當時寫C的痛苦,不過通過寫C代碼,很好的了解了內存的分配機制)在Java里面,分配內存和回收內存的事情是Jvm來管的。

Jvm有自己的機制來管理內存,具體的細節算法這里不講,主要講大概的處理方式。Jvm的GC主要處理堆內存,JVM內存模型中的堆可以細分為 Young Generation和Old Generation(又稱為Tenure Space),其中Young Generation又分為Eden Space(Eden是伊甸園的意思,老鷹樂隊有首歌叫Long Road out of Eden)和Survivor spaces。如下圖所示:
應用JConsole學習Java GC

具體GC的過程如下:

  1. 在Young Generation中,有一個叫Eden Space的空間,主要是用來存放新生的對象,還有兩個Survivor Spaces(from、to),它們的大小總是一樣,它們用來存放每次垃圾回收后存活下來的對象。
  2. 在Old Generation中,主要存放應用程序中生命周期長的內存對象。
  3. 在Young Generation塊中,垃圾回收一般用Copying的算法,速度快。每次GC的時候,存活下來的對象首先由Eden拷貝到某個 SurvivorSpace,當Survivor Space空間滿了后,剩下的live對象就被直接拷貝到OldGeneration中去。因此,每次GC后,Eden內存塊會被清空。
  4. 在Old Generation塊中,垃圾回收一般用mark-compact的算法,速度慢些,但減少內存要求。
  5. 垃圾回收分多級,0級為全部(Full)的垃圾回收,會回收OLD段中的垃圾;1級或以上為部分垃圾回收,只會回收Young中的垃圾,內存溢出通常發生于OLD段或Perm段垃圾回收后,仍然無內存空間容納新的Java對象的情況。

翻譯成為更簡單的語言:

  1. 內存首先在Eden中分配;
  2. Eden中滿了之后,挪到Survivor Space,清空Eden;
  3. Survivor Space滿了之后,挪到Old Generation;
  1. 如果這三個區域都滿了,內存溢出OutOfMemory。

那上面這個過程如何得知呢?今天我就用JConsole給大家演示一下如何看GC的過程。

JConsole介紹

先介紹一下JConsole。

JConsole是什么

JConsole 是一個內置 Java 性能分析器,可以從命令行或在 GUI shell 中運行。您可以輕松地使用 JConsole(或者,它更高端的 “近親” VisualVM )來監控 Java 應用程序性能和跟蹤 Java 中的代碼。

如何啟動JConsole

JConsole是一個程序,在windows里面找到JConsole.exe,雙擊啟動即可。啟動之后的界面如下圖所示:

應用JConsole學習Java GC

可以看到,JConsole即可以連接本地進程,也可以連接遠程進程。

應用JConsole學習GC

那么,如何用JConsole學習GC的過程呢?我們首先要設計一個程序,這個程序一直保持內存增長,直到發生內存泄漏。在這個過程中,我們應該JConsole觀察GC的過程。

寫一個內存泄漏的程序

寫一個內存泄漏的程序比較簡單,讀一個很大的文件到內存中,直至內存溢出,代碼如下:

package com.chzhao.test;import java.io.BufferedReader;import java.io.FileReader;import java.io.IOException;import java.util.ArrayList;import java.util.List;public class StringTest {
    public static void main(String[] args) throws InterruptedException,
            IOException {
        List<String> list = new ArrayList<String>();
        String file = "d:/wisdombud-unicom.log.2014-12-05";
        BufferedReader in = null;
        in = new BufferedReader(new FileReader(file));
        while (true) {
            String lineMsg = in.readLine();
            if (lineMsg == null || lineMsg.equals("")) {
                break;
            }
            list.add(lineMsg);
            Thread.sleep(10);
            System.out.println(list.size());
        }
        in.close();
    }
}

這個文件是我程序的一個日志文件,足夠大,300M+

設置啟動參數

除了寫程序之外,為了快點出現內存泄漏,我們把啟動內存調小。在Eclispse里面調VM arguments就可以,內容為:

-Xms4M -Xmx4M

我設置了4M。

通過JConsole觀察

首先把程序運行起來,在通過JConsole連接到程序上。
因為我們只關心內存,切換到內存標簽頁。有一個下拉圖表,可以觀察不同的內存情況。右下角有個柱狀圖,顯示的是堆內存和棧內存的占用情況。其中堆內存包括Eden Space、Survivor Space和Tenured Gen。如下圖所示:
應用JConsole學習Java GC

可以看到,隨著程序的運行,Eden Space會逐漸變滿,到100%之后,Eden Space會變成0%,Survivor Space會變大;Survivor Space變100%之后,會挪到Tenured Gen中,Survivor Space變0%。這個過程和上面講到的GC過程是一樣的,很直觀。

也可以觀察上面的曲線圖,Eden Space的圖是類似波形圖,每次到波谷都是進行了一次GC。Tenured Gen則是類似梯田,一直向上漲,直到內存溢出。如下兩張圖所示。

應用JConsole學習Java GC

Eden Space的波形圖

應用JConsole學習Java GC

Tenured Gen的波形圖

在VM摘要標簽頁,能看到更多JVM的信息,可以看到分配的內存已經等于堆的最大值了,所以內存溢出。(為什么堆的最大值是5942Kb我沒搞明白,不應該小于4M嗎?如果哪位懂請指點一下。
應用JConsole學習Java GC

結論

通過JConsole工具學習GC的過程,生動形象,很容易明白GC是如何進行的。然而這些知識都屬于GC的基礎知識,如果要了解更多GC的知識,還要多學習。

參考:

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