天天看點

Java記憶體回收

垃圾收集(Garbage Collection,GC)

1.垃圾回收回收是哪些記憶體?

Java堆和方法區:一個接口中的多個實作類需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也可能不一樣。我們隻有在程式中處于運作期間時才能知道會建立哪些對象,這部分記憶體的配置設定和回收都是動态的,垃圾收集器所關注的是這部分記憶體。

2.如何判斷對象已死?

堆裡面存放着Java世界中幾乎所有的對象執行個體,垃圾收集器在對堆進行回收前,第一件事就是确定哪些對象還“存活着”,哪些是已經“死去”(即不可能再被任何途徑使用的對象)

a.引用計數法

給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1,;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不能再被使用的。但是此方法對循環引用的問題失效。

b.可達性分析算法

通過一系列成為“GC Roots”的對象作為起始點,從這些節點開始向下搜尋,搜尋所走過的路徑稱為引用鍊(Reference Chain),當一個對象到GC Roots沒有任何引用鍊相連(圖論:從GC Roots到這個對象不可達)時,則證明此對象是不可用的。

Java記憶體回收
Java記憶體回收

在Java語言中,可作為GC Roots的對象包括下面幾種:(沒看太懂

Java記憶體回收

)

虛拟機棧(棧幀中的本地變量表)中的引用的對象。

方法區中類靜态屬性引用的對象。

方法區中常量引用的對象。

本地方法棧中JNI(即一般說的Native方法)引用的對象

引用:

如果reference類型的資料中存儲的數值代表的是另外一塊記憶體的起始位址,就稱這塊記憶體代表着一個引用。

(關于Java對象引用可參考http://www.cnblogs.com/focusChen/articles/2497768.html)

強引用:Object obj = new Object();隻要強引用還存在,垃圾回收器永遠不會回收掉被引用的對象。Strong Reference

軟引用:用來描述一些還有用但并非必需的對象。在系統将要發生記憶體溢出異常之前,将會把這些對象列進回收範圍之中進行第二次回收。如果這次回收還沒有足夠的記憶體,才會抛出記憶體溢出異常。Soft Reference

弱引用:被弱引用關聯的對象隻能生存到下一次垃圾收集發生之前。當垃圾回收器夠工作時,無論目前記憶體是否足夠,都會回收掉隻被弱引用關聯的對象。(Weak Reference)

虛引用:目的是能在這個對象收集器回收時受到一個系統通知。(PhantomReference)

3.如何回收?

要宣告一個對象死亡,至少要經曆兩次标記過程:如果對象在進行可達性分析後發現沒有與GC Roots相連接配接的引用鍊,那它将會被第一次标記并且進行一次篩選,篩選的條件是此對象是否有必要執行finalize()方法。當對象沒有覆寫finalize()方法,或者finalize()方法已經被虛拟機調用過,虛拟機将這兩種情況都視為“沒有必要執行”。

如果這個對象被判定為有必要執行finalize()方法,那麼這個對象将會放置在一個叫做F-Queue的隊列中,并在稍後由一個虛拟機自動建立的、低優先級的Finalizer線程去執行它。finalize()方法是對象逃脫死亡命運的最後一次機會。譬如把自己(this)指派給某個類變量或者對象的成員變量。那在第二次标記時它将被移除出“即将回收”的集合。

如果對象面臨下一次回收,它的finalize()方法不會被再次執行。

4.垃圾收集算法?

a.标記-清除算法

首先标記處所有需要回收的對象,在标記完成後統一回收所有被标記的對象。

不足之處:标記和清除兩個過程的效率都不高;空間問題:标記清楚之後會産生大量不連續的記憶體碎片,空間碎片太多可能會導緻以後在程式運作過程中需要配置設定較大對象時,無法找到足夠的連續記憶體而不得不提前觸發另一次垃圾收集動作。

b.複制算法

将可用記憶體按容量劃分為大小相等的兩塊,每次隻使用其中的一塊。當這一塊的記憶體用完了,就将還存活着的對象複制到另一塊上面,然後再把已使用過的記憶體空間一次清理掉。

不足:将記憶體縮小為原來的一半。

新生代中的對象98%是“朝生夕死”的,是以并不需要按照1:1的比例來劃分記憶體空間,而是将記憶體分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,将Eden和Survivor中還存活着的對象一次性地複制到另一塊Survivor空間上,最後清理掉Eden和剛才用過的Survivor空間。當Survivor空間不夠用時,需要依賴其他記憶體(老年代)進行配置設定擔保。

c.标記-整理算法

标記過程與标記-清除算法一樣,但後續步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然後直接清理掉端邊界以外的記憶體。

d.分代标記算法

一般是把Java堆分為新生代和老生代,這樣就可以根據各個年代的特點采用最适當的收集算法。在新生代中,每次垃圾收集時都發現大批對象死去,隻有少量存活,那就選用複制算法,隻需要付出少量存活對象的複制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行擔保,就必須使用标記-清除或者标記-整理算法來進行回收。