JVM的很多參數命名很有迷惑性,-XX:+UseParallel,-XX:+UseParallelOldGC,-XX:+UseParNewGC,-XX:+UseConcMarkSweepGC咋一看容易混淆,而且JDK升個級某個GC就可能不見了,為了詳細了解這些參數的區別,先來看看到底都有哪些類型的GC:
// hotspot\share\gc\shared\gc_globals.hpp
product(bool, UseConcMarkSweepGC, false,
"Use Concurrent Mark-Sweep GC in the old generation")
product(bool, UseSerialGC, false,
"Use the Serial garbage collector")
product(bool, UseG1GC, false,
"Use the Garbage-First garbage collector")
product(bool, UseParallelGC, false,
"Use the Parallel Scavenge garbage collector")
product(bool, UseParallelOldGC, false,
"Use the Parallel Old garbage collector")
experimental(bool, UseEpsilonGC, false,
"Use the Epsilon (no-op) garbage collector")
experimental(bool, UseZGC, false,
"Use the Z garbage collector")
好消息是ParNewGC在JDK9中棄用了,JDK10中已經完全移除了,它的理想代替物是G1GC。然后openjdk12中現存的GC有:
- ConcMarkSweepGC
- SerialGC
- G1GC
- ParallelGC
- EpsilonGC
- ZGC
- ShenandoahGC ( OpenJDK12上游的新GC,我的源碼拉的早,就沒有它了)
它們在源碼中都有對應的獨立目錄:
λ tree .
├─gc
│ ├─cms # UseConcMarkSweepGC
│ ├─epsilon # UseEpsilonGC
│ ├─g1 # UseG1GC
│ ├─parallel # UseParallelGC && UseParallelOldGC
│ ├─serial # UseSerialGC
│ ├─shared # 所有GC共享的代碼
│ └─z # UseZGC
本文將要簡要分析Parallel GC和ParallelOld GC的區別。
要想找不同很簡單:對着源碼目錄搜索一下UseParallelGC/UseParallelOldGC標志,可以得到所有源碼的引用,而且找出來的結果通常是兩者伴隨出現的,看來方法是沒問題的。重點關注幾個地方,parallelArgument.cpp它會負責GC早期的參數處理(可以參見EpsilonGC示例):
// hotspot\share\gc\parallel\parallelArguments.cpp
void ParallelArguments::initialize() {
GCArguments::initialize();
assert(UseParallelGC || UseParallelOldGC, "Error");
// Enable ParallelOld unless it was explicitly disabled (cmd line or rc file).
if (FLAG_IS_DEFAULT(UseParallelOldGC)) {
FLAG_SET_DEFAULT(UseParallelOldGC, true);
}
FLAG_SET_DEFAULT(UseParallelGC, true);
...
}
這段代碼告訴我們,除非顯式指定-XX:-UseParallelOldGC,否則都開啟Parallel Old。第二個地方是GCConfiguration:
// hotspot\share\gc\shared\gcConfiguration.cpp
GCName GCConfiguration::young_collector() const {
if (UseG1GC) {
return G1New;
}
// 如果開啟UseParallelGC則新年代使用ParallelScavenge
if (UseParallelGC) {
return ParallelScavenge;
}
if (UseConcMarkSweepGC) {
return ParNew;
}
if (UseZGC) {
return NA;
}
return DefNew;
}
GCName GCConfiguration::old_collector() const {
if (UseG1GC) {
return G1Old;
}
if (UseConcMarkSweepGC) {
return ConcurrentMarkSweep;
}
// 如果開啟UseParallelOldGC則老年代使用ParallelOld,否則使用SerialOld
if (UseParallelOldGC) {
return ParallelOld;
}
if (UseZGC) {
return Z;
}
return SerialOld;
}
通過簡單的字符串搜索就能知道:
+UseParallelGC=新生代ParallelScavenge + 老年代ParallelOld+UseParallelOldGC= 同上-UseParallelOldGC=新生代ParallelScavenge + 老年代SerialOld
ParallelOld和SerialOld字面上意思是老年代並行處理和老年代串行處理,關於這兩個的區別也可以通過字符串搜索一窺究竟:
//hotspot\share\gc\parallel\parallelScavengeHeap.cpp
void ParallelScavengeHeap::do_full_collection(bool clear_all_soft_refs) {
if (UseParallelOldGC) {
bool maximum_compaction = clear_all_soft_refs;
// ParallelOld使用PSParallelCompact做full gc
PSParallelCompact::invoke(maximum_compaction);
} else {
// 關閉ParallelOld則使用PSMarkSweep做full gc
PSMarkSweepProxy::invoke(clear_all_soft_refs);
}
}
PSMarkSweepProxy是一個命名空間,它做的唯一一件事情就是把調用轉發到PSMarkSweep類的同名方法,比如PSMarkSweepProxy::do_a()實際調用的是PSMarkSweep::do_a()。PSMarkSweep和Serial GC Full GC提到的算法幾乎一樣,都是串行地分四個階段對老年代做標記-壓縮,稍有不同的是PSMarkSweep支持UseAdaptiveSizePolicy參數,它可以自適應的調整新生代和老年代的大小。
總的來說,Parallel GC和Parallel Old GC說的是不一樣的事情,前者表示並行分代式垃圾回收器,其老年代和新生代都是多線程並行操作。而后者只是老年代是否使用並行的一個選項(默認開啟),如果關閉則老年代退化為串行操作。之所以這樣是因為早期HotSpot的並行GC只支持新生代並行,老年代的並行是后面版本加入的。
最后,附上所有垃圾回收器名和對應的分代名:
| 垃圾回收器 | 新生代名 | 老年代名 |
|---|---|---|
| G1GC | G1New | G1Old |
| Parallel GC | ParallelScavenge | ParallelOld(-UseParallelOld則是SerialOld) |
| CMS | ParNew | ConcurrentMarkSweep |
| Serial GC | DefNew | SerialOld |
| Epsilon | N/A | N/A |
| ZGC | N/A | Z |
| Shenandoah | N/A | Shenandoah |
