玩轉(zhuǎn)JVM中的對(duì)象及引用④:對(duì)象的分配策略及優(yōu)化技術(shù)

一、基本垃圾回收算法

①按照基本回收策略分

(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 GCFull 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、TenuredPerm。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)變化。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容