簡單談談Java中的垃圾回收器
1. 垃圾回收器演算法
目前主流垃圾回收器都採用的是可達性分析演算法來判斷物件是否已經存活,不使用引用計數演算法判斷物件存活的原因在於該演算法很難解決相互引用的問題。
1.1 標記-清除演算法(Mark-Sweep)
標記-清除演算法由標記階段和清除階段構成。標記階段是把所有活著的物件都做上標記的階段;清除階段是把那些沒有標記的物件,也就是非活動物件回收的階段。通過這兩個階段,就可以令不能利用的記憶體空間重新得到利用。
從標記-清除演算法我們可以看出,該演算法不涉及物件移動,但是可能會產生記憶體碎片化問題。空間碎片太高可能會導致程式執行時需要分配較大記憶體時候,無法找到足夠的連續記憶體,需要其他垃圾回收幫助回收記憶體。
1.2 複製演算法(Copying)
複製演算法記憶體空間分為兩塊區域:From、to,每次只使用其中一塊,在垃圾回收時將正在使用的記憶體中的存活物件複製到未被使用的記憶體塊中,之後,清除正在使用的記憶體塊中的所有物件,交換兩個記憶體的角色,完成垃圾回收。
上面那種複製演算法有一半的空間是浪費的。所以在Java新生代把記憶體區域分為Eden空間、from、to空間3個部分,from和to空間也稱為survivor
空間,用於存放未被回收的物件。物件開始都是Eden生成;當回收時,將Eden和from中存活的物件移動到to區域中。
複製演算法存在空間浪費的情況,始終都要保持一個Survivor是空閒的,並且在GC的時候要是存活物件大小超過了Survivor中的大小,就需要另外的策略儲存存活物件。
目前open JDK新生代回收策略就是採用的複製演算法,其中Eden和Survivor的預設配置為8:1
1.3 標記-壓縮演算法(Mark-Compact)
標記-壓縮演算法由標記階段和壓縮階段構成。標記階段標記-清除演算法中的標記階段完全一樣,壓縮階段是讓所有存活的物件向一端移動。這樣空閒記憶體都在另外一端,屬於連續空間,不存在記憶體碎片化問題,但是會產生物件移動。
1.4 分代演算法(Generational GC)
根據物件的不同生命週期分別管理, JVM 中將物件分為我們熟悉的新生代、老年代和永久代分別管理。這樣做的好處就是可以根據不同型別物件進行不同策略的管理,例如新生代中物件更新速度快,就會使用效率較高的複製演算法。老年代中記憶體空間相對分配較大,而且時效性不如新生代強,就會常常使用Mark-Sweep-Compact (標記-清除-壓縮)演算法。
2. 各個演算法的效能比較
標記-清除 | 複製演算法 | 標記壓縮 | |
---|---|---|---|
時間開銷 | 中等 | 最快 | 最慢 |
空間浪費 | 不存在 | 存在 | 不存在 |
記憶體碎片化 | 存在 | 不存在 | 不存在 |
是否移動物件 | 否 | 是 | 是 |
從表格中我們可以看出,複製演算法效率最高,也不存在記憶體碎片化,但有空間浪費的現象,一般用來處理新生代中的物件
而標記-清除演算法和標記壓縮演算法則主要處理老年代中物件記憶體分配比較大的,並且時效性不如新生代的
3. 常見的垃圾回收器
3.1 按照處理過程分類:
- 序列垃圾回收器(Serial Garbage Collector)
- 並行垃圾回收器(Parallel Garbage Collector)
- 併發標記掃描垃圾回收器(CMS Garbage Collector)
3.1.1 序列垃圾回收器
回收器名稱 | 使用的演算法 | 作用區域 | 單/多執行緒 | 備註 |
---|---|---|---|---|
Serial | 複製演算法 | 新生代 | 單執行緒 | 簡單高效,不建議使用,Client預設的 |
Serial Old | 標記-壓縮 | 老年代 | 單執行緒 | 能和所有的Young GC搭配使用 |
3.1.2 並行垃圾回收器
回收器名稱 | 使用的演算法 | 作用區域 | 單多執行緒 | 備註 |
---|---|---|---|---|
ParNew | 複製演算法 | 新生代 | 多執行緒 | 唯一和CMS搭配的新生代垃圾回收器 |
Parallel Scavenge | 複製演算法 | 新生代 | 多執行緒 | 更關注於吞吐量 |
ParNew Old | 標記整理 | 老年代 | 多執行緒 | 搭配Parallel Scavenge垃圾回收器 |
3.1.3 併發標記掃描垃圾回收器
回收器名稱 | 使用的演算法 | 作用區域 | 單多執行緒 | 備註 |
---|---|---|---|---|
CMS | 標記-清除 | 老年代 | 多執行緒 | 追求最短的暫時時間 |
3.2 按照處理的區域分類:
3.2.1 新生代(Young GC)
- Serial
- ParNew
- Parallel Scavenge
3.2.2 老年代(Old GC)
- Serial Old
- ParNew Old
- CMS
4. 垃圾回收器的選擇策略
- 客戶端程式:Serial + Serial Old;
- 吞吐率優先的服務端程式(比如:計算密集型):Parallel Scavenge + Parallel Old;
- 響應時間優先的服務端程式:ParNew + CMS。
目前很大一部分的Java應用都集中在網際網路的伺服器端,這類應用尤其關係服務的響應時間,希望應用暫停時間更短,所以基本上使用的都是 ParNew + CMS
在啟動JVM引數加上 -XX:+UseConcMarkSweepGC
這個引數表示對於老年代的回收採用 CMS。
4.1 CMS執行過程
CMS 的回收過程主要分為下面的幾個步驟:
- 初始標記(Initial Mark)
- 併發標記(Concurrent marking)
- 併發預清理(Concurrent pre-preclean)
- 重新標記(Final Remark)
- 併發清理(Concurrent sweep)
- 併發重置(Concurrent reset)