概述
垃圾收集即GC照捡。經(jīng)過半個多世紀的發(fā)展,目前內(nèi)存的動態(tài)分配與內(nèi)存回收技術已經(jīng)相當成熟击狮,一切看起來都進入了“自動化”時代佛析,那我們?yōu)槭裁催€要去了解GC和內(nèi)存分配呢?答案很簡單:當需要排查各種內(nèi)存溢出彪蓬,內(nèi)存泄漏問題時寸莫,當垃圾手機成為系統(tǒng)達到更高并發(fā)的瓶頸時,我們就需要對這些“自動化”的技術實施必要的監(jiān)控和調(diào)節(jié)档冬。
GC瞄準誰
我們已知程序計數(shù)器膘茎,虛擬機棧以及本地方法棧都是隨著線程而生桃纯,隨著線程而亡。所以這部分跟隨線程生死的運行時區(qū)域在線程結束時內(nèi)存也自然的被回收了披坏。這一部分的區(qū)域就不需要過多的考慮內(nèi)存的回收的問題态坦。而Java堆和方法區(qū)則不一樣。一個接口中的多個實現(xiàn)類可能需要的內(nèi)存相差很大棒拂,一個方法的多個分支需要的內(nèi)存也可能不一樣伞梯。我們只有在程序運行階段才會知道會創(chuàng)建哪些對象,這部分的內(nèi)存分配及回收都是動態(tài)的帚屉。垃圾收集器關注的也正是這部分內(nèi)存谜诫。
哪些內(nèi)存需要回收
引用計數(shù)算法
給對象添加一個引用計數(shù)器,當有一個地方應用它時計數(shù)器加1攻旦;當引用失效時猜绣,計數(shù)器減1;任何時刻計數(shù)器為0的對象不可能再被使用敬特。
優(yōu)點:
實現(xiàn)簡單掰邢,判定效率高。適用于大部分情況伟阔,但Java并沒有選擇其來管理內(nèi)存辣之。
缺點:很難解決對象間循環(huán)引用問題。
例子:
public class ReferenceCountingGC {
public Object instance = null;
private static final int _1MB = 1024 * 1024;
private byte[] bigSize = new byte[2 * _1MB];
public static void testGC() {
ReferenceCountingGC objA = new ReferenceCountingGC();
ReferenceCountingGC objB = new ReferenceCountingGC();
objA.instance = objB;
objB.instance = objA;
objA = null;
objB = null;
// 該程序是無意義的相互引用且不能再被訪問
// 此處若發(fā)生GC皱炉,引用計數(shù)算法是無法通知GC收集器回收他們的
// 但在Java中會將他們回收怀估,因為Java中并不是通過該算法來管理內(nèi)存的
System.gc();
}
public static void main(String[] args) {
testGC();
}
}
可達性分析算法
通過一系列的稱為“GC Root”的對象作為起始點,從這些節(jié)點開始向下搜索合搅,搜索所走的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root不可達時多搀,則證明這個對象是不可用的。如下圖灾部,對象object5康铭、 object6、 object7赌髓,雖然相互有關聯(lián)但是它們到GC Root不可達从藤,所以它們被判定為是可回收的對象。
在Java語言中锁蠕,可作為GC Root對象包括以下幾種:
- 虛擬機棧(棧幀中的本地變量表)中的引用對象夷野。
- 方法區(qū)中的靜態(tài)屬性或常量引用的對象。
- 本地方法棧中JNI(即一般說的Native方法)引用的對象荣倾。
后來Java又對引用的概念進行了擴充悯搔。引用分為強引用(Strong Reference)、軟引用(Soft Reference)舌仍、弱引用(Weak Reference)妒貌、虛引用(Phantom Reference)4種者娱,這4種引用強度依次逐漸減弱。
- 強引用就是指在程序代碼之中普遍存在的苏揣,類似“Object obj = new Object()”這類的引用,只要強引用還存在推姻,垃圾收集器永遠不會回收掉被引用的對象平匈。
- 軟引用是用來描述一些還有用但并非必需的對象。對于軟引用關聯(lián)著的對象藏古,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前增炭,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內(nèi)存拧晕,才會拋出內(nèi)存溢出異常隙姿。在JDK 1.2之后,提供了SoftReference類來實現(xiàn)軟引用厂捞。
- 弱引用也是用來描述非必需對象的输玷,但是它的強度比軟引用更弱一些,被弱引用關聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前靡馁。當垃圾收集器工作時欲鹏,無論當前內(nèi)存是否足夠,都會回收掉只被弱引用關聯(lián)的對象臭墨。在JDK 1.2之后赔嚎,提供了WeakReference類來實現(xiàn)弱引用。
- 虛引用也稱為幽靈引用或者幻影引用胧弛,它是最弱的一種引用關系尤误。一個對象是否有虛引用的存在,完全不會對其生存時間構成影響结缚,也無法通過虛引用來取得一個對象實例损晤。為一個對象設置虛引用關聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。在JDK 1.2之后红竭,提供了PhantomReference類來實現(xiàn)虛引用沉馆。
非死不可嗎?
要真正宣告一個對象的死亡至少要經(jīng)歷兩次標記過程德崭。如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用連斥黑,那他將會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執(zhí)行finalize()方法眉厨。當對象沒有覆蓋或者finalize()方法已經(jīng)被虛擬機調(diào)用過锌奴,虛擬機將這兩種情況都視為沒有必要執(zhí)行了。
如果該對象被判定為有必要執(zhí)行finalize()方法憾股。那么這個對象將會被放置在F-Quere隊列之中鹿蜀。 并會被虛擬機創(chuàng)建的箕慧,低優(yōu)先級的Finalizer線程去執(zhí)行該對象的finalize()方法。但并不會等待它結束茴恰,因為對象在finalize()方法執(zhí)行中如果出現(xiàn)執(zhí)行緩慢或者發(fā)生死循環(huán)颠焦。將會導致F-Queue隊列中其他對象永久處于等待。甚至導致整個內(nèi)存回收系統(tǒng)崩潰往枣。之后GC將會對F-Queue之中的對象進行第二次標記伐庭。如果在第二次標記前這些對象在自己的finalize()方法中可以拯救自己(重新與引用鏈上的任何一個對象建立關聯(lián)即可)也是可以成功存活下來并被移除“即將回收”的集合的。 如果此時還沒有逃脫分冈,那就真的要被回收了圾另。
雖然其有點兒C/C++中析構函數(shù)的意思,但其其實并不是雕沉,相反卻是Java程序員期初為了更好的被C/C++程序員所接受而做的一種妥協(xié)集乔。
注意:finalize()方法的運行代價高昂,不確定性大坡椒,無法保證各個對象的調(diào)用順序扰路。所以盡量使用try-finally來代替它。
幾種垃圾收集算法
標記—清除算法
算法分為標記和清除兩個階段:首先標記出所有需要回收的對象倔叼,在標記完成后統(tǒng)一回收所有被標記的對象幼衰,它的標記過程就是使用可達性算法進行標記的。
不足:
- 效率問題缀雳,標記和清除兩個過程的效率都不高
- 空間問題渡嚣,標記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,導致以后分配較大對象時內(nèi)存不足以至于不得不提前觸發(fā)另一次垃圾收集動作肥印。
復制算法
復制算法:將可用內(nèi)存按照容量分為大小相等的兩塊识椰,每次只使用其中的一塊。當這一塊的內(nèi)存用完了深碱,就將還存活著的對象復制到另一塊上面腹鹉,然后把已使用過的內(nèi)存空間一次清理掉。
內(nèi)存分配時不用考慮內(nèi)存碎片問題敷硅,只要一動堆頂指針功咒,按順序分配內(nèi)存即可,實現(xiàn)簡單绞蹦,運行高效力奋。代價是將內(nèi)存縮小為原來的一半。
實際應用中將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間幽七,每次使用Eden和其中的一塊Survivor景殷。當回收時,將Eden和Survivor中還存活著的對象一次性復制到另一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間猿挚。
Hotspot虛擬機中默認的Eden和Survivor的大小比例是8:1咐旧。
缺點:
- 在對象存活率較高的情況下,效率變低
- 有額外空間分配擔保負擔绩蜻,無法應對被使用的內(nèi)存100%對象存活的極端情況铣墨,致使老年代不能使用。
標記-整理算法
標記整理算法(Mark-Compact)办绝,標記過程仍然和“標記-清除”一樣伊约,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活對象向一端移動八秃,然后直接清理掉端邊界以外的內(nèi)存。
分代收集算法
根據(jù)對象存活周期的不同將內(nèi)存分為幾塊肉盹。一般把Java堆分為新生代和老年代昔驱,根據(jù)各個年代的特點采用最合適的收集算法。在新生代中上忍,每次垃圾收集時有大批對象死去骤肛,只有少量存活,可以選用復制算法窍蓝。而老年代對象存活率高腋颠,使用標記清理或者標記整理算法。
什么時候回收吓笙?
當這三個分代的堆空間比較緊張或者沒有足夠的空間來為新到的請求分配的時候淑玫,垃圾回收機制就會起作用。有兩種類型的垃圾回收方式:次收集和全收集面睛。當新生代堆空間滿了的時候絮蒿,會觸發(fā)次收集將還存活的對象移到年老代堆空間。當年老代堆空間滿了的時候叁鉴,會觸發(fā)一個覆蓋全范圍的對象堆的全收集土涝。
次收集
- 當新生代堆空間緊張時會被觸發(fā)
- 相對于全收集而言,收集間隔較短
全收集
- 當老年代或者持久代堆空間滿了幌墓,會觸發(fā)全收集操作
- 可以使用System.gc()方法來顯式的啟動全收集
- 全收集一般根據(jù)堆大小的不同但壮,需要的時間不盡相同,但一般會比較長常侣。不過蜡饵,如果全收集時間超過3到5秒鐘,那就太長了