Java初識 VisualVM

目的:熟悉Visual GC動態展現GC的效果,而且簡單熟悉年輕代與老年代的內存大小分配對彼此產生的影響。java

環境:JDK八、IDEA2017(包含VisualVM插件)、windows 64位4核系統windows

測試代碼:(用來不停地建立對象,而且一直運行)工具

public class GcTest {

    public static void main(String[] args) throws InterruptedException {
        while(true){
            GcTestMethod g = new GcTestMethod();
            g.testAllocation();
        }
    }
}


class GcTestMethod{
    public void testAllocation() {}
}

首先,設定初始堆大小、最大堆大小都爲50M,年輕代採用默認分配方式(觀察來看默認爲整個堆內存大小的1/3)測試

VM Option:-Xms50M -Xmx50Mspa

經過上面的參數運行一段時間後,咱們來觀察Java VisualVM 中 Visual GC的動態走勢以下,能夠看到仍是很規律的。插件

 

經過jstat觀察JVM內存的分配狀況,咱們能夠看到年輕代分配16896kb(16.5m),老年代分配34304kb(33.5m),年輕代是按照默認1/3堆大小來分的3d

運行一段時間後,咱們經過jstat觀察程序的GC狀況:日誌

年輕代GC  10727次,總耗時18.554s   Full GC  7次 總耗時0.443scode

年輕代GC平均耗時 0.001729654s   Full GC平均耗時0.063285714s對象

而後,咱們在原來的運行參數基礎上,將年輕代大小固定爲10m,整個堆內存大小不變,讓咱們來看一下減少年輕代大小後對整個GC的動態走勢有什麼影響。

VM Option:-Xms50M -Xmx50M -Xmn10M

經過上面的參數運行一段時間後,咱們來觀察Java VisualVM 中 Visual GC的動態走勢以下,能夠看到和一開始同樣,仍是很規律,區別可能就是GC的頻率和效率了。

經過jstat觀察JVM內存的分配狀況,咱們能夠看到年輕代分配10240kb(10m),老年代分配40960kb(40m),是按照咱們設置的參數來運行的。

運行一段時間後,咱們經過jstat觀察程序的GC狀況:

年輕代GC  14927次,總耗時22.951s   Full GC  8次 總耗時0.559s

年輕代GC平均耗時 0.001537549s   Full GC平均耗時0.069875s

和前面的例子相比能夠看到,當咱們縮小了年輕代的空間後,年輕代GC次數明顯上升,也就驗證了年輕代太小所引起的年輕代GC頻繁的問題

 

剛纔咱們經過固定年輕代的大小爲10m來間接縮小年輕代大小,經過觀察咱們發現總體上並無影響GC的走勢(可能參數不夠小不明顯),可是GC的頻率確定比以前要大了。

此次咱們調整整個堆內存爲20m,年輕代大小固定10m。經過這種方式縮減老年代的大小,再來看看GC的走勢如何。

VM Option:-Xms20M -Xmx20M -Xmn10M

經過運行一段時間咱們再來觀察Java VisualVM 中 Visual GC的GC走勢,發現老年代的空間很快就被填滿了,而且內存始終釋放不出太多,年老代始終處於飽和狀態。

前面咱們年老代的內存爲40m,如今爲10m,經過和上面的GC走勢圖對比,咱們能夠猜測,是否是因爲年老代的縮小,致使程序中一直長期存在的對象在年老代中一直沒法釋放,而且及時Full GC 也不能釋放太多,而且很快又被填滿。這樣下去的結果將是一直的Full GC,年老代一直釋放不出足夠的空間。 

經過jstat觀察JVM內存的分配狀況,咱們能夠看到年輕代分配10240kb(10m),老年代分配10240kb(10m),是按照咱們設置的參數來運行的。

運行一段時間後,咱們經過jstat觀察程序的GC狀況:

年輕代GC  1967次,總耗時11.595s   Full GC  740次 總耗時44.944s

年輕代GC平均耗時 0.005894764s   Full GC平均耗時0.060735135s

這個和前例比較能夠明顯看出,當咱們縮小了老年代的空間後(由50m縮減至10m),Full GC的頻次爆炸性的上升了,緣由也很明顯,老年代的空間不足始終處於飽和狀態,所以頻繁的Full Gc。

 

經過上面的例子,咱們看到了年老代的空間太小致使的詬病,年老代始終被填滿,一直進行Full GC。在剛纔的例子中,咱們再次縮小年輕代的空間,由上面的10m縮減爲5m,總體堆內存大小不變20m

VM Option:-Xms20M -Xmx20M -Xmn5M

經過運行一段時間的觀察咱們發現,減少了年輕代的大小後,年老代的空間佔用問題的帶了緩解,再也不被始終塞滿,可是GC的頻率明顯增長。

經過jstat觀察JVM內存的分配狀況,咱們能夠看到年輕代分配5120kb(5m),老年代分配15360kb(10m),是按照咱們設置的參數來運行的。

運行一段時間後,咱們經過jstat觀察程序的GC狀況:

年輕代GC  20379次,總耗時35.923s   Full GC 72次 總耗時3.376s

年輕代GC平均耗時 0.001762746s   Full GC平均耗時0.046888889s

再次咱們之前一個例子相比,縮減年輕代空間,提高了老年代空間後,相比以前的數據,年輕代GC頻率提升,Full GC頻率降低。

 

這幾個例子中的平均耗時我的感受沒有什麼特別多的參考價值,緣由是代碼都同樣,年輕代的GC效率影響因素本例中主要就是年輕代空間的大小,Full GC的影響效率影響因素本例中主要就是整個堆空間的大小。

 

經過本例的實驗,咱們能夠大體得出結論,合理控制堆內存的大小,而且合理分配年輕代與年老代的佔比。年輕代太小,將致使年輕代的頻繁GC,年輕代中存活的對象將更快速的進入老年代(年輕代中默認年齡計數超過15的對象將分配進入老年代),而且可能致使年輕代對象直接進入老年代,若是此時老年代滿了,會觸發Full GC。老年代太小將致使頻繁的老年代GC以及可以stop the word 的Full GC,所以咱們又會有新的疑問,如何合理的分配年輕代與老年代的佔比?答案是不固定的,只有根據自身項目的狀況來合理的進行分配,而且經過不斷地實驗,才能最終得出最合理的分配。

---------------------------------------------------------------------------------------------------------------------------------

對象與基本類型,那個更加GC友好?

public static void main(String[] args) {
    //封裝類型
    while(true){
        Long n = 99999999999999L;
    }
}

public static void main(String[] args) {
    while(true){
        //基本類型
        long n = 99999999999999L;
    }
}

從兩種代碼的GC走勢來看,不難發現基本類型針對GC更加友好,沒有頻繁的GC發生,反觀封裝類型,卻出現了頻繁的GC,所以不可貴出結論,基本類型相比於封裝類型針對GC更加友好。

那麼爲何基本類型GC更加友好呢?

GC的本質就是垃圾回收,回收的是咱們代碼運行時沒用的對象,既然GC的行爲針對的是對象,那麼越少對象的生成,對堆空間的佔用就越少,GC的執行次數就越少,對GC就越是友好,咱們的代碼效率就越高(不會由於GC的執行(YGC、FGC)而影響程序的執行)

---------------------------------------------------------------------------------------------------------------------------------

經過Eclipse Memory Analyzer工具分析OutOfMemoryError異常

VM Option:

-Xms20M -Xmx20M -XX:+PrintGCDetails -Xloggc:D:\workSpace\58QF\JavaInAction\gc.log -XX:+HeapDumpOnOutOfMemoryError

將堆內存設置的小一點,而且打印GC信息以及輸出GC日誌,當發生OutOfMemoryError時生成Dump信息。

public class GcTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        for(int i=0;i<10000000;i++){
            String str = new String();
            list.add(str);
        }
    }
}

運行一段時間後:

生成日誌以下

gc.log最後幾行

43.132: [Full GC (Ergonomics) [PSYoungGen: 5632K->5632K(6144K)] [ParOldGen: 13568K->13567K(13824K)] 19200K->19199K(19968K), [Metaspace: 3023K->3023K(1056768K)], 0.0919624 secs] [Times: user=0.31 sys=0.00, real=0.09 secs]

43.226: [Full GC (Ergonomics) [PSYoungGen: 5632K->5632K(6144K)] [ParOldGen: 13571K->13569K(13824K)] 19203K->19201K(19968K), [Metaspace: 3052K->3052K(1056768K)], 0.1015494 secs] [Times: user=0.31 sys=0.00, real=0.10 secs]

43.546: [Full GC (Ergonomics) [PSYoungGen: 5632K->0K(6144K)] [ParOldGen: 13572K->595K(13824K)] 19204K->595K(19968K), [Metaspace: 3093K->3093K(1056768K)], 0.0061648 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]

Heap

 PSYoungGen      total 6144K, used 300K [0x00000000ff980000, 0x0000000100000000, 0x0000000100000000)

  eden space 5632K, 5% used [0x00000000ff980000,0x00000000ff9cb020,0x00000000fff00000)

  from space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)

  to   space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)

 ParOldGen       total 13824K, used 595K [0x00000000fec00000, 0x00000000ff980000, 0x00000000ff980000)

  object space 13824K, 4% used [0x00000000fec00000,0x00000000fec94d68,0x00000000ff980000)

 Metaspace       used 3259K, capacity 4486K, committed 4864K, reserved 1056768K

  class space    used 349K, capacity 386K, committed 512K, reserved 1048576K

 

使用Eclipse Memory Analyzer分析Dump文件:

能夠看到String對象實例已經佔據了堆內存97.4%的空間

咱們能夠發現,是java.lang.String這個對象致使的OutOfMemoryError,對比代碼,仍是很直觀的

相關文章
相關標籤/搜索