每週一次的例行發版,照常來臨。我熟悉的打開發版系統,同時也打開了服務監控系統,好讓別人看的出來我是一個對業務負責的小夥子。噸噸噸,噸噸噸,一頓操做...媽耶,這是咋回事,下游服務dubbo請求超時這麼多,看了看個人勞力土,再看了看監控,一分鐘400+的請求超時,請求超時數是原來的10倍,上升到了百的量級...java
故事發生在這週二的深夜。當時準備上一個需求,會致使服務中單個線程的IO變長,這個是預知的。在灰度中,發現了下游的服務dubbo請求超時數增長,腦海中立馬想到了去查了底層存儲的監控,發現底層存儲服務的999線都正常,憑藉着多年的經驗,腦海中一個聲音告訴我必定是服務GC出問題了。程序員
右邊的請求超時個數是以前的10倍(涉及到公司業務保密,相關監控數據打碼處理)而後看了服務的GC,果真單次gc的時間比以前增長了很多。面試
當時立馬找了下游的服務,告訴我這部分的超時是在範圍容許以內的,內心終於鬆了一口氣。因而繼續搞其餘的業務去了,雖然我故做淡定,其實當時內心已經慌的不行了。算法
這時候機智的你是否是說不行就擴容唄,但我是那麼容易妥協的人嗎?這不是考驗我內功的時候嗎?雖然我平時幹着CRUD的活,但哪個程序員不喜歡研究各類技術呢?這是全部程序員的浪漫!因而忙完了手頭上的活以後開始了gc調優。bash
-XX:+PrintGCDetails
參數,將每次的GC的耗時信息打印出來。當時的GC日誌:併發
%xwEx[GC pause (G1 Evacuation Pause) (young), 0.0962103 secs]
[Parallel Time: 23.3 ms, GC Workers: 4]
[GC Worker Start (ms): Min: 146441.2, Avg: 146441.3, Max: 146441.3, Diff: 0.1]
[Ext Root Scanning (ms): Min: 1.5, Avg: 1.8, Max: 2.4, Diff: 1.0, Sum: 7.2]
[Update RS (ms): Min: 1.0, Avg: 1.5, Max: 1.7, Diff: 0.6, Sum: 5.9]
[Processed Buffers: Min: 27, Avg: 34.8, Max: 41, Diff: 14, Sum: 139]
[Scan RS (ms): Min: 0.3, Avg: 0.3, Max: 0.3, Diff: 0.0, Sum: 1.3]
[Code Root Scanning (ms): Min: 0.0, Avg: 0.5, Max: 1.1, Diff: 1.1, Sum: 1.9]
[Object Copy (ms): Min: 18.3, Avg: 19.0, Max: 19.6, Diff: 1.2, Sum: 76.1]
[Termination (ms): Min: 0.0, Avg: 0.0, Max: 0.0, Diff: 0.0, Sum: 0.0]
[Termination Attempts: Min: 1, Avg: 1.8, Max: 3, Diff: 2, Sum: 7]
[GC Worker Other (ms): Min: 0.0, Avg: 0.1, Max: 0.1, Diff: 0.1, Sum: 0.3]
[GC Worker Total (ms): Min: 23.1, Avg: 23.2, Max: 23.2, Diff: 0.1, Sum: 92.7]
[GC Worker End (ms): Min: 146464.4, Avg: 146464.4, Max: 146464.5, Diff: 0.1]
[Code Root Fixup: 0.1 ms]
[Code Root Purge: 0.0 ms]
[Clear CT: 0.7 ms]
[Other: 72.1 ms]
[Choose CSet: 0.0 ms]
[Ref Proc: 69.9 ms]
[Ref Enq: 0.6 ms]
[Redirty Cards: 0.1 ms]
[Humongous Register: 0.1 ms]
[Humongous Reclaim: 0.0 ms]
[Free CSet: 0.8 ms]
[Eden: 4824.0M(4824.0M)->0.0B(4820.0M) Survivors: 88.0M->92.0M Heap: 5044.1M(8192.0M)->224.1M(8192.0M)]
[Times: user=0.17 sys=0.00, real=0.09 secs]
複製代碼
當時GC中基本都是young GC類型的日誌,所以不存在Mixed GC中對於old genaration回收所形成的gc時間增長。而後發現了Ref Proc
這一過程消耗的比較久,而後迅速的想到了並行開啓-XX:+ParallelRefProcEnabled
,而且調大了並行標記的線程-XX:ConcGCThreads
,以及增長了-XX:G1HeapRegionSize
,一套下來行雲流水,開始了枯燥的發版過程。果真一切都在個人預料之中:oracle
等等,我這就完了?固然不是,個人目的是想讓你們看了這篇以後就知道之後如何去優化高併發服務的gc問題,我敢說即便不少在一線大廠中打拼了不少年的老開發,對gc參數調優這塊也是隻知其一;不知其二,所以這部分的知識會了,能夠很容易在面試以及在工做中脫穎而出!我會在下面的篇幅中按部就班,由淺入深的講解關於虛擬機的GC。高併發
目前大多數的公司開發都是基於jdk8,主流使用的GC收集器主要是G1
,這裏拿CMS
和G1
進行簡單的對比,來突出G1
這款收集器是多麼的優秀!可能有的小公司使用的Parallel
收集器,可是Parallel
真的是一款毫無特點的收集器,用它跟G1
對比簡直差距太大了。這裏只先作對比,相關細節後續在詳解,這裏只是突出爲何大多數公司選擇G1
的緣由。佈局
所以經過對比發現,從業務的角度考慮,G1優勢不言而喻,分代收集,不容易產生內存碎片化,一次回收比較完全,不容易Full gc,併發標記,停頓時間相對可控(基於停頓時間和region佔用大小判斷是否有價值回收)。優化
那麼能夠這樣來理解整個GC過程,對於young gc而言,將整個young geenration的對象進行可達性分析,若是發現對象可達,則標記該對象是存活,若是不可達,則標記對象爲dead,而後將標記爲存活的對象拷貝到survivor regions或者old regions中,將dead objects所處的regions回收。
對於Mixed Gc而言,除了會進行上述的young GC中的對於整個young generation進行標記以及拷貝以外,還會對部分old generation中的old regions進行回收,回收的算法採用的是標記--整理,即儘量的釋放出連續的內存空間範圍。
youg-only phase
:將Young regions中的對象提高到Old,存到到Old regions中,而後回收young regions。space-reclamation phase
:常說的Mixed GC中的一個階段。不只對young regions進行回收,也會篩選有價值的Old regions進行回收。這兩個階段之間是能夠轉換的,當老年代的內存使用空間與總老年代空間的百分比超過了必定的閾值(後面篇幅中會講解),不只會對young generation進行回收,也會觸發對老年代的回收。
說白了就是超過這個閾值以後,會先觸發young-only phase
階段,而後young GC完了以後在進行space-reclamation phase
階段,對老年代進行回收;若是老年代的使用空間佔總得老年代空間沒有到達閾值,則不會觸發space-reclamation phase
,只會繼續開啓下一個youg-only phase
,進行循環。
youg-only phase
的停頓
initial Mark:標記GC Roots能直接關聯到的對象,停頓時間很短。標記以後會進入Concurrent Marking(不須要停頓),併發標記做用是肯定Old regions中的全部存活對象是否須要在接下來的space-reclamation phase
保留。初始標記這一過程可能尚未跑完,另外一個不包含初始標記的young gc過程可能就已經開始幹活了。初始標記並不必定發生在全部的young gc中,只有老年代的使用空間佔總老年代空間大小超過了InitiatingHeapOccupancyPercent,纔會觸發初始標記。
Remark:這一個停頓主要是爲了完成標記的過程。由於上一個過程是併發標記,是跟隨用戶線程一塊兒跑的,所以可能就會併發標記事後,一些對象隨着用戶線程的執行,可達性發生了變化,所以須要用戶線程停下來,去清理一些浮動垃圾。這一過程主要處理reference processing
和一些class的卸載,雖然停頓,可是能夠有多個標記線程。
CleanUp:也須要停頓。主要進行region的回收,且對空的region也會進行回收。而且肯定是否要接着進行space-reclamation phase
階段。若是須要進行space-recalmation phase
,則繼續進行,而後cleanUp部分young regions和old regions(有回收價值的old region)。若是肯定不須要接着進行space-reclamation phase
,則當前的cleanup階段就至關因而youg-only phase
的cleanUp,只是單純的表示對young generation的cleanUp已經完成了。
space-reclamation phase
的停頓
好比Colletions(能夠理解可能須要被回收的region的集合,而且一個GC週期中可能有多個這樣的集合)中的老年代object引用了新生代的object,那麼新生代對象所處的region就會把老年代的region記錄進新生代region維護的Remembered set中。所以space-reclamation phase
階段不只會對新生代object的region進行GC,也會分析新生代region背後的Remembered set,挑選出回收性價比高的一些old regions進行回收。
到此爲止,我已經把我知道的G1的回收機制都告訴你了,若是你對關於G1回收的算法感興趣,好比G1標記算法Snapshot-At-The-Beginning
等想進一步瞭解,能夠去oracle官網中查看,後續有時間,我也會出一期這樣的文章。接下來我會針對不一樣的GC場景進行分析,以及給出相關的建議。坐穩了,發車!
這是G1(大多數收集器也一樣)的一個兜底GC的階段。緣由主要是太多old generation的內存佔用致使,好比程序中使用了某一個模板解析引擎,可是沒有提取變量,致使每一次執行都是編譯一個新的class,這種狀況就很容易致使Full GC,或者有不少humongous objects的存在。若是程序中發生了Full gc,除了GC相關的調優,也須要多花時間去優化你的業務代碼。
直白一點就是由於建立了太多的object,致使g1不能及時的去回收。常見的場景是Concurrent Marking
沒有及時complete。
所以Full GC的優化思路主要分爲兩個方面:
Full GC調優Tips:
若是多是大對象太多形成的,能夠gc+heap=info
查看humongous regions個數。能夠增長經過-XX:G1HeapRegionSize
增長Region Size,避免老年代中的大對象佔用過多的內存。
增長heap大小,對應的效果G1能夠有更多的時間去完成Concurrent Marking
。
增長Concurrent Marking
的線程,經過-XX:ConcGCThreads
設置。
強制mark階段提前進行。由於在Mark階段以前,G1會根據應用程序以前的行爲,去肯定the Initiating Heap Occupancy Percent
(IHOP
)閾值大小,好比是否須要執行initial Mark
,以及後續CleanUp
階段的space-reclamation phase
;若是服務流量忽然增長或者其餘行爲改變的話,那麼基於以前的預測的閾值就會不許確,能夠採起下面的思路:
IHOP
分析過程當中的所須要的內存空間,經過-XX:G1ReservePercent
來設置,提升預測的效率。IHOP
分析機制,-XX:-G1UseAdaptiveIHOP
,而後手動的指定這個閾值大小,-XX:InitiatingHeapOccupancyPercent
。這樣就省去了每次預測的一個時間消耗。Full gc多是系統中的humongous object比較多,系統找不到一塊連續的regions區域來分配。能夠經過-XX:G1HeapRegionSize
增長region size,或者將整個heap調大。
gc日誌中能夠看Ref Proc
和Ref Enq
,Ref Proc
G1根據不一樣引用類型對象的要求去更新對應的referents;Ref Enq
G1若是實際引用對象已經不可達了,那麼就會將這些引用對象加入對應的引用隊列中。若是這一過程比較長,能夠考慮將這個過程開啓並行,經過-XX:+ParallelRefProcEnabled
。
young-only
回收較久主要緣由是Collection Set
中有太多的存活對象須要拷貝。能夠經過gc日誌中的Evacuate Collection Set
看到對應的時間,能夠增長young geenration的最小大小,經過-XX:G1NewSizePercent
。 也多是某一個瞬間,倖存下來的對象一會兒有不少,這種狀況會形成gc停頓時間猛漲,通常應對這種狀況經過-XX:G1MaxNewSizePercent
這個參數,增長young generation最大空間。
Mixed
回收時間較久經過開啓gc+ergo+cset=trace
,若是是predicated young regions
花費比較長,能夠針對上文中的方法。若是是predicated old regions
比較長,則能夠經過如下方法:
-XX:G1MixedGCCountTarget
這個參數,將old generation的regions分散到較多的Collection(上文有解釋)中,增長-XX:G1MixedGCCountTarget
參數值。避免單次處理較大塊的Collection。那麼如今回過頭去看我以前調整的參數,是否是明白了我調整了以後,服務的GC效率立馬提高了呢?其實調優的過程不是一蹴而就的,須要持續打磨,有了經驗以後,你看到以後想到的東西永遠比別人多!
手寫辛苦,歡迎各位點個贊留言,也順帶恭喜FPX和TES攜手共進決賽哈哈。