版權聲明:本文為博主原創文章,轉載請注明出處,歡迎交流學習!
Java虛擬機根據對象存活的周期不同,把堆內存划分為幾塊,一般分為新生代、老年代和永久代(對HotSpot虛擬機而言),這就是JVM的內存分代策略。
為什么要分代?
堆內存是虛擬機管理的內存中最大的一塊,也是垃圾回收最頻繁的一塊區域,我們程序所有的對象實例都存放在堆內存中。給堆內存分代是為了提高對象內存分配和垃圾回收的效率。試想一下,如果堆內存沒有區域划分,所有的新創建的對象和生命周期很長的對象放在一起,隨着程序的執行,堆內存需要頻繁進行垃圾收集,而每次回收都要遍歷所有的對象,遍歷這些對象所花費的時間代價是巨大的,會嚴重影響我們的GC效率,這簡直太可怕了。
有了內存分代,情況就不同了,新創建的對象會在新生代中分配內存,經過多次回收仍然存活下來的對象存放在老年代中,靜態屬性、類信息等存放在永久代中,新生代中的對象存活時間短,只需要在新生代區域中頻繁進行GC,老年代中對象生命周期長,內存回收的頻率相對較低,不需要頻繁進行回收,永久代中回收效果太差,一般不進行垃圾回收,還可以根據不同年代的特點采用合適的垃圾收集算法。分代收集大大提升了收集效率,這些都是內存分代帶來的好處。
內存分代划分
Java虛擬機將堆內存划分為新生代、老年代和永久代,永久代是HotSpot虛擬機特有的概念,它采用永久代的方式來實現方法區,其他的虛擬機實現沒有這一概念,而且HotSpot也有取消永久代的趨勢,在JDK 1.7中HotSpot已經開始了“去永久化”,把原本放在永久代的字符串常量池移出。永久代主要存放常量、類信息、靜態變量等數據,與垃圾回收關系不大,新生代和老年代是垃圾回收的主要區域。內存分代示意圖如下:
新生代(Young)
新生成的對象優先存放在新生代中,新生代對象朝生夕死,存活率很低,在新生代中,常規應用進行一次垃圾收集一般可以回收70% ~ 95% 的空間,回收效率很高。
HotSpot將新生代划分為三塊,一塊較大的Eden空間和兩塊較小的Survivor空間,默認比例為8:1:1。划分的目的是因為HotSpot采用復制算法來回收新生代,設置這個比例是為了充分利用內存空間,減少浪費。新生成的對象在Eden區分配(大對象除外,大對象直接進入老年代),當Eden區沒有足夠的空間進行分配時,虛擬機將發起一次Minor GC。
GC開始時,對象只會存在於Eden區和From Survivor區,To Survivor區是空的(作為保留區域)。GC進行時,Eden區中所有存活的對象都會被復制到To Survivor區,而在From Survivor區中,仍存活的對象會根據它們的年齡值決定去向,年齡值達到年齡閥值(默認為15,新生代中的對象每熬過一輪垃圾回收,年齡值就加1,GC分代年齡存儲在對象的header中)的對象會被移到老年代中,沒有達到閥值的對象會被復制到To Survivor區。接着清空Eden區和From Survivor區,新生代中存活的對象都在To Survivor區。接着, From Survivor區和To Survivor區會交換它們的角色,也就是新的To Survivor區就是上次GC清空的From Survivor區,新的From Survivor區就是上次GC的To Survivor區,總之,不管怎樣都會保證To Survivor區在一輪GC后是空的。GC時當To Survivor區沒有足夠的空間存放上一次新生代收集下來的存活對象時,需要依賴老年代進行分配擔保,將這些對象存放在老年代中。
老年代(Old)
在新生代中經歷了多次(具體看虛擬機配置的閥值)GC后仍然存活下來的對象會進入老年代中。老年代中的對象生命周期較長,存活率比較高,在老年代中進行GC的頻率相對而言較低,而且回收的速度也比較慢。
永久代(Permanent)
永久代存儲類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據,對這一區域而言,Java虛擬機規范指出可以不進行垃圾收集,一般而言不會進行垃圾回收。
Minor GC 和 Full GC的區別
新生代GC(Minor GC):Minor GC指發生在新生代的GC,因為新生代的Java對象大多都是朝生夕死,所以Minor GC非常頻繁,一般回收速度也比較快。當Eden空間不足以為對象分配內存時,會觸發Minor GC。
老年代GC(Full GC/Major GC):Full GC指發生在老年代的GC,出現了Full GC一般會伴隨着至少一次的Minor GC(老年代的對象大部分是Minor GC過程中從新生代進入老年代),比如:分配擔保失敗。Full GC的速度一般會比Minor GC慢10倍以上。當老年代內存不足或者顯式調用System.gc()方法時,會觸發Full GC。