Java之JVM垃圾回收 內(nèi)存結(jié)構(gòu)以及垃圾回收算法
作為Java語言的核心之一,JVM垃圾回收幫我們解決了讓我們很頭疼的垃圾回收問題。我們不需要像VC++一樣,作為內(nèi)存管理的統(tǒng)治者需要我們對我們分配的每一塊內(nèi)存進(jìn)行回收,否則就會造成內(nèi)存泄露問題。
是不是只要有JVM存在我們就不會出現(xiàn)內(nèi)存泄露問題,出現(xiàn)內(nèi)存泄露問題我們又該怎么辦,如果我們想提高我們程序的穩(wěn)定性和其他性能我們能從什么地方下手?。?!相信這些問題是我們程序過程中不可逾越的。了解JVM的內(nèi)存分配及其相應(yīng)的垃圾回收機制,不僅僅是可以了解底層的JVM運行機制,而且對于程序性能的優(yōu)化和提升還是很有必要的。
一.JVM堆內(nèi)存介紹
工欲善其事,必先利其器。所以了解堆內(nèi)存的內(nèi)部結(jié)構(gòu)是很必要的。
在JVM中堆空間劃分為三個代:年輕代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation)。年輕帶主要是動態(tài)的存儲,年輕帶主要儲存新產(chǎn)生的對象,年老代儲存年齡大些的對象,永久帶主要是存儲的是java的類信息,包括解析得到的方法、屬性、字段等。永久帶基本不參與垃圾回收。所以說我們說的垃圾回收主要是針對年輕代和年老代。
年輕代又分成3個部分,一個eden區(qū)和兩個相同的survior區(qū)。剛開始創(chuàng)建的對象都是放置在eden區(qū)的。分成這樣3個部分,主要是為了生命周期短的對象盡量留在年輕帶。當(dāng)eden區(qū)申請不到空間的時候,進(jìn)行minorGC,把存活的對象拷貝到survior。年老代主要存放生命周期比較長的對象,比如緩存對象。(經(jīng)過IBM的一個研究機構(gòu)研究數(shù)據(jù)表明,基本上80%-98%的對象都會在年輕代的Eden區(qū)死掉從而本回收掉,所以說真正進(jìn)入到老年代的對象很少,這也是為什么MinorGC比MajorGC更加頻繁的原因)
具體JVM內(nèi)存垃圾回收過程描述如下 :
1、對象在Eden區(qū)完成內(nèi)存分配
2、當(dāng)Eden區(qū)滿了,再創(chuàng)建對象,會因為申請不到空間,觸發(fā)minorGC,進(jìn)行young(eden+1survivor)區(qū)的垃圾回收
3、minorGC時,Eden不能被回收的對象被放入到空的survivor(Eden肯定會被清空),另一個survivor里不能被GC回收的對象也會被放入這個survivor,始終保證一個survivor是空的
4、當(dāng)做第3步的時候,如果發(fā)現(xiàn)survivor滿了,則這些對象被copy到old區(qū),或者survivor并沒有滿,但是有些對象已經(jīng)足夠Old,也被放入Old區(qū) XX:MaxTenuringThreshold
5、當(dāng)Old區(qū)被放滿的之后,進(jìn)行fullGC
補充:
MinorGC:年輕代所進(jìn)行的垃圾回收,非常頻繁,一般回收速度也比較快。
MajorGC:老年代進(jìn)行的垃圾回收,發(fā)生一次MajorGC至少伴隨一次MinorGC,一般比MinorGC速度慢十倍以上。
FullGC:整個堆內(nèi)存進(jìn)行的垃圾回收,很多時候是MajorGC
以后就是堆內(nèi)存結(jié)構(gòu)已經(jīng)大致的垃圾回收過程。
二、對象分配原則
1.對象優(yōu)先分配在Eden區(qū),如果Eden區(qū)沒有足夠的空間時,虛擬機執(zhí)行一次Minor GC。
2.大對象直接進(jìn)入老年代(大對象是指需要大量連續(xù)內(nèi)存空間的對象)。這樣做的目的是避免在Eden區(qū)和兩個Survivor區(qū)之間發(fā)生大量的內(nèi)存拷貝(新生代采用復(fù)制算法收集內(nèi)存)。
3.長期存活的對象進(jìn)入老年代。虛擬機為每個對象定義了一個年齡計數(shù)器,如果對象經(jīng)過了1次Minor GC那么對象會進(jìn)入Survivor區(qū),之后每經(jīng)過一次Minor GC那么對象的年齡加1,知道達(dá)到閥值對象進(jìn)入老年區(qū)。
4.動態(tài)判斷對象的年齡。如果Survivor區(qū)中相同年齡的所有對象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對象可以直接進(jìn)入老年代。
5.空間分配擔(dān)保。每次進(jìn)行Minor GC時,JVM會計算Survivor區(qū)移至老年區(qū)的對象的平均大小,如果這個值大于老年區(qū)的剩余值大小則進(jìn)行一次Full GC,如果小于檢查HandlePromotionFailure設(shè)置,如果true則只進(jìn)行Monitor GC,如果false則進(jìn)行Full GC。
三、垃圾收集器
作為JVM中的核心之一垃圾收集器,主要完成的功能包括:(1)發(fā)現(xiàn)無用信息對象;(2)回收被無用對象占用的內(nèi)存空間,使該空間可被程序再次使用。所以說我們在實現(xiàn)垃圾收集器的同時就要實現(xiàn)兩個算法一個是發(fā)現(xiàn)無用的對象第二就是回收該對象的內(nèi)存。
收集器主要分為引用計數(shù)器和跟蹤收集器兩種,Sun JDK中采用跟蹤收集器作為GC實現(xiàn)策略。發(fā)現(xiàn)無用對象只要的實現(xiàn)算法包括引用計數(shù)法和根搜索算法,引用計數(shù)法主要是JVM的早期實現(xiàn)方法,因為引用計數(shù)無法解決循環(huán)引用的問題,所以現(xiàn)在JVM實現(xiàn)的主要是根搜索算法,
引用計數(shù)法:堆中的每個對象對應(yīng)一個引用計數(shù)器。當(dāng)每一次創(chuàng)建一個對象并賦給一個變量時,引用計數(shù)器置為1。當(dāng)對象被賦給任意變量時,引用計數(shù)器每次加1當(dāng)對象出了作用域后(該對象丟棄不再使用),引用計數(shù)器減1,一旦引用計數(shù)器為0,對象就不可用從而可以被回收。 根搜索算法:通過一系列的名為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當(dāng)一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說就是從GC Roots到這個對象不可達(dá))時,則證明此對象是不可用的。
目前的收集器主要有三種:
串行收集器:使用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高
并行收集器:對年輕代進(jìn)行并行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器機器上使用
并發(fā)收集器:可以保證大部分工作都并發(fā)進(jìn)行(應(yīng)用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應(yīng)時間要求比較高的中、大規(guī)模應(yīng)用
四、垃圾收集器的回收算法
Copying算法:
算法:復(fù)制采用的方式為從根集合掃描出存活的對象,并將找到的存活對象復(fù)制到一塊新的完全未使用的空間中。
過程: 此算法把內(nèi)存空間劃為兩個相等的區(qū)域,每次只使用其中一個區(qū)域。垃圾回收時,遍歷當(dāng)前使用區(qū)域,把正在使用中的對象復(fù)制到另外一個區(qū)域中。次算法每次只處理正在使用中的對象,因此復(fù)制成本比較小,同時復(fù)制過去以后還能進(jìn)行相應(yīng)的內(nèi)存整理,不過出現(xiàn)“碎片”問題。當(dāng)然,此算法的缺點也是很明顯的,就是需要兩倍內(nèi)存空間。

Mark-Sweep算法:
算法:標(biāo)記-清除采用的方式為從根集合開始掃描,對存活的對象進(jìn)行標(biāo)記,標(biāo)記完畢后,再掃描整個空間中未標(biāo)記的對象,并進(jìn)行回收。
過程: 第一階段從引用根節(jié)點開始標(biāo)記所有被引用的對象,第二階段遍歷整個堆,把未標(biāo)記的對象清除。它停止所有工作,收集器從根開始訪問每一個活躍的節(jié)點,標(biāo)記它所訪問的每一個節(jié)點。走過所有引用后,收集就完成了,然后就對堆進(jìn)行清除(即對堆中的每一個對象進(jìn)行檢查),所有沒有標(biāo)記的對象都作為垃圾回收并返回空閑列表。

Mark-Compact算法:
算法:標(biāo)記階段與“Mark-Sweep”算法相同,但在清除階段有所不同。在回收不存活對象所占用的內(nèi)存空間后,會將其他所有存活對象都往左端空閑的空間進(jìn)行移動,并更新引用其對象指針。
過程:此算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個算法的優(yōu)點。也是分兩階段,第一階段從根節(jié)點開始標(biāo)記所有被引用對象,第二階段遍歷整個堆,把清除未標(biāo)記對象并且把存活對象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標(biāo)記-清除”的碎片問題,同時也避免了“復(fù)制”算法的空間問題。

Sun JDK GC策略:

新生代算法實現(xiàn):Copying,Copying,Copying
舊生代算發(fā)實現(xiàn):Mark-Sweep-Compact,Mark –Compact,Mark –Sweep!!