二級java輔導:有關于JVM的垃圾收集(一)

字號:

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