概述
Java中,垃圾收集 Garbage Collection 通常被稱爲“GC”,它誕生於1960年 MIT 的 Lisp 語言,經過半個多世紀,目前已經十分成熟了。
jvm 中,程序計數器、虛擬機棧、本地方法棧都是都是線程私有的,隨線程而生隨線程而滅,棧幀(棧中的對象)隨着方法的進入和退出做入棧和出棧操作,實現了自動的內存清理,因此,我們的內存垃圾回收主要集中於 Java 堆和方法區中,在程序運行期間,這部分內存的分配和使用都是動態的。
垃圾回收算法意義與價值
Java應用程序不用程序員手動管理內存中的垃圾回收,是因爲JVM有專門的垃圾回收線程做這件事。當內存不夠用時,會自動觸發回收。爲了在效率和內存碎片之間均衡,衍生出了一系列的垃圾回收算法。
對象存活判斷
jvm在進行回收資源時需要判斷當前對象是否存活,對於不存活對象進行回收處理,那麼是如何判斷對象存活呢?判斷對象存活有如下兩種方式:
-
引用計數
每個對象都有一個引用計數屬性,當新增一個引用時計數加1,引用釋放時計數減1,當計數爲0時就可以進行回收,此法簡單,無法解決對象相互循環引用的問題。
對象循環引用問題,即對象A引用對象B的,而在對象B中又引用了對象A,那麼對於對象A和對象B來說,其引用計數器都爲1,難以判斷其是否存活。
-
可達性分析(Reachability Analysis)
從GC的Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的,即爲不可達對象。
Java語言中GC Roots包含(虛擬機棧中引用的對象、方法區中類靜態屬性實體引用的對象、方法區中常量引用的對象、本地方法棧中JNI引用的對象),簡言之GC Roots包括棧中引用對象和方法區中引用對象。
垃圾收集算法
標記-清除算法(Mark-Sweep)
執行步驟
標記:遍歷整個內存區域,對需要回收的對象打上標記;
清除:再次遍歷內存,對標記過的內存進行回收;
圖示
適用場合
- 存活對象較多的情況下比較高效;
- 適用於年老代(即舊生代);
算法優缺點分析
優點如下:
- 執行過程比較簡單
缺點如下:
-
效率問題: 遍歷了兩次內存空間(第一次標記,第二次清除)。
-
空間問題: 容易產生大量內存碎片,當再需要一塊比較大的內存時,雖然總的可用內存是夠的,但是由於太過分散,無法找到一塊連續的且滿足分配要求的,因而不得不再次觸發一次GC。
複製算法(Copying)
將內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當一塊用完了,觸發GC時,將該塊中存活的對象複製到另一塊區域,然後一次性清理掉這塊沒有用的內存。下次觸發GC時將那塊中存活的的又複製到這塊,然後抹掉那塊,循環往復。
簡言之:從根集合節點進行掃描,標記出所有的存活對象,並將這些存活的對象複製到一塊兒新的等量大小內存上去,之後將原來的那一塊兒內存全部回收掉。
複製算法的高效性是建立在存活對象少、垃圾對象多的前提下的。
現在的商業虛擬機都採用這種收集算法來回收新生代
圖解
適用場合
- 存活對象較少的情況下比較高效;
- 掃描了整個空間一次(標記存活對象並複製移動);
- 適用於年輕代(即新生代):基本上98%的對象是”朝生夕死”的,存活下來的會很少
算法優缺點分析
優點如下:
- 相對於標記–清理算法解決了內存的碎片化問題,因爲複製的時候,會把存活的對象,聚攏在一起。
- 效率更高(清理內存時,記住首尾地址,一次性抹掉)
缺點如下:
- 需要一塊兒空置的內存空間;
- 需要複製移動對象;
標記-整理算法(Mark-Compact)
當對象的存活率比較高時,或者對象比較大時,用前面的複製算法這樣複製過來,複製過去,沒啥意義且浪費時間。所以針對老年代提出了“標記整理”算法。
執行步驟
- 標記:對需要回收的進行標記;
- 整理:讓存活的對象,向內存的一端移動,然後直接清理掉沒有用的內存;
圖解
適用場合
- 存活對象較多的情況下比較高效;
- 適用於年老代(即舊生代);
算法優缺點分析
優點如下:
- 克服了標記清除算法的內存碎片化的問題;
- 克服了複製算法的低效問題;
缺點如下:
- 效率問題: 遍歷了兩次內存空間;
分代收集算法
GC分代的基本假設:絕大部分對象的生命週期都非常短暫,存活時間短。
分代收集算法其實沒有什麼新東西,就是上面新生代和老年代根據對象不同的特點,採用不同的算法進行回收,取名爲分代收集,是一種劃分策略。
分代收集算法就是目前虛擬機使用的回收算法,它解決了標記整理不適用於老年代的問題,將內存分爲各個年代。一般情況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),在堆區之外還有一個代就是永久代(Permanet Generation),在jdk1.8後永久代合併到元空間(MetaSpace)
在不同年代使用不同的算法,從而使用最合適的算法,新生代存活率低,可以使用複製算法。而老年代對象存活率搞,沒有額外空間對它進行分配擔保,所以只能使用標記清除或者標記整理算法。
新生代與老年代關係
對象通過如下方式由年輕代晉升至老年代
-
提升
對象在多次垃圾回收後,依然存活,也就是多次從from->to 又從to->from 這樣多次,jvm認爲無需讓這樣的對象繼續這樣複製,因此將其晉升到老年代。
-
分配擔保
默認的Survivor只佔整個年輕代的10%,當從eden區複製到from / to的時候,存不下了,這個時候對象會被移動到老年代
-
大對象直接分配老年代
-
動態對象年齡判斷
當eden區中,某一年齡的對象已經佔用整個eden的一半了,那麼大於或者等於這一年齡的對象都會進入老年代。
垃圾回收算法總結
年輕代
複製算法適合年輕代。
-
所有新生成的對象首先都是放在年輕代的,年輕代的目標就是儘可能快速的收集掉那些生命週期短的對象。
-
新生代內存按照8:1:1的比例分爲一個eden區和兩個survivor(survivor0,survivor1)區。
一個Eden區,兩個Survivor區(一般而言)。大部分對象在Eden區中生成。回收時先將eden區存活對象複製到一個survivor0區,然後清空eden區,當這個survivor0區也存放滿了時,則將eden區和survivor0區存活對象複製到另一個survivor1區,然後清空eden和這個survivor0區,此時survivor0區是空的,然後將survivor0區和survivor1區交換,即保持survivor1區爲空, 如此往復。
-
當survivor1區不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。若是老年代也滿了就會觸發一次Full GC(Major GC),也就是新生代、老年代都進行回收。
-
新生代發生的GC也叫做Minor GC,MinorGC發生頻率比較高(不一定等Eden區滿了才觸發)。
老年代
標記-清除或標記-整理適合老年代。
- 在年輕代中經歷了N次垃圾回收後仍然存活的對象,就會被放到年老代中。因此,可以認爲年老代中存放的都是一些生命週期較長的對象。
- 內存比新生代也大很多(大概比例是1:2),當老年代內存滿時觸發Major GC即Full GC,Full GC發生頻率比較低,老年代對象存活時間比較長,存活率標記高。
分代收集
以上這種年輕代與年老代分別採用不同回收算法的方式稱爲”分代收集算法”,這也是當下企業使用的一種方式。
每一種算法都會有很多不同的垃圾回收器去實現,在實際使用中,根據自己的業務特點做出選擇就好
垃圾收集器
收集算法是jvm內存回收過程中具體的、通用的方法,垃圾收集器是jvm內存回收過程中具體的執行者,即各種GC算法的具體實現。
Serial收集器
串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。新生代、老年代使用串行回收;新生代複製算法、老年代標記-整理;垃圾收集的過程中會Stop The World(服務暫停)參數控制:-XX:+UseSerialGC 串行收集器。
圖解
ParNew收集器
ParNew收集器其實就是Serial收集器的多線程版本。**新生代並行,老年代串行;**新生代複製算法、老年代標記-整理。
參數控制:-XX:+UseParNewGC ParNew收集器,-XX:ParallelGCThreads 限制線程數量參數。
圖解
Parallel收集器
Parallel Scavenge收集器類似ParNew收集器,Parallel收集器更關注系統的吞吐量。可以通過參數來打開自適應調節策略,虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整這些參數以提供最合適的停頓時間或最大的吞吐量;也可以通過參數控制GC的時間不大於多少毫秒或者比例;新生代複製算法、老年代標記-整理。
參數控制:-XX:+UseParallelGC 使用Parallel收集器+ 老年代串行。
Parallel Old 收集器
Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法。這個收集器是在JDK 1.6中才開始提供
參數控制:-XX:+UseParallelOldGC使用Parallel收集器+ 老年代並行
CMS 收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。目前很大一部分的Java應用都集中在互聯網站或B/S系統的服務端上,這類應用尤其重視服務的響應速度,希望系統停頓時間最短,以給用戶帶來較好的體驗。
從名字(包含“Mark Sweep”)上就可以看出CMS收集器是基於“標記-清除”算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分爲4個步驟,包括:
- 初始標記(CMS initial mark)
- 併發標記(CMS concurrent mark)
- 重新標記(CMS remark)
- 併發清除(CMS concurrent sweep)
其中初始標記、重新標記這兩個步驟仍然需要“Stop The World”。初始標記僅僅只是標記一下GC Roots能直接關聯到的對象,速度很快,併發標記階段就是進行GC Roots Tracing的過程,而重新標記階段則是爲了修正併發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間一般會比初始標記階段稍長一些,但遠比並發標記的時間短.由於整個過程中耗時最長的併發標記和併發清除過程中,收集器線程都可以與用戶線程一起工作,所以總體上來說,CMS收集器的內存回收過程是與用戶線程一起併發地執行。老年代收集器(新生代使用ParNew)
優缺點分析
優點如下:
- 併發收集
- 低停頓
缺點如下:
- 產生大量內存碎片
- 併發階段會降低吞吐量
參數控制:
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:+ UseCMSCompactAtFullCollection Full GC後,進行一次碎片整理;整理過程是獨佔的,會引起停頓時間變長
-XX:+CMSFullGCsBeforeCompaction 設置進行幾次Full GC後,進行一次碎片整理
-XX:ParallelCMSThreads 設定CMS的線程數量(一般情況約等於可用CPU數量)
圖解
G1 收集器
G1是目前技術發展的最前沿成果之一,HotSpot開發團隊賦予它的使命是未來可以替換掉JDK1.5中發佈的CMS收集器。與CMS收集器相比G1收集器有以下特點:
- 空間整合,G1收集器採用標記整理算法,不會產生內存空間碎片。分配大對象時不會因爲無法找到連續空間而提前觸發下一次GC。
- 可預測停頓,這是G1的另一大優勢,降低停頓時間是G1和CMS的共同關注點,但G1除了追求低停頓外,還能建立可預測的停頓時間模型,能讓使用者明確指定在一個長度爲N毫秒的時間片段內,消耗在垃圾收集上的時間不得超過N毫秒,這幾乎已經是實時Java(RTSJ)的垃圾收集器的特徵了。
上面提到的垃圾收集器,收集的範圍都是整個年輕代或者老年代,而G1不再是這樣。使用G1收集器時,Java堆的內存佈局與其他收集器有很大差別,它將整個Java堆劃分爲多個大小相等的獨立區域(Region),雖然還保留有新生代和老年代的概念,但新生代和老年代不再是物理隔閡了,它們都是一部分(可以不連續)Region的集合。
圖解
G1的新生代收集跟ParNew類似,當新生代佔用達到一定比例的時候,開始出發收集。和CMS類似,G1收集器收集老年代對象會有短暫停頓。
執行步驟
-
標記階段,首先初始標記(Initial-Mark),這個階段是停頓的(Stop the World Event),並且會觸發一次普通Mintor GC。對應GC log:GC pause (young) (inital-mark)
-
Root Region Scanning,程序運行過程中會回收survivor區(存活到老年代),這一過程必須在young GC之前完成。
-
Concurrent Marking,在整個堆中進行併發標記(和應用程序併發執行),此過程可能被young GC中斷。在併發標記階段,若發現區域對象中的所有對象都是垃圾,那個這個區域會被立即回收(圖中打X)。同時,併發標記過程中,會計算每個區域的對象活性(區域中存活對象的比例)。
-
Remark, 再標記,會有短暫停頓(Stop the World Event)。再標記階段是用來收集 併發標記階段 產生新的垃圾(併發階段和應用程序一同運行);G1中採用了比CMS更快的初始快照算法:snapshot-at-the-beginning (SATB)。
-
Copy/Clean up,多線程清除失活對象,會有STW。G1將回收區域的存活對象拷貝到新區域,清除Remember Sets,併發清空回收區域並把它返回到空閒區域鏈表中。
-
複製/清除過程後。回收區域的活性對象已經被集中回收到深藍色和深綠色區域.
ZGC收集器
在JDK 11中即將迎來ZGC(The Z Garbage Collector),這是一個處於實驗階段的,可擴展的低延遲垃圾回收器.
ZGC特點
- 併發
- 基於Region的
- 標記整理
- NUMA感知
- 使用colored oops
- 使用load barrier
- 僅root掃描時STW,因此GC暫停時間不會隨堆的大小而增加。
描述
ZGC的核心原則是將load barrier與colored oops結合使用。這使得ZGC能夠在Java應用程序線程運行時執行併發操作,例如對象遷移。
從Java線程的角度來看,在Java對象中加載引用字段的行爲受到load barrier的影響。除了對象地址之外,colored oops還包含load barrier使用的信息,以確定在允許Java線程使用指針之前是否需要採取某些操作。 例如,對象可能已遷移,在這種情況下,load barrier將檢測情況並採取適當的操作。
與其他替代技術相比,colored oops提供瞭如下非常有吸引力的特性:
- 它允許ZGC在對象遷移和整理階段回收和重用內存。這有助於降低一般堆開銷。這也意味着不需要爲Full GC實現一個單獨的標記整理算法。
- 目前在colored oops中僅存儲標記和對象遷移相關信息。然而,這種方案的通用性使我們能夠存儲任何類型的信息(只要我們可以將它放入指針中)並讓load barrier根據該信息採取它想要的任何動作。比如,在異構內存環境中,這可以用於跟蹤堆訪問模式,以指導GC對象遷移策略,將很少使用的對象移動到冷存儲。
ZGC併發執行如下任務:
- 標記
- 引用處置
- relocation集選擇
- 遷移和整理
侷限性
-
當前版本不支持類卸載
-
當前版本不支持JVMCI
JVMCI是JDK 9 引入的JVM編譯器接口。這個接口允許用Java編寫的編譯器被JVM用作動態編譯器。JVMCI的API提供了訪問VM結構、安裝編譯代碼和插入JVM編譯系統的機制。現有支持Java編譯器的項目主要是 Graal 和 Metropolis 。
工作機制與原理
指針標記
在64位系統上,引用是64位的,引用結構如下:
+-------------------+-+----+-----------------------------------------------+
|00000000 00000000 0|0|1111|11 11111111 11111111 11111111 11111111 11111111|
+-------------------+-+----+-----------------------------------------------+
| | | |
| | | * 41-0 Object Offset (42-bits, 4TB address space)
| | |
| | * 45-42 Metadata Bits (4-bits) 0001 = Marked0 (Address view 4-8TB)
| | 0010 = Marked1 (Address view 8-12TB)
| | 0100 = Remapped (Address view 16-20TB)
| | 1000 = Finalizable (Address view N/A)
| |
| * 46-46 Unused (1-bit, always zero)
|
* 63-47 Fixed (17-bits, always zero)
如上表所示, ZGC使用41-0存儲對象實際地址的前42位, 42位地址爲應用程序提供了理論4TB的堆空間; 45-42位爲metadata比特位, 對應於如下狀態: finalizable,remapped,marked1和marked0; 46位爲保留位,固定爲0; 63-47位固定爲0。
在引用中添加元數據, 使得解除引用的代價更加高昂, 因爲需要操作掩碼以獲取真實的地址, ZGC採用了一種有意思的技巧, 讀操作時是精確知道metadata值的, 而分配空間時, ZGC映射同一頁到3個不同的地址,而在任一時間點,這3個地址中只有一個正在使用中。
具體代碼實現如下:
void ZPhysicalMemoryBacking::map(ZPhysicalMemory pmem, uintptr_t offset) const {
if (ZUnmapBadViews) {
// Only map the good view, for debugging only
map_view(pmem, ZAddress::good(offset), AlwaysPreTouch);
} else {
// Map all views
map_view(pmem, ZAddress::marked0(offset), AlwaysPreTouch);
map_view(pmem, ZAddress::marked1(offset), AlwaysPreTouch);
map_view(pmem, ZAddress::remapped(offset), AlwaysPreTouch);
}
}
void ZPhysicalMemoryBacking::unmap(ZPhysicalMemory pmem, uintptr_t offset) const {
if (ZUnmapBadViews) {
// Only map the good view, for debugging only
unmap_view(pmem, ZAddress::good(offset));
} else {
// Unmap all views
unmap_view(pmem, ZAddress::marked0(offset));
unmap_view(pmem, ZAddress::marked1(offset));
unmap_view(pmem, ZAddress::remapped(offset));
}
}
採用此法後,ZGC堆空間如下結構
// Address Space & Pointer Layout
// ------------------------------
//
// +--------------------------------+ 0x00007FFFFFFFFFFF (127TB)
// . .
// . .
// . .
// +--------------------------------+ 0x0000140000000000 (20TB)
// | Remapped View |
// +--------------------------------+ 0x0000100000000000 (16TB)
// | (Reserved, but unused) |
// +--------------------------------+ 0x00000c0000000000 (12TB)
// | Marked1 View |
// +--------------------------------+ 0x0000080000000000 (8TB)
// | Marked0 View |
// +--------------------------------+ 0x0000040000000000 (4TB)
// . .
// +--------------------------------+ 0x0000000000000000
由此產生副作用,ZGC無法兼容指針壓縮。
分頁
在G1中,堆內存通常被分爲幾千個大小相同region。同樣的,在ZGC中堆內存也被分成大量的區域,它們被稱爲page,不同的是,ZGC中page的大小是不同的。 ZGC有3種不同的頁面類型:小型(2MB大小),中型(32MB大小)和大型(2MB的倍數)。 在小頁面中分配小對象(最大256KB大小),在中間頁面中分配中型對象(最多4MB)。大頁面中分配大於4MB的對象。大頁面只能存儲一個對象,與小頁面或中間頁面相對應。有些令人困惑的大頁面實際上可能小於中等頁面(例如,對於大小爲6MB的大對象)。 這種分配方式有點類似操作系統的內存分配方式。
標記整理
主要分爲10個步驟,具體參考如下代碼實現過程
void ZDriver::run_gc_cycle(GCCause::Cause cause) {
ZDriverCycleScope scope(cause);
// Phase 1: Pause Mark Start
{
ZMarkStartClosure cl;
vm_operation(&cl);
}
// Phase 2: Concurrent Mark
{
ZStatTimer timer(ZPhaseConcurrentMark);
ZHeap::heap()->mark();
}
// Phase 3: Pause Mark End
{
ZMarkEndClosure cl;
while (!vm_operation(&cl)) {
// Phase 3.5: Concurrent Mark Continue
ZStatTimer timer(ZPhaseConcurrentMarkContinue);
ZHeap::heap()->mark();
}
}
// Phase 4: Concurrent Reference Processing
{
ZStatTimer timer(ZPhaseConcurrentReferencesProcessing);
ZHeap::heap()->process_and_enqueue_references();
}
// Phase 5: Concurrent Reset Relocation Set
{
ZStatTimer timer(ZPhaseConcurrentResetRelocationSet);
ZHeap::heap()->reset_relocation_set();
}
// Phase 6: Concurrent Destroy Detached Pages
{
ZStatTimer timer(ZPhaseConcurrentDestroyDetachedPages);
ZHeap::heap()->destroy_detached_pages();
}
// Phase 7: Concurrent Select Relocation Set
{
ZStatTimer timer(ZPhaseConcurrentSelectRelocationSet);
ZHeap::heap()->select_relocation_set();
}
// Phase 8: Prepare Relocation Set
{
ZStatTimer timer(ZPhaseConcurrentPrepareRelocationSet);
ZHeap::heap()->prepare_relocation_set();
}
// Phase 9: Pause Relocate Start
{
ZRelocateStartClosure cl;
vm_operation(&cl);
}
// Phase 10: Concurrent Relocate
{
ZStatTimer timer(ZPhaseConcurrentRelocated);
ZHeap::heap()->relocate();
}
}
ZGC包含10個階段,但是主要是兩個階段標記和relocating。 GC循環從標記階段開始,遞歸標記所有可達對象,標記階段結束時,ZGC可以知道哪些對象仍然存在,哪些是垃圾。ZGC將結果存儲在每一頁的位圖(稱爲live map)中。
在標記階段,應用線程中的load barrier將未標記的引用壓入線程本地的標記緩衝區。一旦緩衝區滿,GC線程會拿到緩衝區的所有權,並且遞歸遍歷此緩衝區所有可達對象。注意:應用線程負責壓入緩衝區,GC線程負責遞歸遍歷。
標記階段後,ZGC需要遷移relocate集中的所有對象。relocate集是一組頁面集合,包含了根據某些標準(例如那些包含最多垃圾對象的頁面)確定的需要遷移的頁面。對象由GC線程或者應用線程遷移(通過load barrier)。ZGC爲每個relocate集中的頁面分配了轉發表。轉發表是一個哈希映射,它存儲一個對象已被遷移到的地址(如果該對象已經被遷移)。
GC線程遍歷relocate集的活動對象,並遷移尚未遷移的所有對象。有時候會發生應用線程和GC線程同時試圖遷移同一個對象,在這種情況下,ZGC使用CAS操作來確定勝利者。
一旦GC線程完成了relocate集的處理,遷移階段就完成了。雖然這時所有對象都已遷移,但是舊地引用址仍然有可能被使用,仍然需要通過轉發表重新映射(remapping)。然後通過load barrier或者等到下一個標記循環修復這些引用。
這也解釋了爲什麼對象引用中有兩個標記位(marked0和marked1)。標記階段交替使用在marked0和marked1位。
load barrier
它的比較容易和CPU的內存屏障(memory barrier)弄混淆,但是它們是完全不同的東西。
從堆中讀取引用時,ZGC需要一個所謂的load barrier(也稱爲read-barrier)。每次Java程序訪問對象字段時,ZGC都會執行load barrier的代碼邏輯,例如obj.field。訪問原始類型的字段不需要屏障,例如obj.anInt或obj.anDouble。ZGC不使用存儲/寫入障礙obj.field = someValue
。
常用收集器組合搭配
年輕代GC策略 | 老年代GC策略 | 說明 |
---|---|---|
組合1 | Serial | Serial Old |
組合2 | Serial | CMS+Serial Old |
組合3 | ParNew | CMS |
組合4 | ParNew | Serial Old |
組合5 | Parallel Scavenge | Serial Old |
組合6 | Parallel Scavenge | Parallel Old |
組合7 | G1GC | G1GC |
垃圾收集器問題延伸
STW爲什麼週期短?
僅root掃描時STW,其他標記、清理、遷移階段,均通過colored oops和load-barrier配合使用,併發執行。
JVM全局停頓
Java中Stop-The-World機制簡稱STW,是在執行垃圾收集算法時,Java應用程序的其他所有線程都被掛起(除了垃圾收集幫助器之外)。Java中一種全局暫停現象,全局停頓,所有Java代碼停止,native代碼可以執行,但不能與JVM交互;這些現象多半是由於gc引起.
JVM裏有一條特殊的線程VM Threads,專門用來執行一些特殊的VM Operation,比如分派GC,thread dump等,這些任務,都需要整個Heap,以及所有線程的狀態是靜止的,一致的才能進行。所以JVM引入了安全點(Safe Point)的概念,想辦法在需要進行VM Operation時,通知所有的線程進入一個靜止的安全點。
JVM觸發安全點操作包含哪些?
- GC
- JIT相關,比如Code deoptimization, Flushing code cache ;
- Class redefinition (javaagent,AOP代碼植入的產生的instrumentation等) ;
- Biased lock revocation 取消偏向鎖
- Various debug operation(hread dump or deadlock check等)
參數控制:-XX:+PrintGCApplicationStoppedTime
查看何種原因導致停頓 參數控制:-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1