Java體系中的自動記憶體管理主要包括了2個方面:
- 自動地給物件分配記憶體。
- 自動地回收分配給物件地記憶體。
一. 記憶體分配規則
1.優先在Eden區分配
大多數情況下,JVM會在 Eden
區優先分配物件,如果 Eden
沒有足夠的空間,則進行一次 Minor GC
。通過引數 -XX:+PrintGCDetails
可以讓虛擬機器在進行垃圾回收時列印日誌,方便我們看到回收前後的記憶體佔用情況。
例: 假如現在記憶體大小指定如下:
- 新生代 ->10M
- Eden區 -> 8M
- from區 -> 1M
- to 區 -> 1M
- 老年代 -> 10M
然後我們又先後在程式碼中建立4個物件:
byte[] byte1 = new byte[2MB];
byte[] byte2 = new byte[2MB];
byte[] byte3 = new byte[2MB];
byte[] byte4 = new byte[4MB];
複製程式碼
當建立完第三個物件後,Eden區已經用掉了6M的空間來存放 byte1,byte2,byte3 三個物件,再建立第四個物件時,Eden區加上一個from區已經放不下了,如先前所述,此時會觸發一次 MinorGC
,將三個2MB的物件轉移到老年代中,騰出Eden區的空間給 第四個物件。
所以,執行後的記憶體情況如下:
- 新生代 -> 10M
- Eden 區 -> 8M (剩餘4M) 存放 byte4
- from區 -> 1M
- to區 -> 1M
- 老年代 -> 10M (剩餘4M) 存放byte1,byte2,byte3。
2.大物件直接進入老年代
通過 -XX:PretentureSizeThreshold
引數設定大於這個值的物件直接分配到老年代。
3.長期存活的物件進入老年代
怎麼算是長期存活 ?
JVM給每個物件定義了一個 物件年齡計數器
。當物件一開始被分配到新生代Eden區,經過一次 MniorGC
後仍然存活,並且Survivor區能夠容納它,則此物件被轉移到 Survivor
區,年齡變為1。
在 Survivor
區中的物件沒熬過一次 MniorGC
,年齡就漲1,當年齡達到我們設定的年齡閾值(JVM預設設定15)時,就會進入老年代。15歲就已經步入老年....
年齡閾值可通過引數 -XX:MaxTenuringThreshold = 指定值
來設定。
對物件年齡判定的優化
JVM中,如果Survivor區中的相同年齡的所有物件的大小總和大於Survivor空間的一半,那麼這些同窗們就直接進入老年代。無須等到上面的年齡閾值。
二. 空間分配擔保機制與回收策略(Mnior GC還是Full GC)
首先介紹一下 Mnior GC
和 Full GC
的區別:
MniorGC : 發生在新生代的垃圾回收活動,由於新生代的Java物件 短命的特性,這種垃圾回收活動頻繁,回收速度較快。(就像掃碎紙屑) Full GC : 發生在老年的垃圾回收活動,不過出現一次 Full GC,也會伴隨著一次 MniorGC,由於老年代中的物件基本都是大物件,長命,所以Full GC的速度比Mnior GC 的速度慢10倍以上。(就像搬大石頭)
新生代的垃圾回收演算法採用的是 複製演算法 ,當進行一次 Mnior GC
時,會將新生代的活動區域( Eden區
和Survivor中的 From區
)中的存活物件複製到 Survivor中的 to區
,如果 to 區的記憶體不足以放下這些物件,那麼這時就需要老年代出馬,進行分配擔保機制,將放不下的物件放到老年代。
所以,在進行 Monior GC
前,JVM會做以下流程的檢查,以確認老年代是否能夠放得下那些物件,來選擇進行 Mnior GC
還是 Full GC
。
Reference:
深入理解Java虛擬機器