內存分配
內存區域劃分
- 年輕代young區
又分為新生代eden和兩塊survivor區。 - 老年代old/tenured區
- 永久區permanent區
內存分配
- 優先進去eden區域,當eden區域內存達到一定水位,則觸發一次minor GC,將eden區域中活躍對象複製到第一塊survivor from區域,並清除eden區域中的所有對象,eden區繼續接受內存申請,若eden區域再次達到一定水位,則將eden區域中活躍對象複製到第二塊survivor to區域,並清除eden區域中所有對象,同時將第一塊survivor from區域中活躍對象也複製到第二塊survivor to區域,並清除第一塊survivor from區域中所有對象,此時所有存活對象都存在第二塊survivor to區域。eden區域繼續接受內存空間申請。
- 對象第一次從eden區域複製到suvivor區域分代年齡記為1,此後每次在兩塊survivor區域之間發生複製就+1,如果分代年齡達到15,則轉移到老年代。
- 在某個對象從young向老年代轉移時,如果此時老年代區域內存到達一定水位,就會觸發full GC,同時也伴隨着一次minor GC,也就是説所有內存區域同時全部發生GC,產生長時間的stop the world,所以這種情況要儘可能避免。
- 如果fullGC產生了足夠的內存,那就完成這個對象的轉移,但如果依然無法產生足夠的內存,那就會發生OOM。
- 大對象在第一次申請內存時就會直接進入老生代,大對象指需要大量連續內存空間的Java對象,最典型的大對象就是那種很長的字符串以及數組。
虛擬機提供了一個-XX:PretenureSizeThreshold參數,令大於這個設置值的對象直接在老年代分配。目的就是避免在Eden區及兩個Survivor區之間發生大量的內存複製。
垃圾回收算法
複製
需要兩塊大小一樣的內存區域A和B,優先在A區域分配內存,此時B區域為空,當A區域內存達到一定水位時,將A區域內存中活動對象複製到B區域,然後A區域清空。
特點
- 需要兩塊大小一樣的內存區域,因而空間使用率為50%,如果存活率高的話,大量的複製操作效率也存在問題。
標記清除
第一步遍歷標記內存區域中可回收的對象,第二步遍歷清除內存中標記為可回收對象。
特點*
- 內存空間碎片化的問題,標記、清除後會產生大量的不連續內存碎片,空間碎片太可能會導致當以後需要分配大對象時無法找到足夠的連續內存二不得不提前觸發另一次垃圾收集動作。
- 執行效率不穩定,如果Java堆中包含大量對象,而且大部分是需要被回收的,這時必須記性大量標記及清除動作,導致標記和清除兩個過程執行效率都隨對象數量增長而降低。
標記整理
標記過程仍然與“標記-清除”算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的內存。
特點
- 避免內存碎片,但整理操作的效率同樣是個問題。
分代回收
- 對於年輕代 Young("eden" & "survivor from" & "survivor to"),由於對象存活率較低,採用複製算法,不會因大量複製操作導致效率問題,同時合理調節新生代eden和存活區survivor的空間比例(例如8:1:1),提高空間利用率。
- 對於老生代 Old/Tenured,由於對象存活率較高,且本身佔用空間大,不存在額外的擔保,只能使用標記清除或者標記整理算法。