jvm堆分代
JVM堆結(jié)構(gòu)圖及分代JVM虛擬機(jī): JVM內(nèi)存分代策略Java虛擬機(jī)根據(jù)對象存活的周期不同,把堆內(nèi)存劃分為幾塊,一般分為新生代、老年代和永久帶,永久帶也叫持久帶(對HotSpot虛擬機(jī)而言),這就是JVM的內(nèi)存分代的策略.現(xiàn)在我們看到的java堆,實際劃分的顆粒度會更細(xì),實際上把堆按分代的結(jié)構(gòu)來處理呢,那就引申出一個問題了,為什么要分代,對內(nèi)存是虛擬機(jī)管理的內(nèi)存中的最大的一塊,也是垃圾回收最頻繁的一塊區(qū)域,我們程序所有的對象實例都存放在堆內(nèi)存中. 給堆內(nèi)存分代是為了提高對象內(nèi)存分配和來及回收的效率,你想如果堆是一塊內(nèi)存,然后在這個堆內(nèi)存中存所有的對象,而且有些都不一樣,有些是朝生夕死的,有些是永久存活的,有些是中間環(huán)節(jié)的,你把這些對象全都塞到一個內(nèi)存里,對于垃圾回收來講,是不是難度大大提升,第二個問題,由于內(nèi)存存放對象的時候是連續(xù)的,以后未來你要挑不同的對象做回收,對內(nèi)存空間做釋放,也就是你內(nèi)存當(dāng)中會產(chǎn)生大量的碎片,產(chǎn)生碎片又會帶來什么影響呢,產(chǎn)生碎片最直接的影響就會內(nèi)存造成浪費,比如原來這一塊是200K,200K,200K三塊,存了三個對象,然后中間的對象唄回收了,被回收這個對象被釋放了200K,現(xiàn)在我要存一個對象300K存不進(jìn)去,這個時候只能往后面找有沒有連續(xù)的兩個200K,那這個200K就浪費掉了,除非小于200K的對象能new上,否則空間就被閑置了所以這就是產(chǎn)生碎片的原因,如果不分代對于垃圾回收來講就很復(fù)雜.分而治之,分代最核心的一個策略,試想一下,如果對內(nèi)存沒有區(qū)域劃分,所有新創(chuàng)建的對象和聲明周期很長的對象放在一起,隨著程序的執(zhí)行,對內(nèi)存需要頻繁的進(jìn)行垃圾收集,而每次回收都要遍歷所有的對象,遍歷這些對象所花費的時間代價是巨大的,會嚴(yán)重影響GC的效率,也是就是難度的一部分,這簡直太可怕了.有了內(nèi)存分代,情況就不同了,新創(chuàng)建的對象會在新生代中生成,經(jīng)過多次回收仍然存活下來的對象放在老年代中靜態(tài)屬性、類信息等放在永久代中,新生代存活的時間短,只需要在新生代頻繁進(jìn)行GC,老年代中對象生命周期長,內(nèi)存回收頻率相對較低,永久代由于是最長的了,JMV一般不會對永久代做垃圾回收,雖然說它可以,但是它一般不會這么去做.還可以根據(jù)不同年代的特點采用合適的垃圾回收算法,分代收集大大提高了收集效率,這些都是內(nèi)存分代所帶來的好處,它把原來一大塊的東西,分成了小塊,然后把不同的對象放在不同的塊中,經(jīng)過重重過濾,新生代里面有一個eden區(qū),這里放的永遠(yuǎn)都是剛出生的對象,如果eden區(qū)的對象并沒有被回收,或者在一定的時間內(nèi)沒有被回收,這個對象的聲明周期很長,那么就不能放在eden區(qū)了,要放到老年代里,然后再是持久代,新生代和持久代仿佛就是一個漏斗在層層過濾,能夠跑到過濾最終點的,一定是這個對象聲明周期最長的,才會到持久代,所以他采用這樣一個方式來解決問題,這是一個很好的方式.內(nèi)存分代劃分:Java虛擬機(jī)將堆內(nèi)存劃分為新生代,老年代,持久代,永久帶是HotSpot虛擬機(jī)所特有的概念,如果你采用的是JDK1.4之前的,不是這個核心的,那就沒有永久代這么一說,它采用永久代的方式來實現(xiàn)方法區(qū),其它的虛擬機(jī)實現(xiàn)沒有這一概念,而且HotSpot也有取消永久代的趨勢,在JDK1.7 HotSpot已經(jīng)開始去永久代,把原本放在永久代的字符串常量池移出,永久代主要存放常量,類信息,靜態(tài)變量數(shù)據(jù),你有沒有發(fā)現(xiàn)堆里面的永久代存放的東西和方法區(qū)存放的東西相似,都是生命周期比較長的,可以把這些內(nèi)容放到方法區(qū)里面去存儲,可以節(jié)省一塊內(nèi)存空間
這個圖是顆粒度非常細(xì)的java堆的演示,僅是堆的進(jìn)一步細(xì)化的演示,其實我們的堆可以分成5塊區(qū)域,JVM會把它分成5塊區(qū)域, Eden,from,to這三塊被稱之為年輕代,old老年代,permanent被稱為持久代也有把old和permanent這兩塊放在一起的,在年輕代里又分成三塊,from,to這兩塊的叫法有好多,統(tǒng)稱為Survivor幸存者,對于from有的人叫做survivor0,有的叫s0,有的叫s1,為什么要用0,因為程序查數(shù)是從0開始的,也有叫from,to的,這三塊共同構(gòu)成了年輕代,前面的eden伊甸園,為什么要用這個來命名呢,說明所有對象的出生都是在這個區(qū)域里的,他是嬰兒的搖籃,大部分對象的初始化都是在這里,有的對象運行完了就沒事了,在這個橫向劃分當(dāng)中,Eden的垃圾回收頻率是最高的,而且在Eden區(qū)里基本能夠回收到整個對象的百分之80以上,甚至百分之85的對象,都會在Eden里運行完就回收掉了,唯獨剩余的一部分怎么辦呢,他也不會放在Eden里,它會放到Survivor Space,再進(jìn)一步?jīng)Q定是不是要回收,如果他又沒有被回收掉,然后再把它放在old區(qū).我們在講來垃圾回收器的時候,由于不同的區(qū)域回收的頻率不一樣,那么所采用的收集器也是不一樣的,收集器不僅僅是對堆這一塊做回收,它會細(xì)化到什么程度呢,對堆里不同的區(qū)域做回收,那么也就是說在這個區(qū)域里邊,從Survivor之后一分為二,左邊的一個垃圾回收器,右邊的一個垃圾回收器,即便是你不指定,JVM也是這么做的,他不是一個垃圾回收器,而是對不同的堆用不同的垃圾回收器,根據(jù)你的區(qū)域,根據(jù)你的分代,這個叫分代垃圾收集,是不是Eden有一個垃圾回收,是不是from有一個垃圾回收,不是這樣的,它的垃圾回收顆粒度沒有這么細(xì),因為它覺得沒有必要,他就是從中間帶和老年代作為一個中間軸,來采用兩代的垃圾回收新生代(Young Generation):新生成的對象優(yōu)先存放在新生代中,新生代對象朝生夕死,存活率很低,在新生代中,常規(guī)應(yīng)用一次垃圾收集,一般可以回收70% ~ 90% 的空間,回收率很高.HotSpot將新生代分為三代,一塊較大的Eden空間和兩塊較小的Survivor空間,默認(rèn)比例是8:1:1,8是80%是Eden,剩下的10%,10%是Survivor區(qū),為什么要給Eden區(qū)放這么大的空間,而給Survivor放這么小呢, 因為你想啊,因為大部分的空間都被回收掉了,剩下的就一小部分了,分那么大的空間會造成很大的浪費,沒必要,所以java虛擬機(jī)所劃分的區(qū)域是8:1:1, HotSpot采用復(fù)制算法來回收新生代,什么叫復(fù)制算法,設(shè)置這個比例是為了充分利用內(nèi)存空間,減少浪費,新生成的對象在Eden區(qū)分配,大對象除外,大對象直接進(jìn)入老年代,Eden區(qū)會放大部分的對象,并不是全部的,另一部分就是大對象,當(dāng)Eden區(qū)沒有足夠空間進(jìn)行分配時,虛擬機(jī)將會發(fā)起一次Minor GC, 它的收集器分為兩種, 一種是次收集器,一種是全收集器,其實又叫Minor GC的,Minor GC就是去收集Eden里的一些收集器,就是指收集年輕代的收集器, GC開始時,對象只會存在于Eden區(qū)和From Survivor區(qū),to Survivor 區(qū), GC進(jìn)行時, Eden區(qū)中所有存活的對象都會被復(fù)制到 to Survivor區(qū), 而在from Survivor區(qū)中, 仍存活的對象會根據(jù)他們的年齡值決定去向, 年齡值達(dá)到年齡閥值(默認(rèn)是15次),就是被回收15次還沒有被回收掉,新生代中的對象每熬過一輪垃圾回收,當(dāng)年齡增加1的時候,如果年齡值達(dá)到15了,放心不會被回收了,而是放在老年代當(dāng)中了, 沒有達(dá)到閥值的對象會被復(fù)制到to Survivor區(qū). 接著清空Eden區(qū)和From Survivor區(qū),新生代中存活的對象都在to Survivor區(qū). 接著from Survivor和to Survivor區(qū)會交換它們的角色,其實這就是我們要講的復(fù)制算法的事件. 其實很好理解,如果年齡沒有超過15次,還在這里放著,內(nèi)存空間肯定是連續(xù)的,通過Minor GC次回收器的回收,超過15次的肯定不會被回收,那怎么辦呢,他不能就把它放在這里呢,放在這里第一個還是會產(chǎn)生碎片,復(fù)制算法非常好理解,他會把這些沒有被回收的對象直接拷貝到to Survivor區(qū),并且按照連續(xù)的來存儲,然后把from區(qū)全都清空,這個時候既保證了空間的釋放,也保證了空間的連續(xù),這就是復(fù)制算法,to Survivor里的也不是永久存放,也是通過一個閥值,如果也是比如15次垃圾回收后存活,那就放在old區(qū)老年代,然后再把to Survivor區(qū)清空,老年代old區(qū)是為了給survivor區(qū)做一個擔(dān)保,就像我們?nèi)ャy行貸款一樣,old區(qū)老年代其實就是給新生代做一個擔(dān)保,總之,不管怎樣都會保證to Survivor區(qū)在一輪GC后都是空的, GC時當(dāng)to Survivor區(qū)沒有足夠的空間存放上一次新生代收集下來的存活對象時,需要老年代進(jìn)行分配擔(dān)保,將這些對象存放在老年代中.新生代就是用來折騰這戲?qū)ο?誰能進(jìn)入到老年代里.老年代(old generation):在新生代中經(jīng)歷了多次,具體要看虛擬機(jī)配置的閥值,GC后仍然存活下來的對象會進(jìn)入老年代中. 老年代中的對象生命周期較長,存活率較高,在老年代中進(jìn)行GC的頻率相對而言較低,而且回收的速度也比較慢.老年代的垃圾回收機(jī)制,肯定不會像新生代那么頻繁,因為老年代生命周期較長,還頻繁收集,第一是無用,第二降低虛擬機(jī)的性能,沒有必要做高頻的垃圾回收.永久代(Permanent Generation):永久代存儲類信息,常量,靜態(tài)變量,即時編譯器編譯后的代碼等數(shù)據(jù),對這一區(qū)域而言,Java虛擬機(jī)規(guī)范指出可以不進(jìn)行垃圾收集,一般而言不會進(jìn)行垃圾回收.JDK1.8和1.9里已經(jīng)沒有永久代了,最多就是到老年代了.
?
總結(jié)