Jvm系列:從一個題目簡析GC垃圾回收

1、概述

閒來有空翻翻書,撿撿一些基礎點,就當靜下心多寫字。
Java基礎的東西不管怎麼樣都會想到JVM,而提JVM必然想到最多見的一些點:字節碼加載,類初始化,方法執行,對象內存分配和回收,線程和鎖機制等等。概括整理的時候,怎麼能夠少了它們。不過,我打算換個方式,不寫太多概念(網上一搜一把的),想從一些代碼、例子、題目或者疑問等方面來寫寫。算法

2、背景知識

JVM內存管理須要理解的點:內存空間的劃分、內存分配和內存回收。安全

  • 內存空間:認識方法區、堆區、本地方法棧等幾個空間,瞭解堆的分代管理。服務器

  • 內存分配:在Java中這塊比較容易,就是棧和堆,而建立對象都在堆區。併發

  • 內存回收:實戰上最主要是關注何時會觸發回收(GC)和回收對性能的影響,學習上能夠了解不一樣回收起和回收算法。jvm

驗證和測試須要的點:常見的啓動參數、GUI類工具。工具

  • 常見的啓動參數:-Xms -Xmx -Xmn 等等性能

  • GUI類工具:JProfiler(推薦)、JVisualVM、MAT、JMap、JHat。學習

3、一個GC題目

1)當用-Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC 執行上面的代碼時會執行幾回Minor GC和幾回Full GC呢?
2)分別說明你的結果是如何推出來的?測試

public static void main(String[] args) throws Exception{
    List<Object> caches=new ArrayList<Object>();
    for(int i=0;i<7;i++){
        caches.add(new byte[10241024*3]);
    }
    caches.clear();
    for(int j=0;j<2;j++){
        caches.add(new byte[10241024*3]);
    }
    Thread.sleep(5000);
}

先思考下,別忙着往下看,萬一個人分析並不對呢!!
先思考下,別忙着往下看,萬一個人分析並不對呢!!
先思考下,別忙着往下看,萬一個人分析並不對呢!!spa

4、小白式的分析過程

第一步分析啓動參數:
首先,看到"UseParallelGC"參數,表示這裏採用的是「Parallel Scavenge+Serial Old」。那麼,從回收器的類型,能夠知道堆的新生代是基於複製算法的gc(即內存模型有from和to區),堆的老年代則基於標記-整理算法。
其次,看到"-Xms30m -Xmx30m"參數,表示堆區最大爲30M,且不會動態擴展。
最後,再看到"-Xmn10m"參數,表示新生代區爲10m,並且採用默認的7.5:1,即Eden7.5M(7680k),而from和to區各爲1.25M。

第二步分析第一個循環
i0(即表示i=0產生的對象,後面規律相同)和i1 會直接放入Eden區。由於優先放Eden區,並且夠放,此時Eden爲 6m/7.5m.(二者表示 已用/剩餘 。此處的6m爲近似值,其餘jvm對象之類佔用內存的,不細討論)。

i2來了,要放入Eden區,發現空間不夠。觸發MinorGC。而後把i2放到Eden。
結果將i0~i1直接誒轉入老年代 ,緣由是對象3m太大放不了From區(前面提到才1.25m)。
此時Eden區 3m/7.5m 老年代6m/20m
gc的log --> [PSYoungGen: 6451K->272K(8960K)] 6451K->6416K(29440K)

i3來了,直接繼續放入Eden區。 此時Eden區 6m/7.5m 老年代6m/20m

i4來了,跟i2同樣的狀況,發現Eden不夠放了。再次觸發MinorGC。而後把i4放到Eden。
結果將i2~i3直接誒轉入老年代,去陪i0和i1了。
此時Eden區 3m/7.5m 老年代12m/20m
gc的log --> [PSYoungGen: 6650K->256K(8960K)] 12794K->12544K(29440K)

i5來了,繼續放入Eden區,此時加上前面i4,Eden區 6m/7.5m 老年代12m/20m

i6來了,跟前面i二、i4狀況同樣,觸發了一次MinorGC。而後把i6放到Eden。
結果: i6 在Eden,i0~i5 6個在老年代。 此時Eden區 3m/7.5m 老年代18m/20m
gc的log -->[PSYoungGen: 6453K->224K(8960K)] 18741K->18656K(29440K)
這裏還多了一次FullGC。
gc的log -->[PSYoungGen: 240K->0K(8960K)] [PSOldGen: 18432K->18593K(20480K)]
暫時未能徹底分析明白這點,但gc日誌來猜想,應該是標記-整理起做用了,爲了整理出連續的空間吧。

第三步: 因爲執行了caches.clear(); 等於宣告前面的i0~i6的7個對象都不可用了(即GC Roots不可達)
但還因爲各區都有足夠大的空間,只要程序運行未達到"GC安全點"是不會觸發GC的。

第四步:
j0來了,它會繼續放到Eden區,此時陪着還沒回收的i6。
此時Eden區 6m/7.5m 老年代18m/20m

j1來了,這時候內存管理會發現新生代和老年代都不夠空間申請了,即觸發FullGC。
結果:從老年代把不可用的i0~i5所有幹掉,再把j0移入老年代,再把新生代中的i6幹掉,而後j1放入新生代。
此時Eden區 3m/7.5m 老年代3m/20m
gc的log -->[PSYoungGen: 6178K->0K(8960K)] [PSOldGen: 18593K->3233K(20480K)]

5、經過運行程序,驗證分析

運行上面的程序,參數爲: -Xms30m -Xmx30m -Xmn10m -XX:+UseParallelGC -XX:+PrintGCDetails
加上-XX:+PrintGCDetails,這樣能夠打印GC的log狀況來驗證前面的分析。

ps:不一樣的虛擬機版本打印的輸出多少會有差別,如下log,是我在sum的1.6.0_43版本打印出來的,另外刪減掉一些不關心的輸出。

[GC [PSYoungGen: 6451K->320K(8960K)]
[GC [PSYoungGen: 6698K->240K(8960K)]
[GC [PSYoungGen: 6437K->240K(8960K)]
[Full GC [PSYoungGen: 240K->0K(8960K)] [PSOldGen: 18432K->18594K(20480K)] 18672K->18594K(29440K)
[Full GC [PSYoungGen: 6178K->0K(8960K)] [PSOldGen: 18594K->3234K(20480K)] 24773K->3234K(29440K)
Heap
PSYoungGen total 8960K, used 3248K
    >eden space 7680K, 42% used
    >from space 1280K, 0% used
    >to space 1280K, 0% used
PSOldGen total 20480K, used 3234K
PSPermGen total 21248K, used 3043K

6、最後補充涉及到的知識點

6.1 關於堆區分代管理

新生代GC(Minor GC):主要是發生在新生代的收集動做,聽說IBM作過調查,絕大多數對象都是朝生夕死,因此MinorGC很是頻繁,速度也比較快。
老年代GC(Full GC):是指發生在老年代的收集動做,可是一般也會對年輕代進行垃圾收集。

6.2 GC幾個參數說明

-Xmx 設置JVM最大可用內存爲30M。
-Xms 設置JVM擴展內存爲30M。(此值通常設置與-Xmx相同,以免每次垃圾回收完後JVM從新分配內存)
-Xmn:設置年輕代大小爲10m。 (整個堆大小=年輕代大小 + 年老代大小 持久代大小 : 持久代PermGen是非堆的,能夠經過jconsole查看)
-Xss128k:(設置每一個線程的堆棧大小)
持久代,是經過PermSize和MaxPermSize
-XX:+UseParallelGC:選擇垃圾收集器爲並行收集器。

6.3 不一樣類型處理器

串行處理器: 適用數據量比較小(100M左右);單處理器下而且對響應時間無要求的應用。 缺點:只能用於小型應用
並行處理器: 適用「對吞吐量有高要求」,多CPU、對應用響應時間無要求的中、大型應用。舉例:後臺處理、科學計算。 缺點:應用響應時間可能較長
併發處理器: 適用「對響應時間有高要求」,多CPU、對應用響應時間有較高要求的中、大型應用。舉例:Web服務器/應用服務器、電信交換、集成開發環境。

6.4 其它

GC Roots不可達:GC回收過程判斷對象是否能夠回收的一種方式,叫可達性測試!打印gc日誌:啓動程序時加vm參數-XX:+PrintGCDetails。

相關文章
相關標籤/搜索