本系列為《深入理解Java虛擬機器 》(周志明著)讀書筆記。
垃圾收集器
JVM規範對於垃圾收集器的實現沒有任何規定,因此不同廠商、版本的虛擬機器所提供的垃圾收集器可能會有很大的差異。這裡我們的討論將基於Sun Hotspot虛擬機器1.6版Update22,此虛擬機器包含的記憶體回收行程如:
中共有HotSpot 1.6中共有7種記憶體回收行程,如果兩個記憶體回收行程之間有連線,說明二者可以搭配使用。下面我將對這些垃圾收集器一一進行介紹。
Serial收集器Serial是曆史最久的垃圾收集器之一,是一個單線程的收集器。它在進行垃圾收集時,必須暫停其他背景工作執行緒,直到它收集結束。Seiral收集器的記憶體回收過程是由虛擬機器在後台發起和完成的,在使用者不可見的情況下暫停所有背景工作執行緒,這將帶來惡劣的使用者體驗,其工作原理如所示:
Serial/Serial Old收集器運行Serial收集器儘管顯得“簡陋”並且使用者體驗不佳,但到1.6為止它仍然是虛擬機器運行在Client模式下的預設新生代收集器。這主要得益於它的簡單高效,並且在案頭應用中,分配給JVM管理的記憶體一般不會很大,收集幾十兆到一兩百兆的新生代,停頓時間完全可以控制住幾十毫秒最多一百多毫秒內,只要不是頻繁發生,這點兒卡頓是完全可以接受的。ParNew收集器ParNew收集器其實就是Serial收集器的多線程版本,除使用多線程收集職位,其餘的行為包括收集演算法、Stop the world、對象分配規則、回收策略等都與Serial收集器完全一樣,實際上這兩種收集器共用了很多代碼,ParNew收集器的工作原理如所示:
ParNew/Serial Old收集器運行ParNew是許多運行在Server模式下的虛擬機器中首選的新生代收集器,其中一個重要原因是,除Serial收集器外,只有它能與CMS收集器配合工作。CMS是HotSpot虛擬機器中第一款真正意義上的並發收集器,我們將在下面詳細討論CMS收集器。垃圾收集器中的並發與並行並行(Parallel):指多頭垃圾收集器線程並行工作,但此時使用者線程仍處於等待狀態。並發(Concurrent):指使用者線程與回收器線程同時工作。Parallel Scavenge收集器Parallel Scavenge與ParNew很類似,是一個使用複製演算法的新生代的並行多線程收集器。那麼它與ParNew的區別在哪裡?他們之間最大的區別是關注點不一樣,ParNew等收集器的關注點在於儘可能地縮短垃圾收集時使用者線程的停頓時間,而Parallel Scavenge收集器的目標是達到一個可控制的輸送量(Throughput)。輸送量指用於運行使用者代碼的CPU時間與總CPU時間的比值,即輸送量=運行使用者代碼時間/(運行使用者代碼時間 + 垃圾收集時間)。Parallel Scavenge收集器的另一個特點是可以使用自適應的調節策略。使用這種策略,虛擬機器會根據當前系統的運行情況收集效能監控資訊,動態調整新生代大小、Eden與Survivor區的比例、晉陞老年代對象年齡等細節參數。其工作過程如所示:
Serial Old收集器Serial Old是Serial收集器的老年代辦不,其工作過程如所示:
Serial/Serial Old收集器運行Parallel Old收集器Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記 - 整理”演算法,其工作過程如所示:CMS收集器CMS(Concurrent Mark Sweep)收集器是基於“標記 - 清除”演算法的並發收集器,其設計目標為擷取最短回收停頓時間。它的運作過程比上面介紹的收集器都要複雜一些,整個過程分為四個步驟,包括:
- 初始標記
- 並發標記
- 重新標記
- 並發清除
初始標記和重新標記仍需暫停所有使用者線程,即Stop the World,但初始標記只是標記GC Roots能直接關聯的對象,而重新標記則只是為了修正並發標記期間,因使用者程式繼續運行而產生變動那一部分對象,這個階段的停頓時間比前面介紹的Stop the World的時間要短得多。整個收集過程中耗時最久的並發標記和並發清除則和使用者線程一起工作,所以總地來講,CMS中GC線程是和使用者線程一起並發執行的。可以比較清楚地解釋這個過程:CMS收集器運行CMS是一款突破性的收集器,它極大地縮短了使用者線程停頓時間,可以認為其實現了並發記憶體回收,但金無足赤,人無完人,CMS還是具有這幾個缺陷:
- 對CPU資源非常敏感。幾乎所有的並行/並發系統都對CPU敏感。雖然它很少導致使用者卡頓,但是會因為佔用了一部分線程而導致應用程式變慢,總輸送量變低。
- 無法處理浮動垃圾(Floating Garbage),可能出現“Concurrent Mode Failure”失敗而導致另一次Full GC的產生。由於CMS並發清理階段使用者線程還在同時執行,因此此時這些線程產生的這部分垃圾CMS無法處理,只好留在下一次GC時再清理,這一部分垃圾就被稱為“浮動垃圾”。因為在垃圾收集階段使用者線程還在運行,因此CMS需要預留足夠的空間供這些線程使用,而不能向其他收集器那樣等老年代幾乎被完全充滿時再進行回收。預設CMS收集器在老年代使用68%之後就被啟用。
- 這個缺點來自於CMS所採用的“標記 - 清除”演算法。這種方式容易產生大量片段,當片段過多時,容易出現老年代空間有很大剩餘,但找不到連續空間進行分配給大對象,從而不得不提前觸發一次GC。
G1 收集器G1(Garbage First)收集器是當前收集器技術發展的最前沿成果,它與CMS相比會有兩個顯著改進:
- 採用“標記 - 整理”演算法,避免產生片段
- 可以精確地控制卡頓。這是通過讓使用指定一個參數來控制在一個長度為M的時間片內記憶體回收的時間N。
G1之所以可以在基本不犧牲輸送量的前提下完成記憶體回收,是因為它能夠盡量避免全地區的記憶體回收。之前的收集器是進行收集的範圍是整個新生代或老年代,而G1將整個Java堆(包括新生代、老年代)劃分為多個大小固定的獨立地區,並且跟蹤這些地區的堆積成都,在後台維護一個優先列表,每次根據優先順序從列表中挑選地區進行收集。