剖析垃圾回收機制(上)
阿新 • • 發佈:2020-07-28
前言:
關於 JVM 垃圾回收機制面試中主要涉及這三個考題:
垃圾回收的第一步,就是找出活躍的物件。我們反覆強調 GC 過程是逆向的。根據 GC Roots 遍歷所有的可達物件,這個過程,就叫作標記。
如圖所示,圓圈代表的是物件。綠色的代表 GC Roots,紅色的代表可以追溯到的物件。可以看到標記之後,仍然有多個灰色的圓圈,它們都是被回收的物件。
清除(Sweep)
清除階段就是把未被標記的物件回收掉。
但是這種簡單的清除方式,有一個明顯的弊端,那就是碎片問題。比如我申請了 1k、2k、3k、4k、5k 的記憶體。
由於某種原因 ,2k 和 4k 的記憶體,我不再使用,就需要交給垃圾回收器回收。
這個時候,我應該有足足 6k 的空閒空間。接下來,我打算申請另外一個 5k 的空間,結果系統告訴我記憶體不足了。系統執行時間越長,這種碎片就越多。
在很久之前使用 Windows 系統時,有一個非常有用的功能,就是記憶體整理和磁碟整理,執行之後有可能會顯著提高系統性能。這個出發點是一樣的。
複製(Copy)
解決碎片問題沒有銀彈,只有老老實實的進行記憶體整理。
有一個比較好的思路可以完成這個整理過程,就是提供一個對等的記憶體空間,將存活的物件複製過去,然後清除原記憶體空間。在程式設計中,一般遇到擴縮容或者碎片整理問題時,複製演算法都是非常有效的。比如:HashMap 的擴容也是使用同樣的思路,Redis 的 rehash 也是類似的。整個過程如圖所示:
這種方式看似非常完美的,解決了碎片問題。但是,它的弊端也非常明顯。它浪費了幾乎一半的記憶體空間來做這個事情,如果資源本來就很有限,這就是一種無法容忍的浪費。
整理(Compact)
其實,不用分配一個對等的額外空間,也是可以完成記憶體的整理工作。你可以把記憶體想象成一個非常大的陣列,根據隨機的 index 刪除了一些資料。那麼對整個陣列的清理,其實是不需要另外一個數組來進行支援的,使用程式就可以實現。它的主要思路,就是移動所有存活的物件,且按照記憶體地址順序依次排列,然後將末端記憶體地址以後的記憶體全部回收。
我們可以用一個理想的演算法來看一下這個過程。
- JVM 中有哪些垃圾回收演算法?它們各自有什麼優劣?
- CMS 垃圾回收器是怎麼工作的?有哪些階段?
- 服務卡頓的元凶到底是誰?
last = 0 for但是需要注意,這只是一個理想狀態。物件的引用關係一般都是非常複雜的,我們這裡不對具體的演算法進行描述。你只需要瞭解,從效率上來說,一般整理演算法是要低於複製演算法的。(i=0;i<mems.length;i++){ if(mems[i] != null){ mems[last++] = mems[i] changeReference(mems[last]) } } clear(mems,last,mems.length)
分代