上一篇文章 介紹了jvm虛擬機運行時內(nèi)存結(jié)構(gòu)以及如何標識需要回收的對象,這一節(jié)主要講解垃圾回收的基本算法。
基本上 jvm內(nèi)存回收有三種 基本算法
標記清除的算法最簡單,主要是標記出來需要回收的對象,然后然后把這些對象在內(nèi)存的信息清除。如何標記需要回收的對象,在上一篇文章里面已經(jīng)有說明。

這個算法是在標記-清除的算法之上進行一下壓縮空間,重新移動對象的過程。因為標記清除算法會導致很多的留下來的內(nèi)存空間碎片,隨著碎片的增多,嚴重影響內(nèi)存讀寫的性能,所以在標記-清除之后,會對內(nèi)存的碎片進行整理。最簡單的整理就是把對象壓縮到一邊,留出另一邊的空間。由于壓縮空間需要一定的時間,會影響垃圾收集的時間。

這個算法是吧內(nèi)存分配為兩個空間,一個空間(A)用來負責裝載正常的對象信息,,另外一個內(nèi)存空間(B)是垃圾回收用的。每次把空間A中存活的對象全部復制到空間B里面,在一次性的把空間A刪除。這個算法在效率上比標記-清除-壓縮高,但是需要兩塊空間,對內(nèi)存要求比較大,內(nèi)存的利用率比較低。適用于短生存期的對象,持續(xù)復制長生存期的對象則導致效率降低
由于現(xiàn)在的處理器都是多核的,處理器的性能得到了極大的提升,所以在此基礎上有產(chǎn)生了幾種垃圾收集算法。主要包括兩種算法
所謂并行,就是原來垃圾回收只是一個線程進行?,F(xiàn)在創(chuàng)建多個垃圾回收線程。并行的進行標記和清除。比如把需要標記的對象平均分配到多個線程之后,當標記完成之后,多個線程進行清除。
所謂并發(fā),就是應用程序和垃圾回收可以同時執(zhí)行。在標記清除算法中,在標記對象和清除對象,以及壓縮對象的情況下是需要暫停應用的。那么并行標記清除壓縮算法則是在標記清除壓縮算法的基礎上,把標記清除壓縮算法分為以下幾個過程
初始標記->并發(fā)標記->重新標記->并發(fā)清除->重置
以上幾種算法是垃圾回收的基本算法,jvm垃圾回收就是在以上幾種算法為基礎的,在以上幾種算法的基礎上,java垃圾回收器可以分為以下幾種:
用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,也無法使用多處理器的優(yōu)勢,所以此收集器適合單處理器機器
單線程收集器。在目前多核服務器端運行的情況下,效率比較低。比較適合堆內(nèi)存小的情況下使用。

用多線程處理所有垃圾回收工作,利用多核處理器的優(yōu)勢。但是如果線程數(shù)量過多,導致線程之間頻繁調(diào)度,也會影響性能。一半并行收集的線程是處理器的個數(shù)。
“對吞吐量有高要求”,多CPU、對應用響應時間無要求的中、大型應用。舉例:后臺處理、科學計算。

并發(fā)收集器主要減少年老代的暫停時間,他在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集初期并發(fā)收集器 會對整個應用進行簡短的暫停(初始標記的過程),在收集中還會再暫停一次。第二次暫停會比第一次稍長(重新標記的過程),在此過程中多個線程同時進行垃圾回收工作。
并發(fā)收集器使用處理器換來短暫的停頓時間。在一個N個處理器的系統(tǒng)上,并發(fā)收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。
在只有一個處理器的主機上使用并發(fā)收集器,設置為incremental mode模式也可獲得較短的停頓時間。
浮動垃圾:由于在應用運行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產(chǎn)生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。所以,并發(fā)收集器一般需要20%的預留空間用于這些浮動垃圾。
Concurrent Mode Failure:并發(fā)收集器在應用運行時進行收集,所以需要保證堆在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,堆空間先滿了。這種情況下將會發(fā)生“并發(fā)模式失敗”,此時整個應用將會暫停,進行垃圾回收。
并發(fā)收集器,在垃圾回收的時候采用并發(fā)標記清除算法的收集器
對響應時間要求高的,多CPU,大型應用。比如頁面請求/web服務器。前端業(yè)務系統(tǒng)用的比較多。

我們以sun公司的hotspot虛擬機為例,hotspot采用的是分代回收算法,因為根據(jù)經(jīng)驗,一般80%的對象就是朝生夕滅,對象的生命期都很短。而根據(jù)對象的生命周期不同,可以劃分出來幾個區(qū)域,一部分區(qū)域的對象創(chuàng)建比較頻繁,但是生命周期比較短,一部分區(qū)域?qū)ο笊芷诒容^長,還有一部分區(qū)域?qū)ο笊芷趲缀鹾蚸ava虛擬機相同。
sunspot堆內(nèi)存分為三個區(qū)域:

新生代包括兩個區(qū):Eden區(qū)和Survivor區(qū),其中Survivor區(qū)一般也分成兩塊,簡稱Survivor1 Space 和 Survivor2 Space (或者From Space 和 To Space)。新生代通常存活時間較短,因此基于標記清除復制算法來進行回收,掃描出存活的對象,并復制到一塊新的完全未使用的空間中,對應于新生代,就是在Eden和From或To之間copy。新生代采用空閑指針的方式來控制GC觸發(fā),指針保持最后一個分配的對象在新生代區(qū)間的位置,當有新的對象要分配內(nèi)存時,用于檢查空間是否足夠,不夠就觸發(fā)GC。當連續(xù)分配對象時,對象會逐漸從eden到Survior,最后到舊生代。
可以采用串行處理和并行處理器
在垃圾回收多次,如果對象仍然存活,并且新生代的空間不夠,則對象會存放在老年代。
在老年代采用的是 標記清除壓縮算法。因為老年代的對象一般存活時間比較長,每次標記清除之后,會有很多的零碎空間,這個就是所謂的浮動垃圾。當老年代的零碎空間不足以分配一個大的對象的時候,就會采用壓縮算法。在壓縮的時候,應用需要暫停。
可以采用串行處理,并行處理器,以及并發(fā)處理器。
這部分空間主要存放java方法區(qū)的數(shù)據(jù)以及啟動類加載器加載的對象。這一部分對象通常不會被回收。所以持久代空間在默認的情況下是不會被垃圾回收的。
由于把內(nèi)存空間分為三塊,一般把新生代的GC稱為minor GC ,把老年代的GC成為 full GC,所謂full gc 會先出發(fā)一次minor gc,然后在進行老年代的GC。
具體的過程如下:
首先想eden區(qū)申請分配空間,如果空間夠,就直接進行分配,否則進行一次Minor GC。minor GC 首先會對Eden區(qū)的對象進行標記,標記出來存活的對象。然后把存活的對象copy到From空間。如果From空間足夠,則回收eden區(qū)可回收的對象。如果from內(nèi)存空間不夠,則把From空間存活的對象復制到To區(qū),如果TO區(qū)的內(nèi)存空間也不夠的話,則把To區(qū)存活的對象復制到老年代。如果老年代空間也不夠(或者達到觸發(fā)老年年垃圾回收條件的話)則觸發(fā)一次full GC。
簡單方向就是Eden->From->To->Old,如下圖所示。

|