繼續接着上一次【http://www.javashuo.com/article/p-sexeeasr-co.html】的來學習,上次在結尾處提到了JVM常見的GC算法,以下:html

接下來則逐一的對其進行學習,不過仍是純理論,比較枯燥可是必須得過一遍。web
標記-清除算法(Mark-Sweep):算法
- 算法分爲「標記」和「清除」兩個階段,首先標記出全部須要回收的對象,而後回收全部須要回收的對象。
- 缺點:
a、效率問題:標記和清理兩個過程效率都不高。
b、空間問題:標記清理以後會產生大量不連續的內存碎片,空間碎片太多可能會致使後續使用中沒法找到足夠的連續內存而提早觸發另外一次的垃圾蒐集動做。
下面用圖表說明一下整個算法的過程,首先初始內存爲:學習

能夠看到從棧中有兩個引用指向了堆,而堆中的對象存有互相引用的狀況,其中左側的棧能夠稱爲Root GC,它可達的對象爲A和B,以下:spa

其中標綠的是不能被GC回收的,接着繼續:3d


最終內存的可達狀況爲:指針

其中標紅的則是不能被Root GC所能引用的,也就是應該是被回收掉的,因此被回收以後整個圖就變成了:htm

因此從上圖的整個過程也能看到:對象
- 效率不高,須要掃描全部對象,堆越大,GC越慢。
- 存在內存碎片問題。GC次數越多,碎片越嚴重。
複製蒐集算法(Copying):blog
- 將可用內存分爲兩塊,每次只使用其中的一塊,當半區內存用完了,僅將不存活的對象複製到另一塊上面,而後就把原來整塊內存空間一次性清理掉。
- 這樣使得每次內存回收都是對整個半區的回收,內存分配時也就不用考慮內存碎片等複雜狀況,只要移動堆頂指針,按順序分配內存就能夠了,實現簡單,運行高效。只是這種算法的代價是將內存縮小爲原來的一半,代價高昂。
- 如今的商業虛擬機中都是用了這一種收集算法來回收新生代【啥叫新生代呢?一般狀況下剛new出來的對象都會位於新生代當中,當新生代經歷了幾輪垃圾回收以後,還沒有被回收的對象,這時JVM就會認爲這些對象的存活時間比較長,則會將它們晉升到老年代中】。
- 將內存分爲一塊較大的eden空間和2塊較少的survivor【倖存者】空間,每次使用eden和其中一塊survivor,當回收時將eden和survivor還存活的對象一次性拷貝到另一塊survivor空間上,而後清理掉eden和用過的survivor。
- Oracle Hotspot虛擬機默認eden和survivor的大小比例是8:1,也就是每次只有1%的內存是「浪費」的。
- 複製收集算法在對象存活率高的時候,效率有所降低。
- 若是不想浪費50%的空間,就須要有額外的空間進行分配擔保用於應付半區內存中全部對象都100%存活的極端狀況,因此在老年代通常不能直接選用這種算法。
下面則來用圖來理解一下它的流程:

其中From-Space則是真正對象存放的空間,而To-Space則是垃圾回收以後接收被複制對象的空間,如上圖,因爲Stack引用了A和C,則會將A和C拷貝到To-Space空間中,而因爲C引用了H、K,因此H也會進到To-Space中,以下:

另外因爲H又引用了L,因此L也會進來,以下:

而又引用了B、E、I,則都會進來:

其中D、G是沒有被Stack所引用,照理只回收這倆就成了,可是!!此時則會將From-Space所有給清空,以下:

下面再對其進行一些特色描述:
- 只須要掃描存活的對象,效率更高。
- 不會產生碎片。
- 須要浪費額外的內存做爲複製區。
- 複製算法很是適合生命週期比較短的對象,由於每次GC總能回收大部分的對象,複製的開銷比較小。
- 根據IBM的專門研究,98%的Java對象只會存活1個GC週期,對這些對象很適合用複製算法。並且不用1:1的劃分工做區和複製區的空間。
標記-整理算法(Mark-Compact):
它的標記過程跟上面的複製搜索算法是同樣的,但後續步驟不是進行直接清理,而是令全部存活的對象一端移動,而後直接清理掉這端邊界之外的內存。
如圖:

能夠看到下面就是進行了回收整理的空間,它的特色是:
- 沒有內存碎片
- 比Mark-Sweep(標記清除算法)耗費更多的時間進行compact(壓縮整理)
分代收集(Generational Collecting)算法:
- 當前商業虛擬機的垃圾收集都是採用「分代收集」(Generational Collecting)算法,根據對象不一樣的存活週期將內存劃分爲幾塊。
- 通常是把Java堆分做新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法,譬如新生代每次GC都有大批對象死去,只有少許存活,那就選用複製算法只須要付出少許對象的複製成本就能夠完成收集。
- 綜合前面幾種GC算法的優缺點,針對不一樣生命週期的對象採用不一樣的GC算法,好比:
New表明新生代,如以前所說它裏面的垃圾回收算法能夠採用複製算法,而對於Old老年代的內存則能夠經過標記清除算法或者標記整理算法。
- Hotspot JVM6中共劃分爲三個代:年輕代(Young Generation)、老年代(Old Generation)、和永久代(Permanent Generation,注意這個是對於JDK8以前而言的,在JDK8以後此代是沒有了)。以下圖:
在最開始時,對象是處於年輕代中,以下:

而後對象是存在其中的Eden Space和From Space中,這倆的比例是能夠調整的,其整個默認比例是8:1:1,而當進行垃圾回收時,則會將Eden Space和From Space留下來的對象都轉到To Space上去,此時Eden Space和To Space又能夠搭配工做,此時圖中的From Space就變爲了To Space,而圖中的To Space又變成了From Space。而通過了幾回回收以後,年輕代的對象就會進入到老年代,以下:

而最終還有一個永久代,它是針對JDK8以前而存在的,以下:

下面再具體來看一下各代的概念:
- 年輕代(Young Generation)
一、新生成的對象都放在新生代。年輕代用複製算法進行GC(理論上,年輕代對象的生命週期很是短,因此適合複製算法)。
二、年輕代分三個區。一個Eden區,兩個Survivor區(能夠經過參數設置Survivor個數)。對象在Eden區中生成。當Eden區滿時,還存活的對象將被複制到一個Survivor區,當這個Survivor區滿時,此區的存活對象將被複制到另一個Survivor區,當第二個Survivor區也滿了的時候,從第一個Survivor區複製過來的而且此時還存活的對象,將被複制到老年代。2個Survivor是徹底對稱,輪流替換。
三、Eden和2個Survivor的缺省比例是8:1:1,也就是10%的空間會被浪費。能夠根據GC log的信息調整大小的比例。
- 老年代(Old Generation)
一、存放了通過一次或屢次GC還存活的對象。
二、通常採用Mark-Sweep或者Mark-Compact算法進行GC。
三、有多種垃圾收集器能夠選擇。每種垃圾收集器能夠看做一個GC算法的具體實現。能夠根據具體應用的需求選用合適的垃圾收集器(追求吞吐量?追求最短的響應時間?)。
- 永久代【注意:從JDK8開始已經用元空間來替代它了】一、並不屬於堆(Heap)。可是GC也會涉及到這個區域。二、存放了每一個Class的結構信息,包括常量池、字段描述、方法描述。與垃圾收集要收集的Java對象關係不大。