在面向?qū)ο笳Z(yǔ)言程序中,我們的程序在運(yùn)行中會(huì)創(chuàng)建很多對(duì)象,程序會(huì)為對(duì)象在內(nèi)存中開(kāi)辟一段空間并分配好內(nèi)存地址,當(dāng)對(duì)象使用結(jié)束后,需要釋放占用的內(nèi)存空間,釋放對(duì)象內(nèi)存的機(jī)制就叫垃圾回收機(jī)制(Garbage Collection,GC)。在C語(yǔ)言中,動(dòng)態(tài)分配內(nèi)存用 malloc() 函數(shù),釋放內(nèi)存用 free() 函數(shù),而C++中,我們則使用運(yùn)算符 new 和 delete 來(lái)管理內(nèi)存,這樣雖然能靈活有效的申請(qǐng)和釋放內(nèi)存,但是對(duì)程序員來(lái)說(shuō),有時(shí)候使一種負(fù)擔(dān),而Java的垃圾回收機(jī)制則很好的解決了這一問(wèn)題。
一、垃圾回收機(jī)制:
JVM自動(dòng)不定時(shí)去堆內(nèi)存中清理不可達(dá)對(duì)象。不可達(dá)的對(duì)象并不會(huì)馬上就會(huì)直接回收,垃圾收集器在一個(gè)Java程序中的執(zhí)行是自動(dòng)的,不能強(qiáng)制執(zhí)行,即使程序員能明確地判斷出有一塊內(nèi)存已經(jīng)無(wú)用了,是應(yīng)該回收的,程序員也不能強(qiáng)制垃圾收集器回收該內(nèi)存塊。程序員唯一能做的就是通過(guò)調(diào)用System.gc 方法來(lái)"建議"執(zhí)行垃圾收集器,但其是否可以執(zhí)行,什么時(shí)候執(zhí)行卻都是不可知的。這也是垃圾收集器的最主要的缺點(diǎn)。當(dāng)然相對(duì)于它給程序員帶來(lái)的巨大方便性而言,這個(gè)缺點(diǎn)是瑕不掩瑜的。
1. 不可達(dá)對(duì)象:?對(duì)象沒(méi)有被引用,獲知對(duì)象沒(méi)有存活。
2. finalize方法:
Java在垃圾收集器將對(duì)象從內(nèi)存中清除出去前,使用finalize()方法做必要的清理工作。該方法在Object類(lèi)中定義,因此所有的類(lèi)都繼承了它。子類(lèi)覆蓋finalize()方法以整理系統(tǒng)資源或者執(zhí)行其他清理工作。finalize方法是在垃圾收集器刪除對(duì)象之前對(duì)這個(gè)對(duì)象調(diào)用的。
3. 新生代和老年代:
堆內(nèi)存被劃分成兩個(gè)不同的區(qū)域:新生代 ( Young )、老年代 ( Old )。
新生代 ( Young ) 又被劃分為三個(gè)區(qū)域:Eden、From Survivor(S0)、To Survivor(S1)。S0區(qū)和S1區(qū)內(nèi)存大小相等。

新生代:剛new出不久的對(duì)象存放在新生代中,存放不經(jīng)常被使用的對(duì)象。
老年代:存放比較活躍的對(duì)象,存放經(jīng)常被引用的對(duì)象。
垃圾回收機(jī)制在新生代回收的次數(shù)比較頻繁,而在老年代回收的次數(shù)相對(duì)較少。而且一般情況下,老年代的內(nèi)存空間大于新生代的內(nèi)存空間。
二、判斷對(duì)象存活的方法:
1.引用計(jì)數(shù)法:
給對(duì)象中添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用它時(shí),計(jì)數(shù)器值加1;當(dāng)引用失效時(shí),計(jì)數(shù)器值減1。任何時(shí)刻計(jì)數(shù)器值為0的對(duì)象就是不可能再被使用的。當(dāng)對(duì)象計(jì)數(shù)器的值大于15時(shí),會(huì)被存放到堆內(nèi)存的老年代中,當(dāng)對(duì)象的值大于0小于15時(shí)則存放在新生代中。因其很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題,所以該算法逐漸被淘汰,而別很少出現(xiàn)在主流的Java虛擬機(jī)中。
2.根搜索算法:
根搜索算法的基本思路就是通過(guò)一系列名為”GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱(chēng)為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的。
在Java語(yǔ)言中,可以作為GCRoots的對(duì)象包括下面幾種:
(1). 棧(棧幀中的局部變量區(qū),也叫做局部變量表)中引用的對(duì)象。
(2). 方法區(qū)中的類(lèi)靜態(tài)屬性引用的對(duì)象。
(3). 方法區(qū)中常量引用的對(duì)象。
(4). 本地方法棧中JNI(Native方法)引用的對(duì)象。
三、垃圾回收策略:
1、標(biāo)記清除算法:
標(biāo)記階段:遍歷堆,將不可達(dá)對(duì)象標(biāo)記為1,可達(dá)對(duì)象標(biāo)記為0。
清除階段:遍歷堆,逐個(gè)把標(biāo)記為1的不可達(dá)對(duì)象回收。
缺點(diǎn):由于堆內(nèi)存空間不連續(xù),所以逐個(gè)回收對(duì)象時(shí)會(huì)產(chǎn)生內(nèi)存空間碎片化,而且效率低。
應(yīng)用場(chǎng)景:用于對(duì)象存活周期較長(zhǎng)的老年代。
2、復(fù)制算法:
復(fù)制算法的基本思想是JVM一開(kāi)始就會(huì)將可用內(nèi)存分為大小相等的兩塊:from域(S0區(qū))和to域(S1區(qū))。每次只是使用from域,to域則空閑著。當(dāng)from域內(nèi)存不夠了,開(kāi)始執(zhí)行GC操作,這個(gè)時(shí)候,會(huì)把from域存活的對(duì)象拷貝到to域,然后直接把from域進(jìn)行內(nèi)存清理。
應(yīng)用場(chǎng)景:新生代。在新生代中,內(nèi)存會(huì)被分為Eden區(qū)、S0區(qū)和S1區(qū)。剛new出來(lái)的對(duì)象剛開(kāi)始會(huì)被存放在Eden區(qū),當(dāng)Eden區(qū)內(nèi)存滿(mǎn)后,觸發(fā)新生代的GC操作,把可達(dá)對(duì)象拷貝至S0區(qū),清除Eden區(qū)中的所有對(duì)象,當(dāng)Eden區(qū)再次觸發(fā)GC操作時(shí),會(huì)掃描Eden區(qū)和S0區(qū)域,對(duì)兩個(gè)區(qū)域進(jìn)行垃圾回收,經(jīng)過(guò)這次回收后還存活的對(duì)象,則直接復(fù)制到S1區(qū)域,并將Eden和From區(qū)域清空;當(dāng)后續(xù)Eden又發(fā)生GC回收時(shí),會(huì)對(duì)Eden和S1區(qū)域進(jìn)行垃圾回收,存活的對(duì)象復(fù)制到S0區(qū)域,并將Eden和S1區(qū)清空;如此交換15次(由JVM參數(shù)MaxTenuringThreshold決定,這個(gè)參數(shù)默認(rèn)是15),最終如果還是存活,就存入到老年代。
優(yōu)缺點(diǎn):性能高,解決了碎片化問(wèn)題,但是S0和S1區(qū)總有一塊內(nèi)存空白,造成內(nèi)存空間浪費(fèi)。
應(yīng)用場(chǎng)景:新生代。
3、標(biāo)記壓縮算法:
在標(biāo)記清除算法的基礎(chǔ)上,將對(duì)象進(jìn)行排序,使不可達(dá)對(duì)象盡可能地排序在一起,GC操作時(shí)就可整段刪除不可達(dá)對(duì)象,從而解決標(biāo)記清除算法中產(chǎn)生碎片化的問(wèn)題。
4、分代算法:
根據(jù)對(duì)象的存活周期的不同將內(nèi)存劃分成幾塊,新生代和老年代,這樣就可以根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴āP律捎脧?fù)制算法,老年代使用標(biāo)記壓縮算法。