一、新生代
新生代主要用來存放新生的對象。一般占據堆空間的1/3。在新生代中,保存着大量的剛剛創建的對象,但是大部分的對象都是朝生夕死,所以在新生代中會頻繁的進行MinorGC,進行垃圾回收。新生代又細分為三個區:Eden區、SurvivorFrom、ServivorTo區,三個區的默認比例為:8:1:1。
- Eden區:Java新創建的對象絕大部分會分配在Eden區(如果對象太大,則直接分配到老年代)。當Eden區內存不夠的時候,就會觸發MinorGC(新生代采用的是復制算法),對新生代進行一次垃圾回收。
- SurvivorFrom區和To區:在GC開始的時候,對象只會存在於Eden區和名為From的Survivor區,To區是空的,一次MinorGc過后,Eden區和SurvivorFrom區存活的對象會移動到SurvivorTo區中,然后會清空Eden區和SurvivorFrom區,並對存活的對象的年齡+1,如果對象的年齡達到15(因為對象頭用4bit記錄年齡),則直接分配到老年代。MinorGC完成后,SurvivorFrom區和SurvivorTo區的功能進行互換。下一次MinorGC時,會把SurvivorTo區和Eden區存活的對象放入SurvivorFrom區中,並計算對象存活的年齡。
二、老年代
老年代主要存放應用中生命周期長的內存對象。老年代比較穩定,不會頻繁的進行MajorGC。而在MajorGC之前才會先進行一次MinorGc,使得新生的對象進入老年代而導致空間不夠才會觸發。當無法找到足夠大的連續空間分配給新創建的較大對象也會提前觸發一次MajorGC進行垃圾回收騰出空間。
在老年代中,MajorGC采用了標記—清除算法:首先掃描一次所有老年代里的對象,標記出存活的對象,然后回收沒有標記的對象。MajorGC的耗時比較長。因為要掃描再回收。MajorGC會產生內存碎片,當老年代也沒有內存分配給新來的對象的時候,就會拋出OOM(Out of Memory)異常。
三、永久代
永久代指的是永久保存區域。主要存放Class和Meta(元數據)的信息。Classic在被加載的時候被放入永久區域,它和存放的實例的區域不同,在Java8中,永久代已經被移除,取而代之的是一個稱之為“元數據區”(元空間)的區域。元空間和永久代類似,都是對JVM中規范中方法的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存的限制。類的元數據放入native memory,字符串池和類的靜態變量放入java堆中。這樣可以加載多少類的元數據就不再由MaxPermSize控制,而由系統的實際可用空間來控制。
采用元空間而不用永久代的原因:
- 為了解決永久代的OOM問題,元數據和class對象存放在永久代中,容易出現性能問題和內存溢出。
- 類及方法的信息等比較難確定其大小,因此對於永久代大小指定比較困難,大小容易出現永久代溢出,太大容易導致老年代溢出(堆內存不變,此消彼長)。
- 永久代會為GC帶來不必要的復雜度,並且回收效率偏低。
四、HotSpot VM GC
-
Minor GC(新生代的GC):新生代通常存活時間較短基於Copying算法進行回收,所謂Copying算法就是掃描出存活的對象,並復制到一塊新的完全未使用的空間中,對應於新生代,就是在Eden和FromSpace或ToSpace之間copy。新生代采用空閑指針的方式來控制GC觸發,指針保持最后一個分配的對象在新生代區間的位置,當有新的對象要分配內存時,用於檢查空間是否足夠,不夠就觸發GC。當連續分配對象時,對象會逐漸從Eden到Survivor,最后到老年代。Full GC:針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局范圍的GC;
- Major GC(老年代的GC ):基本上發生了一次Major GC 就會發生一次 Minor GC。並且Major GC 的速度往往會比 Minor GC 慢 10 倍。
-
- 對於一個大對象,我們會首先在Eden 嘗試創建,如果創建不了,就會觸發Minor GC
- 隨后繼續嘗試在Eden區存放,發現仍然放不下
- 嘗試直接進入老年代,老年代也放不下
- 觸發 Major GC 清理老年代的空間
- 放的下 成功
- 放不下 OOM(發生Full GC)
-
-
Full GC:針對整個新生代、老生代、元空間(metaspace,java8以上版本取代perm gen)的全局范圍的GC;