JVM垃圾回收機制和常見演算法
在探討Java垃圾回收機制之前,我們首先應該記住一個單詞:Stop-the-World。Stop-the-world意味著 JVM由於要執行GC而停止了應用程式的執行,並且這種情形會在任何一種GC演算法中發生。當Stop-the-world發生時,除了GC所需的執行緒以外,所有執行緒都處於等待狀態直到GC任務完成。事實上,GC優化很多時候就是指減少Stop-the-world發生的時間,從而使系統具有高吞吐 、低停頓的特點。
垃圾回收機制
垃圾回收機制是由垃圾回收器Garbage Collection來實現的。GC是後臺的守護程序,其特別之處在於它是一個低優先順序程序,但是可以根據記憶體使用情況動態的調整它的優先順序,而且這個服務不是我們啟動的而是自動啟動的。因此,它在記憶體中低到一定程度時,才會自動執行,從而實現對記憶體的回收,這就是垃圾回收時間不確定的原因。
程式執行期間,所有物件例項儲存在執行時資料區域的堆記憶體中,當一個物件不再被引用(使用),它就需要被回收。在GC過程中,不需要被使用的物件從堆記憶體中回收,這樣就會有空間被迴圈利用。
可作為GC Roots的節點主要在全域性性的引用(例如常量或類靜態屬性)與執行上下文(例如棧幀中的本地變量表)
1 引用計數法
為物件A增加一個引用計數器,用於記錄被引用的次數。如果有物件對A進行引用,那麼A的引用計數器就+1,當引用失效時,A的引用計數器-1。垃圾回收時,只回收計數器為0的物件。
特點:簡單但是速度很慢,缺陷是不能處理迴圈引用的情況。
實時性高,不需要等到記憶體不足時,才開始回收。
2 標記-清除法
標記清除演算法分為“標記”和“清除”兩個階段:
- 標記:從跟節點開始標記引用的物件;
- 清除:未被標記引用的物件就是垃圾物件,可以被清理。
可以看出標記清除法解決了引用計數法的迴圈引用問題,從根節點不可達的物件都會被回收。
缺陷:
1、效率問題。標記和清除兩個過程的效率都不高
2、空間問題。標記清除後會產生大量不連續的記憶體碎片,空間碎片太多可能會導致以後在程式執行過程中需要分配較大物件時,無法找到足夠的、連續的記憶體而不得不提前觸發另一次垃圾收集動作。
3 複製演算法
為了解決效率問題,它將可用的記憶體按照容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的記憶體用完了,就將活著的物件複製到另外一塊上面,然後再把已使用過的記憶體空間清理掉。這樣使得每次都是對整個半區進行記憶體回收,記憶體分配時也就不用考慮記憶體碎片等複雜情況。只要移動堆指標,按照順序分配記憶體即可,實現簡單,執行高效。
缺陷:這種演算法的代價是將記憶體縮小為原來的一半。
4 標記-整理演算法
標記過程仍然與”標記-清除”演算法一樣,但是後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然後直接清理掉端邊界以外的記憶體。
5 分代收集演算法
根據物件的存活週期的不同,將記憶體劃分為幾塊。一般是把java堆分成新生代和老年代,這樣就可以根絕各個年代的特點採取最適當的收集演算法。在新生代中,每次垃圾回收時都發現大批物件的死去,只有少量存活,那就選用複製演算法,只需要付出少量存活物件的複製成本就可以完成收集。而老年代中因為物件存活率高,沒有額外空間對它進行分配擔保,就必須使用”標記-清理”或者“標記-整理”演算法來進行回收。
讀後有收穫,小禮物走一走,請作者喝咖啡。 Buy me a coffee. ☕