二級(jí)java輔導(dǎo):有關(guān)于JVM的垃圾收集(一)

字號(hào):

Java 中使用 new、newarray、anewarray 和 multianewarray 指令來(lái)創(chuàng)建的對(duì)象,當(dāng)這些對(duì)象不再使用時(shí)由垃圾收集來(lái)釋放。那么 反序列化等都是間接使用了前面的某個(gè)指令, clone()  是個(gè)本地方法?
    JVM 規(guī)范不需要任何特定的垃圾收集技術(shù),甚至也沒(méi)要求有垃圾收集機(jī)制。大概只是說(shuō)不需要手工釋放內(nèi)存,具體怎么實(shí)現(xiàn)各 JVM 自行決定。
    GC 除了釋放不再被引用的對(duì)象,還要處理堆碎片,整理出連續(xù)的空閑空間才能放得下新的對(duì)象。不至于出現(xiàn)總的空閑空間足夠,但碎片太多而報(bào)出 "Out of Memory" 的異常。
    GC 有兩個(gè)好處:一個(gè)是提高了生產(chǎn)率,不用埋頭于 Memory Link 的有時(shí)甚至是逐行的檢查;二,GC 也是 Java 安全策略的一部分,有了它不至于因錯(cuò)誤的釋放內(nèi)存而導(dǎo)至 JVM 崩潰。但是 GC 的一個(gè)潛在缺陷影響了程序的性能,它需要一直在后臺(tái)不時(shí)的做些事情,而且實(shí)時(shí)性也有所欠缺。
    垃圾收集算法
    GC 算法要做兩件基本的事情:1. 檢測(cè)出垃圾對(duì)象;2. 回收垃圾對(duì)象,釋放相應(yīng)堆空間。垃圾檢測(cè)一般是先建立一個(gè)根對(duì)象集合,其他對(duì)象要是從根對(duì)象起可觸及就是活的,無(wú)法到達(dá)的就是垃圾。這里的根對(duì)象的認(rèn)定就有些講究的,不同的 JVM 的看法不完全一致,但總是會(huì)包含局部變量中的對(duì)象引用和棧幀的操作數(shù)棧(以及類變量中的引用)。
    另一個(gè)根對(duì)象的來(lái)源是被加載的類的常量池中的對(duì)象引用。類的常量池中的字符串包括有類名、超類名、超接口名、字段名、字段特征簽名、方法名、方法特征簽名
    還有一個(gè)來(lái)源是傳遞到方法中的、沒(méi)有被本地方法“釋放”的對(duì)象引用(根據(jù)本地方法接口,本地方法可以通過(guò)簡(jiǎn)單的返回來(lái)釋放引用,或者顯式的調(diào)用一個(gè)回調(diào)函數(shù)來(lái)釋放傳遞來(lái)的引用,或是這兩者的結(jié)合)。
    再一個(gè)潛在的根對(duì)象來(lái)源是,JVM 運(yùn)行時(shí)數(shù)據(jù)區(qū)中從垃圾收集器的堆中分配的部分。某些實(shí)現(xiàn)中,方法區(qū)中的類數(shù)據(jù)本身可能被存放在使用垃圾收集器的堆中,以便使用和釋放對(duì)象同樣的垃圾收集算法來(lái)檢測(cè)和卸載不再被引用的類。
    在某些 JVM 實(shí)現(xiàn)中,像基本類型,如一個(gè) int 如果被解釋為一個(gè)本地指針,那它就是指向堆中的一個(gè)對(duì)象,但是保守的垃圾收集器對(duì)這種基本類型引用的堆中的對(duì)象不處理。
    區(qū)別活動(dòng)對(duì)象和垃圾的兩個(gè)基本方法是引用計(jì)數(shù)和跟蹤。
    引用計(jì)數(shù)收集器
    引用計(jì)數(shù)是垃圾收集的早期策略,這種方法時(shí)堆中的每個(gè)對(duì)象都有一個(gè)引用計(jì)數(shù)。當(dāng)對(duì)象創(chuàng)建并賦給一個(gè)變量時(shí),引用計(jì)數(shù)為 1。每次賦給別的變量時(shí),引用計(jì)數(shù)加 1,當(dāng)對(duì)象的引用超過(guò)了生存期或指向到了新值(如果引用置為 null),對(duì)象的引用計(jì)減 1。這樣對(duì)象的引用計(jì)數(shù)為 0 時(shí)就是垃圾,可清除的。引用計(jì)數(shù)對(duì)多個(gè)對(duì)象的循環(huán)引用無(wú)能為力,其實(shí)這些對(duì)象都是死的,但引用計(jì)數(shù)都不為 0,還有引用數(shù)的增減帶來(lái)額外開銷,基于這些缺陷,這種技術(shù)現(xiàn)在已經(jīng)不為人所接受了。
    跟蹤收集器
    跟蹤收集追蹤從根節(jié)點(diǎn)開始的對(duì)象引用圖。基本的追蹤算法叫作“標(biāo)記并清除”,也就是垃圾收集的兩個(gè)階段。標(biāo)記階段,垃圾收集器遍歷引用樹,標(biāo)記每一個(gè)遇到的對(duì)象。清除階段,未被標(biāo)記的對(duì)象被釋放??赡茉趯?duì)象本身設(shè)置標(biāo)記,要么就是用一個(gè)獨(dú)立的位圖來(lái)設(shè)置標(biāo)記。
    壓縮收集器
    垃圾收集同時(shí)要應(yīng)對(duì)碎片整理的任務(wù)。標(biāo)記和清除通常使用兩種策略來(lái)消除堆碎片:壓縮和拷貝,這兩種方法都是快速移動(dòng)對(duì)象來(lái)減小碎片。
    壓縮收集我想應(yīng)該是在標(biāo)記清除之后來(lái)做的?壓縮收集器把活動(dòng)對(duì)象越過(guò)空閑區(qū)滑到堆的一堆,留下另一端的大的連續(xù)空閑塊。被移動(dòng)的對(duì)象的引用也被更新,指向新的位置。更新被移動(dòng)對(duì)象的引用有時(shí)通過(guò)一個(gè)間接對(duì)象引用層來(lái)實(shí)現(xiàn)的,對(duì)象的引用不實(shí)際指向堆中對(duì)象,而是指向一個(gè)對(duì)象句柄表(由它完成對(duì)象引用到堆中對(duì)象的實(shí)際位置的映射),對(duì)象被移動(dòng)了,只需要更新對(duì)象句柄表的句柄值,這樣程序中的對(duì)象引用不變。這種方法簡(jiǎn)化了消除堆碎片的工作,但是每次對(duì)象訪問(wèn)都要查一下映射表,帶來(lái)了性能上的損失。
    拷貝收集器
    拷貝收集器把所有的活動(dòng)對(duì)象移動(dòng)到一個(gè)新的區(qū)域。這種方法在追蹤對(duì)象過(guò)程中隨著發(fā)現(xiàn)而被拷貝,不再有標(biāo)記和清除的區(qū)分。一般的拷貝收集算法稱為“停止并拷貝”。在這個(gè)方案中,堆被分為兩個(gè)區(qū)域,任何時(shí)候只使用其中一個(gè)區(qū)域。對(duì)象在某一個(gè)區(qū)域中分配,直到這個(gè)區(qū)域被耗盡時(shí),程序執(zhí)行停止,遍歷這個(gè)區(qū)域,活動(dòng)對(duì)象移到另一個(gè)區(qū)域,完成后程序恢復(fù)執(zhí)行,對(duì)象在新的區(qū)域分配。原來(lái)的區(qū)域剩下垃圾,全清除。直到新的區(qū)域耗盡時(shí),程序停止,活動(dòng)對(duì)象又往回移,循環(huán)工作。這種方法的代價(jià)就是堆內(nèi)存只能使用到一半。
    下面是“停止和拷貝”算法的垃圾收集過(guò)程中以時(shí)間為線索的 9 個(gè)快照:  
    
    看過(guò)這個(gè)圖,應(yīng)該不用多加解釋,反正就是堆分成上下兩個(gè)區(qū)域,哪部分滿了,活動(dòng)對(duì)象往另一部分跑,被移出的區(qū)域剩下的對(duì)象全是垃圾,可以嘩一下全清空。來(lái)來(lái)回回,新對(duì)象總是在正用的那部份分配。想想你在運(yùn)行 Java 程序的時(shí)候應(yīng)該有過(guò)突然被中止不動(dòng)的時(shí)候,可能就是 GC 在活動(dòng)了。
    分代收集器
    簡(jiǎn)單的停止拷貝收集器的缺點(diǎn)是,每次收集時(shí),所有的活動(dòng)對(duì)象都要移動(dòng)來(lái)移動(dòng)去。對(duì)于短生命的對(duì)象還好說(shuō),經(jīng)??梢跃偷亟鉀Q掉,可是對(duì)于長(zhǎng)生命周期的對(duì)象就純粹是個(gè)體力勞動(dòng)了,把它挪來(lái)挪去除消耗大量的時(shí)間,沒(méi)有產(chǎn)生任何效益。分代收集能直接讓長(zhǎng)生命周期的對(duì)象長(zhǎng)時(shí)間的呆在一個(gè)地方按兵不動(dòng)。GC 的精力可以更多的花在收集*對(duì)象上。
    這種方法里,堆被分成兩個(gè)或更多的子堆,每一個(gè)堆為一“代”對(duì)象服務(wù)。最年幼的那一代進(jìn)行最頻繁的垃圾收集。因?yàn)槎鄶?shù)對(duì)象是*的,只有很小部分的年幼對(duì)象可以在經(jīng)歷第一次收集后還存活。如果一個(gè)最年幼的對(duì)象經(jīng)歷了好幾次垃圾收集后仍是活著的,那這個(gè)對(duì)象就成為壽命更高的一代,它被轉(zhuǎn)移到另外一個(gè)子堆中去。年齡更高一代的收集沒(méi)有年輕一代來(lái)得頻繁。每當(dāng)對(duì)象在所屬的年齡代中變得成熟(多次垃圾收集后仍幸存)之后,就可以轉(zhuǎn)移到更高年齡的一代中去。
    分代收集除了可應(yīng)用于拷貝算法,也可以應(yīng)用于標(biāo)記清除算法。不管在哪種情況下,把堆按照對(duì)象年齡分組可以提高最基本的垃圾收集的性能。