JVM中的堆,一般分為三大部分:新生代、老年代、永久代:

image
一:新生代:主要是用來存放新生的對象。一般占據(jù)堆的1/3空間。由于頻繁創(chuàng)建對象,所以新生代會頻繁觸發(fā)MinorGC進行垃圾回收。
新生代又分為 Eden區(qū)、ServivorFrom、ServivorTo三個區(qū)。
Eden區(qū):Java新對象的出生地(如果新創(chuàng)建的對象占用內(nèi)存很大,則直接分配到老年代)。當Eden區(qū)內(nèi)存不夠的時候就會觸發(fā)MinorGC,對新生代區(qū)進行一次垃圾回收。
ServivorTo:保留了一次MinorGC過程中的幸存者。
ServivorFrom:上一次GC的幸存者,作為這一次GC的被掃描者。
MinorGC的過程:MinorGC采用復制算法。首先,把Eden和ServivorFrom區(qū)域中存活的對象復制到ServicorTo區(qū)域(如果有對象的年齡以及達到了老年的標準,則賦值到老年代區(qū)),同時把這些對象的年齡+1(如果ServicorTo不夠位置了就放到老年區(qū));然后,清空Eden和ServicorFrom中的對象;最后,ServicorTo和ServicorFrom互換,原ServicorTo成為下一次GC時的ServicorFrom區(qū)。

image
二:老年代:主要存放應用程序中生命周期長的內(nèi)存對象。
老年代的對象比較穩(wěn)定,所以MajorGC不會頻繁執(zhí)行。在進行MajorGC前一般都先進行了一次MinorGC,使得有新生代的對象晉身入老年代,導致空間不夠用時才觸發(fā)。當無法找到足夠大的連續(xù)空間分配給新創(chuàng)建的較大對象時也會提前觸發(fā)一次MajorGC進行垃圾回收騰出空間。
MajorGC采用標記—清除算法:首先掃描一次所有老年代,標記出存活的對象,然后回收沒有標記的對象。MajorGC的耗時比較長,因為要掃描再回收。MajorGC會產(chǎn)生內(nèi)存碎片,為了減少內(nèi)存損耗,我們一般需要進行合并或者標記出來方便下次直接分配。
當老年代也滿了裝不下的時候,就會拋出OOM(Out of Memory)異常。
三:永久代
指內(nèi)存的永久保存區(qū)域,主要存放Class和Meta(元數(shù)據(jù))的信息,Class在被加載的時候被放入永久區(qū)域. 它和和存放實例的區(qū)域不同,GC不會在主程序運行期對永久區(qū)域進行清理。所以這也導致了永久代的區(qū)域會隨著加載的Class的增多而脹滿,最終拋出OOM異常。
在Java8中,永久代已經(jīng)被移除,被一個稱為“元數(shù)據(jù)區(qū)”(元空間)的區(qū)域所取代。
元空間的本質(zhì)和永久代類似,都是對JVM規(guī)范中方法區(qū)的實現(xiàn)。不過元空間與永久代之間最大的區(qū)別在于:元空間并不在虛擬機中,而是使用本地內(nèi)存。因此,默認情況下,元空間的大小僅受本地內(nèi)存限制。類的元數(shù)據(jù)放入 native memory, 字符串池和類的靜態(tài)變量放入java堆中. 這樣可以加載多少類的元數(shù)據(jù)就不再由MaxPermSize控制, 而由系統(tǒng)的實際可用空間來控制.
采用元空間而不用永久代的幾點原因:(參考:http://www.cnblogs.com/paddix/p/5309550.html)
1、為了解決永久代的OOM問題,元數(shù)據(jù)和class對象存在永久代中,容易出現(xiàn)性能問題和內(nèi)存溢出。
2、類及方法的信息等比較難確定其大小,因此對于永久代的大小指定比較困難,太小容易出現(xiàn)永久代溢出,太大則容易導致老年代溢出(因為堆空間有限,此消彼長)。
3、永久代會為 GC 帶來不必要的復雜度,并且回收效率偏低。
4、Oracle 可能會將HotSpot 與 JRockit 合二為一。