一、基本垃圾回收算法
①按照基本回收策略分
(1)引用計(jì)數(shù)(Reference Counting)
比較古老的回收算法。原理是此對(duì)象有一個(gè)引用,即增加一個(gè)計(jì)數(shù),刪除一個(gè)引用則減少一 個(gè)計(jì)數(shù)。垃圾回收時(shí),只用收集計(jì)數(shù)為 0 的對(duì)象。此算法最致命的是無(wú)法處理循環(huán)引用的 問(wèn)題。
(2)可達(dá)性分析清理

標(biāo)記-清除(Mark-Sweep):此算法執(zhí)行分兩階段。第一階段從引用根節(jié)點(diǎn)開始標(biāo)記所有被引用的對(duì)象,第二階段遍歷整個(gè)堆,把未標(biāo)記的對(duì)象清除。此算法需要暫停整個(gè)應(yīng)用,同時(shí), 會(huì)產(chǎn)生內(nèi)存碎片

復(fù)制(Copying):此算法把內(nèi)存空間劃為兩個(gè)相等的區(qū)域,每次只使用其中一個(gè)區(qū)域。垃圾回收時(shí),遍歷當(dāng)前使用區(qū)域,把正在使用中的對(duì)象復(fù)制到另外一個(gè)區(qū)域中。次算法每次只處理正在使用中的對(duì)象,因此復(fù)制成本比較小,同時(shí)復(fù)制過(guò)去以后還能進(jìn)行相應(yīng)的內(nèi)存整理, 不會(huì)出現(xiàn)“碎片”問(wèn)題。當(dāng)然,此算法的缺點(diǎn)也是很明顯的,就是需要兩倍內(nèi)存空間。

標(biāo)記-整理(Mark-Compact):此算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個(gè)算法的優(yōu)點(diǎn)。也是分兩階段,第一階段從根節(jié)點(diǎn)開始標(biāo)記所有被引用對(duì)象,第二階段遍歷整個(gè)堆,清除標(biāo)記對(duì)象,并未標(biāo)記對(duì)象并且把存活對(duì)象“壓縮”到堆的其中一塊,按順序排放。此算法避免了“標(biāo) 記-清除”的碎片問(wèn)題,同時(shí)也避免了“復(fù)制”算法的空間問(wèn)題。
②按分區(qū)對(duì)待的方式分
(1)增量收集(Incremental Collecting):實(shí)時(shí)垃圾回收算法,即:在應(yīng)用進(jìn)行的同時(shí)進(jìn) 行垃圾回收。不知道什么原因 JDK5.0 中的收集器沒有使用這種算法的。
(2)分代收集(Generational Collecting):基于對(duì)對(duì)象生命周期分析后得出的垃圾回收算法。把對(duì)象分為年青代、年老代、持久代,對(duì)不同生命周期的對(duì)象使用不同的算法(上述方 式中的一個(gè))進(jìn)行回收?,F(xiàn)在的垃圾回收器(從 J2SE1.2 開始)都是使用此算法的。
③按系統(tǒng)線程分
(1)串行收集:串行收集使用單線程處理所有垃圾回收工作,因?yàn)闊o(wú)需多線程交互,實(shí)現(xiàn)容 易,而且效率比較高。但是,其局限性也比較明顯,即無(wú)法使用多處理器的優(yōu)勢(shì),所以此收 集適合單處理器機(jī)器。當(dāng)然,此收集器也可以用在小數(shù)據(jù)量(100M 左右)情況下的多處理 器機(jī)器上。
(2)并行收集:并行收集使用多線程處理垃圾回收工作,因而速度快,效率高。而且理論上 CPU 數(shù)目越多,越能體現(xiàn)出并行收集器的優(yōu)勢(shì)。
(3)并發(fā)收集:相對(duì)于串行收集和并行收集而言,前面兩個(gè)在進(jìn)行垃圾回收工作時(shí),需要暫 停整個(gè)運(yùn)行環(huán)境,而只有垃圾回收程序在運(yùn)行,因此,系統(tǒng)在垃圾回收時(shí)會(huì)有明顯的暫停, 而且暫停時(shí)間會(huì)因?yàn)槎言酱蠖介L(zhǎng)。
優(yōu)化技術(shù):分代處理垃圾

試想,在不進(jìn)行對(duì)象存活時(shí)間區(qū)分的情況下,每次垃圾回收都是對(duì)整個(gè)堆空間進(jìn)行回收,花費(fèi)時(shí)間相對(duì)會(huì)長(zhǎng),同時(shí),因?yàn)槊看位厥斩夹枰闅v所有存活對(duì)象,但實(shí)際上,對(duì)于生命周期長(zhǎng)的對(duì)象而言,這種遍歷是沒有效果的,因?yàn)榭赡苓M(jìn)行了很多次遍歷,但是他們依舊存在。 因此,分代垃圾回收采用分治的思想,進(jìn)行代的劃分,把不同生命周期的對(duì)象放在不同代上, 不同代上采用最適合它的垃圾回收方式進(jìn)行回收。
虛擬機(jī)中的共劃分為三個(gè)代:年輕代(Young Generation)、年老點(diǎn)(Old Generation)和 持久代(Permanent Generation)。其中持久代主要存放的是 Java 類的類信息,與垃圾收集要收集的 Java 對(duì)象關(guān)系不大。年輕代和年老代的劃分是對(duì)垃圾收集影響比較大的。
年輕代:所有新生成的對(duì)象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對(duì)象。年輕代分三個(gè)區(qū)。一個(gè) Eden 區(qū),兩個(gè) Survivor 區(qū)(一般而言)。 大部分對(duì)象在 Eden 區(qū)中生成。當(dāng) Eden 區(qū)滿時(shí),還存活的對(duì)象將被復(fù)制到 Survivor 區(qū)(兩個(gè)中的一個(gè)),當(dāng)這個(gè) Survivor 區(qū)滿時(shí),此區(qū)的存活對(duì)象將被復(fù)制到另外一個(gè) Survivor 區(qū),當(dāng)這個(gè) Survivor 去也滿了的時(shí)候,從第一個(gè) Survivor 區(qū)復(fù)制過(guò)來(lái)的并且此時(shí)還存活的對(duì)象, 將被復(fù)制“年老區(qū)(Tenured)”。需要注意,Survivor 的兩個(gè)區(qū)是對(duì)稱的,沒先后關(guān)系,所以同一個(gè)區(qū)中可能同時(shí)存在從 Eden 復(fù)制過(guò)來(lái) 對(duì)象,和從前一個(gè) Survivor 復(fù)制過(guò)來(lái)的對(duì)象,而復(fù)制到年老區(qū)的只有從第一個(gè) Survivor 去過(guò)來(lái)的對(duì)象。而且,Survivor 區(qū)總有一個(gè)是空的。 同時(shí),根據(jù)程序需要,Survivor 區(qū)是可以配置為多個(gè)的(多于兩個(gè)),這樣可以增加對(duì)象在年輕代中的存在時(shí)間,減少被放到年老代的可能。
年老代:在年輕代中經(jīng)歷了 N 次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代中。因此, 可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。
持久代:用于存放靜態(tài)文件,如今 Java 類、方法等。持久代對(duì)垃圾回收沒有顯著影響,但是 有些應(yīng)用可能動(dòng)態(tài)生成或者調(diào)用一些 class,例如 Hibernate 等,在這種時(shí)候需要設(shè)置一個(gè) 比較大的持久代空間來(lái)存放這些運(yùn)行過(guò)程中新增的類。持久代大小通過(guò) -XX:MaxPermSize=<N> 進(jìn)行設(shè)置。
JAVA 中垃圾回收 GC 的類型
由于對(duì)象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時(shí)間也不一樣。GC 有兩種類型:Scavenge GC 和 Full GC。
Scavenge GC: 一般情況下,當(dāng)新對(duì)象生成,并且在 Eden 申請(qǐng)空間失敗時(shí),就會(huì)觸發(fā) Scavenge GC,對(duì) Eden 區(qū)域進(jìn)行 GC,清除非存活對(duì)象,并且把尚且存活的對(duì)象移動(dòng)到 Survivor 區(qū)。然后整理 Survivor 的兩個(gè)區(qū)。這種方式的 GC 是對(duì)年輕代的 Eden 區(qū)進(jìn)行,不 會(huì)影響到年老代。因?yàn)榇蟛糠謱?duì)象都是從 Eden 區(qū)開始的,同時(shí) Eden 區(qū)不會(huì)分配的很大, 所以 Eden 區(qū)的 GC 會(huì)頻繁進(jìn)行。因而,一般在這里需要使用速度快、效率高的算法,使 Eden 去能盡快空閑出來(lái)。
Full GC: 對(duì)整個(gè)堆進(jìn)行整理,包括 Young、Tenured 和 Perm。Full GC 因?yàn)樾枰獙?duì)整個(gè) 對(duì)進(jìn)行回收,所以比 Scavenge GC 要慢,因此應(yīng)該盡可能減少 Full GC 的次數(shù)。在對(duì) JVM 調(diào)優(yōu)的過(guò)程中,很大一部分工作就是對(duì)于 FullGC 的調(diào)節(jié)。
有如下原因可能導(dǎo)致 Full GC:· 年老代(Tenured)被寫滿、持久代(Perm)被寫滿、System.gc()被顯示調(diào)用 、上一次 GC 之后 Heap 的各域分配策略動(dòng)態(tài)變化。