再介紹垃圾回收算法以前,先來看看 Java中的堆,Java裏的堆指的是用於存放 Java 對象的內存區域。JVM的堆被同一個JVM實例中全部的Java線程共享,它一般由某種自動管理機制所管理,這種機制一般叫作"垃圾回收"。html
在Java 1.8 中,堆的內存模型大體以下: java
堆大小 = 新生代 + 老年代。其中堆的大小能夠經過參數 -Xms,-Xmx
來指定。算法
默認的,新生代(Young) 與老年代(Old)的比例的值是 1:2 (該值能夠經過參數 -XX: NewRatio來指定),即: 新生代(Young) = 1/3的堆空間大小,老年代(Old) = 2/3的堆空間大小。oracle
其中,新生代(Young)被細分爲 Eden 和 兩個 Survivor區域,這兩個 Survivor區域分別被命名爲 from 和 to,以示區分。jvm
默認的,Eden:from:to = 8:1:1 (能夠經過參數 -XX: SurvivorRatio來設定),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。jsp
JVM每次只會使用 Eden和其中的一塊 Survivor區域來爲對象服務,因此不管何時,總有一塊 Survivor區域是空閒着的,新生代實際可用的內存空間爲 90% 的新生代空間。post
在GC算法中,最簡單的就是 "標記-清除"(Mark-Sweep)算法。它的原理比較簡單,首先根據可達性分析算法對不可達對象進行標記,在標記完成後統一回收全部被標記的對象。標記-清除算法的執行過程以下圖: 網站
標記-清除算法有兩個缺點:基於Mark-Sweep的GC 多用於老年代.net
複製算法的思路是它將可用內存按容量劃分爲大小相等的兩塊,每次只用其中的一塊。當這塊內存用完了,就將還存活的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。線程
這樣每次都是對半區進行內存回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存便可。可是這種算法是用空間換時間,代價是將內存縮小爲原來的一半,代價很高。而新生代的對象通常是存活時間較短的對象,GC頻率較高,佔內存較少,所以新生代通常都採用基於複製的GC。複製算法過程以下:
HotSpot 虛擬機將新生代內存分爲 一塊較大的 Eden空間和兩塊較小的 Survivor空間,Eden和Survivor的大小比例是8:1。每次新生代中可用內存空間爲整個新生代容量的 90%。咱們沒有辦法保證每次回收都只有很少於 10%的對象存活,當 Survvivor 空間不夠用時,須要依賴老年代進行分配擔保。
複製收集算法在對象存活率較高時就要進行較多的複製操做,效率會變低,它比較適合收集新生代對象,至於老年代這種通常不選用複製算法。根據老年代的特色,可使用 "標記-整理"算法或者"標記-清除"算法。
標記 - 整理算法能夠解決內存碎片的問題,並且思路也比較簡單,它的思想就是,讓全部存活的對象都向一端移動,而後直接清理掉邊界之外的內存,以下圖所示:
當前商業虛擬機的垃圾收集都採用"分代收集",將堆分爲新生代和老年代,根據各個年代的特色採用最適當的收集算法:
下面的分析參照 R大對於GC算法的分析:hllvm.group.iteye.com/group/topic…
分代式 GC裏,老年代經常使用 mark-sweep
(標記 - 清除算法),或者是 mark-sweep /mark-compact 的混合方式,通常狀況下用 mark-sweep,統計估算碎片量達到必定程度時用 mark-compact(標記 - 整理)。這是由於傳統上你們認爲老年代的對象可能會長時間存活且存活率高,或者是比較大,這樣拷貝起來不划算,還不如採用就地收集的方式。 Mark-Sweep
,Mark-compact
,copying
這三種基本算法裏,只有mark-sweep是不移動對象的(也就是不拷貝的),因此老年代常選用 mark-sweep。固然針對不一樣的垃圾收集器,GC 算法是有區別的
如下是三種算法的比較:
mark-sweep | mark-compact | copying | |
---|---|---|---|
速度 | 中等 | 最慢 | 最快 |
空間開銷 | 少(但會堆積碎片) | 少(不堆積碎片) | 一般須要活對象的2倍大小(不堆積碎片) |
移動對象? | 否 | 是 | 是 |
關於時間開銷:
若是把 mark,sweep,compact,copying這幾種動做的耗時放在一塊兒看,大體有這樣的關係:
compaction >= copying > marking > sweeping marking + sweeping > copying
總結一下:
在分代式假設中,年輕代中對象在 minor GC 時的存活率應該很低,這樣用copying算法就是最合算的,由於其時間開銷與活對象的大小成正比,若是沒多少活對象,它就很是快。並且 young GC 自己應該比較小,就算須要2倍空間也只會浪費不太多的空間
而老年代被 GC 時對象存活率可能會很高,並且假定可用剩餘空間不太多,這樣copying 算法就不太合適,因而更可能選用另兩種算法,特別是不用移動對象的 Mark-Sweep算法
不過 HotSpot VM中除了CMS收集器以外的其餘收集器都是會移動對象的,也就是要麼是 copying,要麼是mark-compact的變種
-XX:+<option>
啓用選項 例如:-XX:+PrintGCDetails啓動打印GC信息的選項,其中+號表示true,開啓的意思-XX:-<option>
不啓用選項 ,例如:-XX:-PrintGCDetails關閉啓動打印GC信息的選項,其中-號表示false,關閉的意思-XX:<option>=<number>
-XX:<option>=<string>
更多JVM參數選項設置,請參考Oracle官方網站給出的相關信息: www.oracle.com/technetwork…
以上主要參考了《深刻理解Java虛擬機》這本書以及R大對於GC算法的分析,本人對於JVM是渣渣級選手,若有問題之處,歡迎指出
另外關於垃圾算法更加詳解的解釋,三種算法的具體實現參考 中村成洋的《垃圾回收的算法與實現》