原文連結:最簡單例子圖解jvm記憶體配置設定和回收
jvm采用分代垃圾回收。在jvm的記憶體空間中把堆空間分為年老代和年輕代。将大量(據說是90%以上)建立了沒多久就會消亡的對象存儲在年輕代,而年老代中存放生命周期長久的執行個體對象。年輕代中又被分為eden區(聖經中的伊甸園)、和兩個survivor區。新的對象配置設定是首先放在eden區,survivor區作為eden區和old區的緩沖,在survivor區的對象經曆若幹次收集仍然存活的,就會被轉移到年老區。

簡單講,就是生命期短的對象放在一起,将少數生命期長的對象放在一起,分别采用不同的回收政策。生命期短的對象回收頻率比較高,生命期長的對象采用比較低回收頻率,生命期短的對象被嘗試回收幾次發現還存活,則被移到另外一個地方去存起來。就像現在夏天了,勤勞的douma把doudou和douba常穿的衣服放在順手的地方,把冬天的衣服打包放在櫃子另一個地方。雖然把doudou的小衣服類比成虛拟機裡的對象有點不合适,大緻意思應該就是這樣。
本文中通過最簡單的一個例子來demo下這個過程,代碼很短,很簡單,希望剖析的細一點,包括每一步操作後對象的配置設定和回收對記憶體堆産生的影響。設定上包括對堆中年輕代(年輕代中eden區和survivor區)、年老代大小的設定,以及設定門檻值控制年輕代到年老代的晉升。
下面是最簡單的代碼,通過代碼的每一步的執行來剖析其中的規則。
通過jvm 參數設定幾個區域的大小,結合代碼執行可以觀察到對象在堆上配置設定和回收的過程。執行參數如下:
通過設這-xms200m -xmx200m 設定java堆大小為200m,不可擴充,-xmn100m設定其中100m配置設定給新生代,則200-100=100m,即剩下的100m配置設定給老年代。-xx:survivorratio=8設定了新生代中eden與survivor的空間比例是1:8。
執行上述代碼結果如下:
從中可以看到eden 大小為81920k, survivor中from區域和to區域大小都是10240k。新生代總的92160k指的是eden和一個survivor區域的和。
即原始的記憶體如圖:
為了示範年輕代對象晉級到年老代的過程。需要設定一個vm參數, 這裡設定maxtenuringthreshold=1。前面不設定的時候,預設maxtenuringthreshold取值15。當設定不同的門檻值,jvm在記憶體處理會有不同。我們重點觀察觀察alloc1 這麼小塊區域在不同的maxtenuringthreshold參數設定下的遭遇。
這時候jvm的參數中加上maxtenuringthreshold=1如下:
可以看到輸出結果是:
下面觀察每一步語句執行後,jvm記憶體的變化情況,并給出解析。
檢視源代碼
列印幫助
<code>1</code>
<code>alloc1 =</code><code>new</code> <code>byte</code><code>[tenmb /</code><code>5</code><code>];</code>
後,根據分代政策,在新生代的eden區配置設定2m的空間存儲對象。
<code>alloc2 =</code><code>new</code> <code>byte</code><code>[</code><code>5</code> <code>* tenmb];</code>
前面alloc1配置設定2m後,因為eden的80m空間還有80-2=78m還可以容納下allocation2要求的50m空間,是以接着在eden區域配置設定。
<code>alloc3 =</code><code>new</code> <code>byte</code><code>[</code><code>4</code> <code>* tenmb];</code>
還是嘗試在eden上配置設定,但是eden空間隻剩下28m,不能容納alloc3要求的40m空間。于是觸發在新生代上的一次gc,将eden區的存活對象轉移到survivor區。在這個裡先将2m的alloc1對象存放(其實是copy,參見java 垃圾回收政策的描述)到from區,然後copy 50m的alloc2對象,顯然survivor區不能容納下alloc2對象,該對象被直接copy到年老代。需要說明的是複制到survivor區的對象在經曆一次gc後期對象年齡會被加一。
在eden區gc後騰出空間可以存放allocation3的40m對象,則alloc3配置設定40m對象如圖:
<code>alloc3 =</code><code>null</code><code>;</code>
這是eden上alloc3配置設定的的40m對象則變成可被回收狀态。
<code>allocation3 =</code><code>new</code> <code>byte</code><code>[</code><code>6</code> <code>* tenmb];</code>
還是嘗試先在eden區上配置設定,發現超出了eden區域的容量,則再次觸發新生代上的一次gc。首先eden上配置設定的40m對象因為沒有被再使用,則直接被回收。而根據的設定不同,這次gc的行為會稍有不同。
先看maxtenuringthreshold不設定,即取預設值15的時候。eden區上無用的40m回收後,再考察survivor區域的對象是否滿足對象晉升老年代的年齡門檻值,發現from中的2m對象,年齡是1,不滿足晉升條件,則不被處理,隻是把survivor區域的經曆這次回收未被處理的對象age加一,即新的age為2.如圖:
通過輸出日志也顯示:經過這次回收年輕代大小,由43114k變為2184k,總的大小由94344k變為53384k,即反映出回收了40m無用對象。
在年輕代上gc後騰出空間後,新的alloc3的60m空間被配置設定到eden 區域上。配置設定後堆如下:
以上是不設定晉升門檻值maxtenuringthreshold情況下進行的gc,以及gc後alloc3的配置設定。
再看看當maxtenuringthreshold設定為1的情況。同樣eden區上無用的40m回收後,再考察survivor區域的對象是否滿足對象晉升老年代的年齡門檻值,發現from中的2m對象,年齡是1,滿足晉升條件,則survivor區域滿足年齡的對象被拷貝到年老區。
通過日志顯示年輕代的大小被清0了,表示survivor的存活對象因為滿足晉升條件被移到被移到年老代了。
同樣的,gc完後會在eden上配置設定空間來存儲alloc3對象,這種情況下堆結構如圖:
比較上面兩個圖,發現差别就僅僅在于survivor中的2m對象是否被認為生存時間足夠長科院被移到年老代中去。從上面日志高亮部分from區域的最終存儲也可反映出了這個差别。
比較前面兩個日志可以看到:總的大小和上面設定和不設定maxtenuringthreshold(其實是maxtenuringthreshold設定1還是15)沒有關系,都是由94344k變為53384k,即都是回收了40m eden區域無用對象。第n次gc時存活的滿足晉升條件則由survivor移到年老代,不滿足的還留在survivor區域,堆的總的大小沒有變。
上面通過最簡單的例子示意了下在jvm堆上對象是如果配置設定的,當空間不足時,是如何調整回收的。希望可以對jvm的堆上結構和gc思路有個基本的了解。當然相關參數(其實反映的是機制)遠比這個複雜,有挺多細節,更多的是在實踐中來體會。