public class Test {
public Object instance = null;
public static void main(String[] args) {
Test a = new Test();
Test b = new Test();
a.instance = b;
b.instance = a;
a = null;
b = null;
doSomething();
}
}
在上述代碼中,a 與 b 引用的對象實例互相持有了對象的引用,因此當我們把對 a 對象與 b 對象的引用去除之后,由于兩個對象還存在互相之間的引用,導致兩個 Test 對象無法被回收。
現(xiàn)在的商業(yè)虛擬機都采用這種收集算法回收新生代,但是并不是劃分為大小相等的兩塊,而是一塊較大的 Eden 空間和兩塊較小的 Survivor 空間,每次使用 Eden 和其中一塊 Survivor。在回收時,將 Eden 和 Survivor 中還存活著的對象全部復制到另一塊 Survivor 上,最后清理 Eden 和使用過的那一塊 Survivor。
通過引入 Region 的概念,從而將原來的一整塊內(nèi)存空間劃分成多個的小空間,使得每個小空間可以單獨進行垃圾回收。這種劃分方法帶來了很大的靈活性,使得可預(yù)測的停頓時間模型成為可能。通過記錄每個 Region 垃圾回收時間以及回收所獲得的空間(這兩個值是通過過去回收的經(jīng)驗獲得),并維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的 Region。
每個 Region 都有一個 Remembered Set,用來記錄該 Region 對象的引用對象所在的 Region。通過使用 Remembered Set,在做可達性分析的時候就可以避免全堆掃描。
如果不計算維護 Remembered Set 的操作,G1 收集器的運作大致可劃分為以下幾個步驟:
初始標記
并發(fā)標記
最終標記:為了修正在并發(fā)標記期間因用戶程序繼續(xù)運作而導致標記產(chǎn)生變動的那一部分標記記錄,虛擬機將這段時間對象變化記錄在線程的 Remembered Set Logs 里面,最終標記階段需要把 Remembered Set Logs 的數(shù)據(jù)合并到 Remembered Set 中。這階段需要停頓線程,但是可并行執(zhí)行。
篩選回收:首先對各個 Region 中的回收價值和成本進行排序,根據(jù)用戶所期望的 GC 停頓時間來制定回收計劃。此階段其實也可以做到與用戶程序一起并發(fā)執(zhí)行,但是因為只回收一部分 Region,時間是用戶可控制的,而且停頓用戶線程將大幅度提高收集效率。
具備如下特點:
并行和并發(fā)
分代收集
空間整合:整體來看是基于“標記 - 整理”算法實現(xiàn)的收集器,從局部(兩個 Region 之間)上來看是基于“復制”算法實現(xiàn)的,這意味著運行期間不會產(chǎn)生內(nèi)存空間碎片。
可預(yù)測的停頓:能讓使用者明確指定在一個長度為 M 毫秒的時間片段內(nèi),消耗在 GC 上的時間不得超過 N 毫秒。
五、降低停頓時間
1. 使用 CMS 收集器 CMS 收集器進行垃圾回收,有 4 個步驟:
初始標記
并發(fā)標記
重新標記
并發(fā)清除
其中初始標記和重新標記需要 “stop the world”,但耗時時間最長的并發(fā)標記、并發(fā)清除過程中,GC 線程都可與用戶線程一起工作。整體上說,CMS 和用戶線程是并行的。