JVM中的堆一般分為三大部分:新生代、老年代、永久代,其大致的占比如下:
一、新生代
新生代主要用來存放新生的對象。一般占據堆空間的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,則直接分配到老年代。MinorGC完成后,SurvivorFrom區和SurvivorTo區的功能進行互換。下一次MinorGC時,會把SurvivorTo區和Eden區存活的對象放入SurvivorFrom區中,並計算對象存活的年齡。
二、老年代
老年代主要存放應用中生命周期長的內存對象。老年代比較穩定,不會頻繁的進行MajorGC。而在MaiorGC之前才會先進行一次MinorGc,使得新生的對象進入老年代而導致空間不夠才會觸發。當無法找到足夠大的連續空間分配給新創建的較大對象也會提前觸發一次MajorGC進行垃圾回收騰出空間。
在老年代中,MajorGC采用了標記—清除算法:首先掃描一次所有老年代里的對象,標記出存活的對象,然后回收沒有標記的對象。MajorGC的耗時比較長。因為要掃描再回收。MajorGC會產生內存碎片,當老年代也沒有內存分配給新來的對象的時候,就會拋出OOM(Out of Memory)異常。
三、永久代
永久代指的是永久保存區域。主要存放Class和Meta(元數據)的信息。Classic在被加載的時候被放入永久區域,它和存放的實例的區域不同,在Java8中,詞鋒代已經被移除,取而代之的是一個稱之為“元數據區”(元空間)的區域。元空間和永久代類似,都是對JVM中規范中方法的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。因此,默認情況下,元空間的大小僅受本地內存的限制。類的元數據放入native memory,字符串池和類的靜態變量放入java堆中。這樣可以加載多少類的元數據就不再由MaxPermSize控制,而由系統的實際可用空間來控制。
采用元空間而不用永久代的原因:
- 為了解決永久代的OOM問題,元數據和class對象存放在永久代中,容易出現性能問題和內存溢出。
- 類及方法的信息等比較難確定其大小,因此對於永久代大小指定比較困難,大小容易出現永久代溢出,太大容易導致老年代溢出(堆內存不變,此消彼長)。
- 永久代會為GC帶來不必要的復雜度,並且回收效率偏低。