前言
很多人的博文裏面會提到JVM
三種垃圾收集器:串行,並行和併發。這麼分類也不能說錯,但事實上也沒有這麼簡單。本文簡要介紹Hotspot
早期的垃圾收集器(Garbage Collector
)。
名詞解釋
- 年輕代和老年代:
JVM
中不同的對象,生命週期是不同的。比如線程中的臨時變量,在方法結束之後便會被銷燬;而靜態變量,則會一直存在直到其所對應的類被銷燬爲止。基於這樣的事實,纔有了分代收集的概念:將JVM
堆分爲年輕代和老年代。年輕代存放剛被創建的對象,老年代存放經過多次GC
之後依然存活的對象。一般年輕代空間比較小,垃圾收集速度快,觸發次數頻繁,Minor GC
就發生在年輕代;老年代空間比較大,垃圾收集速度慢,觸發頻率低,Full GC
會包含老年代的回收。 - Stop-The-World(STW):垃圾收集器工作的時候,爲了防止與應用程序線程相互干擾,會暫停應用線程。所以對於程序來說,垃圾收集時就像時間被停止了一般。
- 標記-複製算法(copying):垃圾收集器的收集策略,將存活的對象標記出來,統一複製到一塊連續的內存之中。這種算法要求有一塊足夠大的空間可以放下所有存活的對象。
- 標記-清除算法(mark-sweep):垃圾收集器的收集策略,將存活的對象標記出來,然後清除掉死亡的對象。在連續的內存中清除掉一部分死亡對象之後,會導致內存碎片的產生。
- 標記-清除-壓縮算法(mark-sweep-compact):垃圾收集器的收集策略,在標記-清除算法之外,將存活的對象進行壓縮,可以想象成將對象往內存的一邊移動以防止內存碎片。
各收集器簡介
上面這張圖參考自Oracle
官博,每個小方框代表了一種收集器,可以看到,早期的收集器有六種,三個在年輕代,三個在老年代。收集器之間的連線表示了,它們之間是否可以一起工作。
- 老年代串行(Serial Old):單線程的收集器,採用標記-清除-壓縮算法,會
STW
。 - 老年代併發(CMS):從名字
Concurrent Mark Sweep
看出,它採用標記-清除算法,大部分時間和應用程序線程併發,極短的時間會STW
。 - 老年代並行(Parallel Old):多線程的收集器,採用標記-壓縮算法,會
STW
。 - 年輕代串行(Serial):單線程的收集器,採用標記-複製算法,會
STW
。 - 年輕代並行(Parallel Scavenge):多線程的收集器,採用標記-複製算法,會
STW
。 - 年輕代並行(ParNew):多線程的收集器,採用標記-複製算法,會
STW
。它與Parallel Scavenge
的區別是,對CMS
收集器做了兼容,如在適當的地方加了線程間的同步處理。
收集器與JVM參數
可以用JVM
啓動參數來顯示指定選擇何種收集器。
- -XX:+UseSerialGC:年輕代是串行(Serial),老年代是串行(Serial Old)。
- -XX:+UseParNewGC:年輕代是並行(ParNew),老年代是串行(Serial Old)。
- -XX:+UseConcMarkSweepGC:年輕代是並行(ParNew),老年代是併發(CMS)和串行(Serial Old)。
- -XX:+UseParallelGC:年輕代是並行(Parallel Scavenge),老年代是串行(Serial Old)。
- -XX:+UseParallelOldGC:年輕代是並行(Parallel Scavenge),老年代是並行(Parallel Old)。
注意:
- 老年代的串行收集器(Serial Old)可以和任意一個年輕代的收集器一起工作。
UseSerialGC
,UseParNewGC
和UseParallelGC
都只指定了年輕代的收集器,老年代默認還是用的串行收集器(Serial Old)。 - 爲什麼打開
-XX:+UseConcMarkSweepGC
之後,老年代會有兩種收集器?前面有說到,CMS
採用的標記-清除算法會產生內存碎片,所以當內存碎片化到一定程度之後可能導致無法繼續分配新的對象(從年輕代promote上來的對象),此時會觸發一次串行的Full GC
(帶壓縮),通常會導致應用程序長時間的停頓。至於爲什麼不退化成並行收集器(Parallel Old),只能說開發者偷懶了... -XX:+UseConcMarkSweepGC
和-XX:+UseSerialGC
不能一起使用,否則會報收集器衝突的錯。
總結
本文簡要介紹了所謂的串行(Serial),並行(Parallel)和併發(CMS)收集器,以及相應的啓用參數。因爲目前還有很多公司還在用JAVA8
甚至之前的版本,所以瞭解它們還是很有必要的。