JVM中的STW和CMS

 

Java中Stop-The-World機制簡稱STW,是在執行垃圾收集算法時,Java應用程序的其餘全部線程都被掛起(除了垃圾收集幫助器以外)。Java中一種全局暫停現象,全局停頓,全部Java代碼中止,native代碼能夠執行,但不能與JVM交互;這些現象多半是因爲gc引發。html

GC時的Stop the World(STW)是你們最大的敵人。但可能不少人還不清楚,除了GC,JVM下還會發生停頓現象。java

JVM裏有一條特殊的線程--VM Threads,專門用來執行一些特殊的VM Operation,好比分派GC,thread dump等,這些任務,都須要整個Heap,以及全部線程的狀態是靜止的,一致的才能進行。因此JVM引入了安全點(Safe Point)的概念,想辦法在須要進行VM Operation時,通知全部的線程進入一個靜止的安全點。算法

除了GC,其餘觸發安全點的VM Operation包括:安全

1. JIT相關,好比Code deoptimization, Flushing code cache ;多線程

2. Class redefinition (e.g. javaagent,AOP代碼植入的產生的instrumentation) ;併發

3. Biased lock revocation 取消偏向鎖 ;app

4. Various debug operation (e.g. thread dump or deadlock check);jvm

監控安全點看看JVM到底發生了什麼?高併發

最簡單的作法,在JVM啓動參數的GC參數裏,多加一句:工具

-XX:+PrintGCApplicationStoppedTime

它就會把所有的JVM停頓時間(不僅是GC),打印在GC日誌裏。

2016-08-22T00:19:49.559+0800: 219.140: Total time for which application threads were stopped: 0.0053630 seconds

這是個頗有用的必配參數,能夠打出幾乎一切的停頓……

可是,在JDK1.7.40之前的版本,它竟然沒有打印時間戳,因此只能知道JVM停了多久,但不知道何時停的。此時一個土辦法就是加多一句「 -XX:+PrintGCApplicationConcurrentTime」,打印JVM在兩次停頓之間的正常運行時間(一樣沒有時間戳),但好歹能配合有時間戳的GC日誌,反推出Stop發生的時間了。

2016-08-22T00:19:50.183+0800: 219.764: Application time: 5.6240430 seconds

如何打印出事哪一種緣由致使的停頓呢?

再多加兩個參數:-XX:+PrintSafepointStatistics -XX: PrintSafepointStatisticsCount=1

此時,在stdout中會打出相似的內容

vmop [threads: total initially_running wait_to_block]1913.425: GenCollectForAllocation [ 55 2 0 ] [time: spin block sync cleanup vmop] page_trap_count[ 0 0 0 0 6 ] 0

此日誌分兩段,第一段是時間戳,VM Operation的類型,以及線程概況

total: 安全點裏的總線程數 

initially_running: 安全點時開始時正在運行狀態的線程數 

wait_to_block: 在VM Operation開始前須要等待其暫停的線程數

第二行是到達安全點時的各個階段以及執行操做所花的時間,其中最重要的是vmop

spin: 等待線程響應

safepoint號召的時間 

block: 暫停全部線程所用的時間 

sync: 等於 spin+block,這是從開始到進入安全點所耗的時間,可用於判斷進入安全點耗時 

cleanup: 清理所用時間 

vmop: 真正執行VM Operation的時間

可見,那些不少但又很短的安全點,全都是RevokeBias,詳見 偏向鎖實現原理, 高併發的應用通常會乾脆在啓動參數里加一句"-XX:-UseBiasedLocking"取消掉它。另外還看到有些類型是no vm operation, 文檔上說是保證每秒都有一次進入安全點(若是這秒已經GC過就不用了),給一些須要在安全點裏進行,又非緊急的操做使用,好比一些採樣型的Profiler工具,可用-DGuaranteedSafepointInterval來調整,不過實際看它並非每秒都會發生,時間不定。

在實戰中,咱們利用安全點日誌,發現過有程序定時調用Thread Dump等等狀況。不過由於安全點日誌默認輸出到stdout,由於性能及stdout日誌的整潔性等緣由,咱們平時默認沒有開啓它。只有在須要時纔打開。

再再增長下面三個參數,能夠知道更多VM裏發生的事情。惋惜JVM不會由於設了這三個參數,就把安全點日誌轉移到vm.log裏面來,而是白白打印了兩次。

-XX:+UnlockDiagnosticVMOptions -XX:+LogVMOutput -XX:LogFile=/dev/shm/vm.log

總結

本文關於快速理解Java垃圾回收和jvm中的stw的介紹就到這裏,但願對你們有所幫助,感興趣的朋友能夠參閱:淺談Java回收對象的標記和對象的二次標記過程 、Java虛擬機裝載和初始化一個class類代碼解析 、Java中map遍歷方式的選擇問題詳解等,有什麼問題能夠隨時留言,小編會及時回覆你們的。

 

 

 

併發標記清除(CMS)垃圾收集器

 

 

 

CMS GC設計之初最根本的目的就是減少最大響應時間。

隨着愈來愈多的應用要求有一個垃圾收集器,它能比串行或並行垃圾收集器有更短的最壞狀況的中斷時間,犧牲一些應用的吞吐量來消除或極大地減小漫長的GC 中斷數量也是可以接受的,針對這種狀況,CMS 垃圾收集器被開發出來。

在CMS 垃圾收集器中,年輕代的垃圾收集與並行垃圾收集器很相似,它們是並行的並且會stop-the-world,也就是說在年輕代的垃圾收集過程當中全部的Java 應用線程都會被暫停,而垃圾收集工做會用多線程的方式來執行。須要注意的是,你能夠給CMS 垃圾收集器配置一個單線程模式的年輕代收集器,但在Java 8 中並不推薦這個方式,這個選項在Java 9 中被移除了。

並行垃圾收集器與CMS 垃圾收集器最主要的區別是在老年代的收集上。CMS 收集器的老年代收集活動試圖避免應用線程的長時間中斷。爲了實現這個目的,CMS 老年代收集器在應用線程執行的同時作了大部分工做(垃圾收集線程與應用線程同時工做),除了少許相對短的GC 同步暫停。一般來講,絕大多數狀況下CMS 是併發的,老年代收集的某些階段會暫停應用線程,好比初始標記和從新標記階段。在CMS 最初的實現中,初始標記和從新標記階段都是單線程的,但如今它們都已經被改成多線程的。激活多線程的初始標記和從新標記階段的HotSpot 命令行選項分別是-XX:+CMSParallelInitial Mark Enabled和-XX:CMSParallelRemarkEnabled,當經過命令行選項XX:+UseConcurrentMarkSweepGC 激活CMS 垃圾收集器時也會缺省自動激活這兩個選項。

有可能,或者說極有可能會在一個老年代併發收集正在進行的時候,又發生了一個年輕代收集。一旦發生這種狀況,老年代併發收集會被年輕代收集所中斷,直到後者結束以後馬上恢復執行。CMS GC 的缺省年輕代收集器被稱爲ParNew 收集器。

圖1.3 描述了因爲年輕代垃圾收集(黑色箭頭)、CMS 初始標記,從新標記階段以及老年代垃圾收集stop-the-world 階段,致使Java 應用線程被暫停。CMS 垃圾收集器的老年代收集活動從一個stop-the-world 的初始標記階段開始。一旦完成初始標記,就進入併發標記階段,在這個階段容許Java 應用線程和CMS 標記線程同時執行。圖1.3 中,在「標記/預清理」標籤下方,前兩個比較長的黑色箭頭就是併發標記線程。一旦併發標記完成,CMS線程就執行併發預清理,即圖中「標記/預清理」標籤下方兩個較短的黑色箭頭。須要注意的是,若是有足夠的可用硬件線程,CMS 線程的執行成本並不會對Java 應用線程的性能產生太大影響。但若是硬件線程是飽和的或被高度利用的,CMS 線程就會和Java 應用線程競爭CPU 週期。一旦併發預清理完成,stop-the-world 的從新標記階段就會開始。在從新標記階段會標記初始標記、併發標記以及併發預清理過程當中可能錯過的對象。當從新標記階段結束後,併發清除啓動,釋放全部死亡對象的空間。

使用CMS 垃圾收集器面臨的一個挑戰就是要在應用消耗完Java 的可用堆空間以前完成併發收集工做。所以對CMS 來講有個很棘手的部分,就是找到一個合適的時機來啓動這個併發工做。這種併發方式每每致使一個結果,就是處理同一個應用,CMS GC 會比並行GC 多佔用10%~20%的Java 堆空間。這也是爲了縮短垃圾收集暫停時間所付出的代價。

CMS 垃圾收集器的另外一個挑戰是如何處理老年代中的空間碎片,也就是當老年代中對象間的空間碎片過小,以致於沒法容納從年輕代晉升上來的對象,由於在CMS 的併發收集循環中並不執行壓縮,哪怕是增量或局部壓縮。一旦沒法找到可用空間,就會使CMS 回過來使用串行GC,觸發一次full 收集,致使一個漫長的暫停。伴隨CMS 碎片的另外一個很不幸的挑戰就是上述問題徹底沒法預測。一樣都是老年代碎片,某些應用可能沒有經歷過一次full GC,而有些可能時不時就要經歷一次。

對CMS 垃圾收集器作些調整,對應用作些優化改動,諸如避免生成大尺寸對象,會有助於延緩空間碎片的產生。固然,調優本就是個不尋常的工做,對專業能力有很高的要求,因此改動應用來避免碎片也是個不小的挑戰。

相關文章
相關標籤/搜索