一些長時間GC停頓問題的排查及解決辦法

2021-02-20 佔小狼的博客

點擊上方「佔小狼的博客」,選擇「設為星標」

對於許多企業級應用,尤其是OLTP應用來說,長暫停很可能導致服務超時,而對這些運行在JVM上的應用來說,垃圾回收(GC)可能是長暫停最主要的原因。本文將描述一些可能碰到GC長暫停的不同場景,以及說明我們如何排查和解決這些GC停頓的問題。

下面是一些應用在運行時,可能導致GC長暫停的不同場景。

1. 碎片化

這個絕對要排在第一位。因為,正是因為碎片化問題--CMS最致命的缺陷,導致這個統治了OLAP系統十多年的垃圾回收器直接退出歷史舞臺(CMS已經是deprecated,未來版本會被移除,請珍惜那些配置了CMS的JVM吧),面對G1以及最新的ZGC,天生殘(碎)缺(片)的CMS毫無還手之力。

對於CMS,由於老年代的碎片化問題,在YGC時可能碰到晉升失敗(promotion failures,即使老年代還有足夠多有效的空間,但是仍然可能導致分配失敗,因為沒有足夠連續的空間),從而觸發Concurrent Mode Failure,發生會完全STW的FullGC。FullGC相比CMS這種併發模式的GC需要更長的停頓時間才能完成垃圾回收工作,這絕對是Java應用最大的災難之一。

為什麼CMS場景下會有碎片化問題?由於CMS在老年代回收時,採用的是標記清理(Mark-Sweep)算法,它在垃圾回收時並不會壓縮堆,日積月累,導致老年代的碎片化問題會越來越嚴重,直到發生單線程的Mark-Sweep-Compact GC,即FullGC,會完全STW。如果堆比較大的話,STW的時間可能需要好幾秒,甚至十多秒,幾十秒都有可能。

接下來的cms gc日誌,由於碎片率非常高,從而導致promotion failure,然後發生concurrent mode failure,觸發的FullGC總計花了17.1365396秒才完成:

{Heap before GC invocations=7430 (full 24):

parnew generation total 134400K, used 121348K[0x53000000, 0x5c600000, 0x5c600000)

eden space 115200K, 99% used [0x53000000, 0x5a07e738, 0x5a080000)

from space 19200K, 32% used [0x5a080000, 0x5a682cc0, 0x5b340000)

to space 19200K, 0% used [0x5b340000, 0x5b340000, 0x5c600000)

concurrent mark-sweep generation total 2099200K, used 1694466K [0x5c600000, 0xdc800000, 0xdc800000)

concurrent-mark-sweep perm gen total 409600K, used 186942K [0xdc800000, 0xf5800000, 0xfbc00000)

10628.167: [GC Before GC:

Statistics for BinaryTreeDictionary:

-

Total Free Space: 103224160

Max Chunk Size: 5486

Number of Blocks: 57345

Av. Block Size: 1800

Tree Height: 36 <---- High fragmentation

Statistics for IndexedFreeLists:

--

Total Free Space: 371324

Max Chunk Size: 254

Number of Blocks: 8591 <---- High fragmentation

Av. Block Size: 43

free=103595484

frag=1.0000 <---- High fragmentation

Before GC:

Statistics for BinaryTreeDictionary:

-

Total Free Space: 0

Max Chunk Size: 0

Number of Blocks: 0

Tree Height: 0

Statistics for IndexedFreeLists:

--

Total Free Space: 0

Max Chunk Size: 0

Number of Blocks: 0

free=0 frag=0.0000

10628.168: [ParNew (promotion failed) Desired survivor size 9830400 bytes, new threshold 1 (max 1)

- age 1: 4770440 bytes, 4770440 total: 121348K->122157K(134400K), 0.4263254secs]

10628,594: [CMS10630.887: [CMS-concurrent-mark: 7.286/8.682 secs] [Times: user=14.81, sys=0.34, real=8.68 secs]

(concurrent mode failure):1698044K->625427K(2099200K), 17.1365396 secs]

1815815K->625427K(2233600K), [CMS Perm : 186942K->180711K(409600K)]

After GC:

Statistics for BinaryTreeDictionary:

-

Total Free Space: 377269492

Max Chunk Size:

377269492

Number of Blocks: 1 <---- No fragmentation

Av. Block Size: 377269492

Tree Height: 1 <---- No fragmentation

Statistics for IndexedFreeLists:

--

Total Free Space: 0

Max Chunk Size: 0

Number of Blocks: 0

free=377269492

frag=0.0000 <---- No fragmentation

After GC:

Statistics for BinaryTreeDictionary:

-

Total Free Space: 0

Max Chunk Size: 0

Number of Blocks: 0

Tree Height: 0

Statistics for IndexedFreeLists:

--

Total Free Space: 0

Max Chunk Size: 0

Number of Blocks: 0

free=0 frag=0.0000

, 17.5645589 secs] [Times: user=17.82 sys=0.06, real=17.57 secs]

Heap after GC invocations=7431 (full 25):

parnew generation total 134400K, used 0K [0x53000000, 0x5c600000, 0x5c600000)

eden space 115200K, 0% used [0x53000000, 0x53000000, 0x5a080000)

from space 19200K, 0% used [0x5b340000, 0x5b340000, 0x5c600000)

to space 19200K, 0% used [0x5a080000, 0x5a080000, 0x5b340000)

concurrent mark-sweep generation total 2099200K, used 625427K [0x5c600000, 0xdc800000, 0xdc800000)

concurrent-mark-sweep perm gen total 409600K, used 180711K [0xdc800000, 0xf5800000, 0xfbc00000)

}

Total time for which application threads were stopped: 17.5730653 seconds

不知道怎麼看CMS的碎片率?請戳筆者另一篇文章吧:[如何了解CMS的垃圾碎片率:  如何了解CMS垃圾碎片率 ]

2. GC時作業系統的活動

當發生GC時,一些作業系統的活動,比如swap,可能導致GC停頓時間更長,這些停頓可能是幾秒,甚至幾十秒級別。

如果你的系統配置了允許使用swap空間,作業系統可能把JVM進程的非活動內存頁移到swap空間,從而釋放內存給當前活動進程(可能是作業系統上其他進程,取決於系統調度)。Swapping由於需要訪問磁碟,所以相比物理內存,它的速度的令人髮指。所以,如果在GC的時候,系統正好需要執行Swapping,那麼GC停頓的時間一定會非常非常非常恐怖。

下面是一段持續了29.48秒的YGC日誌:

{Heap before GC invocations=132 (full 0):

par new generation total 2696384K, used 2696384K [0xfffffffc20010000, 0xfffffffce0010000, 0xfffffffce0010000)

eden space 2247040K, 100% used [0xfffffffc20010000, 0xfffffffca9270000, 0xfffffffca9270000)

from space 449344K, 100% used [0xfffffffca9270000, 0xfffffffcc4940000, 0xfffffffcc4940000)

to space 449344K, 0% used [0xfffffffcc4940000, 0xfffffffcc4940000, 0xfffffffce0010000)

concurrent mark-sweep generation total 9437184K, used 1860619K [0xfffffffce0010000, 0xffffffff20010000, 0xffffffff20010000)

concurrent-mark-sweep perm gen total 1310720K, used 511451K [0xffffffff20010000, 0xffffffff70010000, 0xffffffff70010000)

2013-07-17T03:58:06.601-0700: 51522.120: [GC Before GC: :2696384K->449344K(2696384K), 29.4779282 secs] 4557003K->2326821K(12133568K) ,29.4795222 secs] [Times: user=915.56, sys=6.35, real=29.48 secs]

最後一行[Times: user=915.56, sys=6.35, real=29.48 secs]中real就是YGC時應用真實的停頓時間。

發生YGC的這個時間點,vmstat命令輸出結果如下:

r b w swap free re mf pi po fr de sr s0 s1 s2 s3 in sy cs us sy id

0 0 0 77611960 94847600 55 266 0 0 0 0 0 0 0 0 0 3041 2644 2431 44 8 48

0 0 0 76968296 94828816 79 324 0 18 18 0 0 0 0 1 0 3009 3642 2519 59 13 28

1 0 0 77316456 94816000 389 2848 0 7 7 0 0 0 0 2 0 40062 78231 61451 42 6 53

2 0 0 77577552 94798520 115 591 0 13 13 0 0 13 12 1 0 4991 8104 5413 2 0 98

YGC總計花了29秒才完成。vmstat命令輸出結果表示,可用swap空間在這個時間段減少了600m。這就意味著,在GC的時候,內存中的一些頁被移到了swap空間,這個內存頁不一定屬於JVM進程,可能是其他作業系統上的其他進程。

從上面可以看出,作業系統上可用物理內容不足以運行系統上所有的進程,解決辦法就是儘可能運行更少的進程,增加RAM從而提升系統的物理內存。在這個例子中,Old區有9G,但是只使用了1.8G(mark-sweep generation total 9437184K, used 1860619K)。我們可以適當的降低Old區的大小以及整個堆的大小,從而減少內存壓力,最小化系統上的應用發生swapping的可能。

除了swapping以外,我們也需要監控了解長GC暫停時的任何IO或者網絡活動情況等, 可以通過iostat和netstat兩個工具來實現. 我們還能通過mpstat查看CPU統計信息,從而弄清楚在GC的時候是否有足夠的CPU資源。

3. 堆空間不夠

如果應用程式需要的內存比我們執行的Xmx還要大,也會導致頻繁的垃圾回收,甚至OOM。由於堆空間不足,對象分配失敗,JVM就需要調用GC嘗試回收已經分配的空間,但是GC並不能釋放更多的空間,從而又回導致GC,進入惡性循環。

應用運行時,頻繁的FullGC會引起長時間停頓,在下面這個例子中,Perm空間幾乎是滿的,並且在Perm區嘗試分配內存也都失敗了,從而觸發FullGC:

166687.013: [Full GC [PSYoungGen:126501K->0K(922048K)] [PSOldGen: 2063794K->1598637K(2097152K)]2190295K->1598637K(3019200K) [PSPermGen: 165840K->164249K(166016K)],6.8204928 secs] [Times: user=6.80 sys=0.02, real=6.81 secs]

166699.015: [Full GC [PSYoungGen:125518K->0K(922048K)] [PSOldGen: 1763798K->1583621K(2097152K)]1889316K->1583621K(3019200K) [PSPermGen: 165868K->164849K(166016K)],4.8204928 secs] [Times: user=4.80 sys=0.02, real=4.81 secs]

同樣的,如果在老年代的空間不夠的話,也會導致頻繁FullGC,這類問題比較好辦,給足老年代和永久代,不要做太摳門的人了,嘿嘿。

4. JVM Bug

什麼軟體都有BUG,JVM也不例外。有時候,GC的長時間停頓就有可能是BUG引起的。例如,下面列舉的這些JVM的BUG,就可能導致Java應用在GC時長時間停頓。

6459113: CMS+ParNew: wildly different ParNew pause times depending on heap shape caused by allocation spread

fixed in JDK 6u1 and 7

6572569: CMS: consistently skewed work distribution indicated in (long) re-mark pauses

fixed in JDK 6u4 and 7

6631166: CMS: better heuristics when combatting fragmentation

fixed in JDK 6u21 and 7

6999988: CMS: Increased fragmentation leading to promotion failure after CR#6631166 got implemented

fixed in JDK 6u25 and 7

6683623: G1: use logarithmic BOT code such as used by other collectors

fixed in JDK 6u14 and 7

6976350: G1: deal with fragmentation while copying objects during GC

fixed in JDK 8

如果你的JDK正好是上面這些版本,強烈建議升級到更新BUG已經修復的版本。

5. 顯示System.gc調用

檢查是否有顯示的System.gc調用,應用中的一些類裡,或者第三方模塊中調用System.gc調用從而觸發STW的FullGC,也可能會引起非常長時間的停頓。如下GC日誌所示,Full GC後面的(System)表示它是由調用System.GC觸發的FullGC,並且耗時5.75秒:

164638.058: [Full GC (System) [PSYoungGen: 22789K->0K(992448K)]

[PSOldGen: 1645508K->1666990K(2097152K)] 1668298K->1666990K(3089600K)

[PSPermGen: 164914K->164914K(166720K)], 5.7499132 secs] [Times: user=5.69, sys=0.06, real=5.75 secs]

如果你使用了RMI,能觀察到固定時間間隔的FullGC,也是由於RMI的實現調用了System.gc。這個時間間隔可以通過系統屬性配置:

-Dsun.rmi.dgc.server.gcInterval=7200000

-Dsun.rmi.dgc.client.gcInterval=7200000

JDK 1.4.2和5.0的默認值是60000毫秒,即1分鐘;JDK6以及以後的版本,默認值是3600000毫秒,即1個小時。

如果你要關閉通過調用System.gc()觸發FullGC,配置JVM參數 -XX:+DisableExplicitGC即可。

那麼如何定位並解決這類問題問題呢?

配置JVM參數:-XX:+PrintGCDetails -XX:+PrintHeapAtGC -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps and -XX:+PrintGCApplicationStoppedTime. 如果是CMS,還需要添加-XX:PrintFLSStatistics=2,然後收集GC日誌。因為GC日誌能告訴我們GC頻率,是否長時間停頓等重要信息。

使用vmstat, iostat, netstat和mpstat等工具監控系統全方位健康狀況。

使用GCHisto工具可視化分析GC日誌,弄明白消耗了很長時間的GC,以及這些GC的出現是否有一定的規律。

嘗試從GC日誌中能否找出一下JVM堆碎片化的表徵。

監控指定應用的堆大小是否足夠。

檢查你運行的JVM版本,是否有與長時間停頓相關的BUG,然後升級到修復問題的最新JDK。

分享一份蠻不錯的《Java核心知識點整理.pdf》,覆蓋了JVM、鎖、高並發、反射、Spring原理、微服務、Zookeeper、資料庫、數據結構等等。

獲取方式:關注公眾號並回復 666 領取,更多內容陸續奉上,敬請期待。

 近期熱文:

相關焦點

  • 氨氮速測儀在使用過程會出現的問題和解決辦法
    6.試劑開封後禁止長時間放置,應儘快使用。7.試劑要按照儲藏要求存放,禁止將不同批次試劑混合使用。二、幹擾物質和解決辦法1.水樣中含有懸浮物、餘氯、鈣鎂等金屬離子。氨氮是不需要消解,可直接進行檢測的,所以操作流程相對於別的參數來說,要簡單一些,主要注意點為操作嚴格,量取準確即可。所有的化學試劑儘量不要與皮膚和口腔接觸,請操作的時候一定要加倍小心。
  • 關於進一步集中排查解決群眾舉報身邊環境汙染問題的通知
    為進一步解決群眾身邊的環境汙染問題,嚴厲打擊環境違法行為,切實維護群眾合法環境權益,根據省廳《關於進一步集中排查解決群眾舉報身邊環境汙染問題的通知》(,統一按照「解決問題以事發地為主、以戶籍地或常住地為輔,穩控工作以戶籍地常住地為主、以事發地為輔」的要求,明確問題的解決責任。
  • Windows主機入侵痕跡排查辦法
    為了確保實施人員在有限的時間範圍內,可以高效且保證質量的前提下完成主機入侵痕跡排查工作,本人總結了自己的一些經驗,下面的內容特此分享主機入侵痕跡排查服務中重點、關鍵的排查項,僅作為參考使用。 首先,在排查前,作為項目經理,應該與客戶溝通好,取得授權,確認排查範圍和排查方案和辦法,客戶若是沒有授意或者同意,那麼下面的操作都是違規操作,甚至有的還違法。 取得客戶同意後,我們再從資產面臨的風險等級、資產的重要程度、攻擊者的攻擊思路、手法及目標選擇傾向幾個方面去初步篩選出排查資產。
  • 雷射切割常見問題及解決辦法
    問題一:不鏽鋼拐角掛渣。解決辦法:拐角倒圓,降低焦點,加大氣壓。問題二:不鏽鋼整體掛硬渣。解決辦法:降低焦點、加大氣壓、加大割嘴;焦點不可過低,氣壓也不可過大,否則會導致斷面分層和面粗糙。問題三:切不鏽鋼整體掛顆粒狀軟渣。
  • JAVA系列-GC
    這樣能在一些場景中顯著提高性能,因為避免了在Java堆和Native堆中來回複製數據。UseG1GCjava version "13.0.2" 2020-01-14Java(TM) SE Runtime Environment (build 13.0.2+8)Java HotSpot(TM) 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)Java的gc
  • CAD正交快捷鍵F8失靈解決辦法
    有時,CAD或者其他設計軟體的某一快捷鍵突然失靈,大概有兩種原因:1,當前快捷鍵和其他軟體的快捷鍵相衝突,解決辦法就是找到相關軟體,排查衝突快捷鍵,修改或關閉,例如輸入法快捷鍵的排查另外一種情況就是未知原因
  • 生產腐植酸水溶肥常見問題及解決辦法
    現摘編中國腐植酸工業協會副秘書長、中國科學院瀋陽應用生態研究所高級工程師江志陽總結腐植酸水溶肥工藝和使用過程中常出現的問題及其解決辦法,分享給大家。 一、腐植酸固體水溶肥料常見3個問題及其解決辦法 1.吸潮結塊:成品貯藏一段時間後
  • 全國將重點排查治安問題
    本報北京2月29日電(記者李麗)中央社會治安綜合治理委員會近日決定,今年3月至9月,在全國範圍內集中組織開展排查調處矛盾糾紛、排查整治治安混亂地區和突出治安問題活動(簡稱「兩個排查」活動),集中力量解決好影響社會和諧穩定的突出問題。
  • 電腦屏幕閃爍的原因與解決辦法
    電腦屏幕閃爍四、顯示器硬體問題或電源問題以上情況遇到的比較少,遇到最多的是硬體問題,比如顯示器與主機連接的數據線不量等,大家都可以採用更換排除的方法解決,另外顯示器電源也只重點排查對象,在遇到的電腦屏幕抖動與電腦屏幕閃爍的故障中
  • 免疫組化常見問題及其解決辦法
    解決辦法是,每次使用新抗體前應當對其工作濃度進行測試,使每一抗體個體化,找到適合自己實驗室的理想工作濃度,既使是即用型的抗體也應如此,不能只簡單的按說明書進行染色。  (2)抗體孵育時間過長或溫度較高:解決辦法是,嚴格執行操作規程,最好隨身佩帶報時表或報時鐘,及時提醒,避免因遺忘而造成時間延長。
  • 光模塊在使用時會出現的常見問題及解決方案匯總
    導讀 光模塊作為較敏感的光學器件,在使用過程中有時候會出現一些問題,下面易天光通信(ETU-LINK)就給大家羅列一些光模塊在使用中比較常見的問題及解決的辦法,一起來學習一下吧
  • UG安裝錯誤所有問題解決辦法
    :這個問題沒有對症的藥方,就是說明你的許可證沒有啟動。 原因:出現以上問題的原因是裝了高版本的UG,再裝低版本的。會出現許可證問題。 解決方法:在卸載UG許可證出現這個問題的時候,不要點確定,直接Ctrl+Atl+Delete
  • 電燈關燈後常亮或發微光的原因及解決辦法
    電燈雖然電路結構簡單,但是發生的問題卻不少。故障發生頻率最高的,除了不亮以外,就是不滅了——開關斷開後,電燈正常發光或發微光。首先提醒一點,日光燈管關燈後發出微光,過一會兒就滅了,這不屬於故障。電燈故障原因,排查起來並不難。
  • 18650鋰電池充不進電怎麼辦,有什麼解決辦法(下)
    上篇文章講到了18650鋰電池充不進電的問題和解決方式,今天繼續分享鋰電池的修復原理。修復原理二:溫度比較低的時候,電池內部的電解液會有些許的變化,這會讓已冷凍的電池發生化學反應。鋰電池的使用過程是一個充放電過程。
  • 百戰程式設計師分享:Java中GC回收機制
    關於回收有幾個方法:System.gc(); Sysytem.getRuntime().gc();但調用這兩個方法並不能立刻進行垃圾回收。GC回收的過程在 Java的存在周期是自動的,不需要分配和釋放內存,同時也避免了內存洩漏。
  • Tengine問題排查必備
    本文分享Tengine中關於「時間」及「緩存」的一些問題,介紹問題發生的原因,並從用戶及服務端的角度嘗試分析這些問題。假如日誌欺騙了你,不要悲傷,看下這篇文章吧。一 前言在日常排查問題或者同用戶連調的時候,是否經常會遇到,在access_log裡看到用戶請求很快,但用戶卻反饋很慢?
  • 液相色譜儀常見問題及解決方案
    高效液相色譜儀出現氣泡峰該怎麼解決?氣泡峰是指攜帶氣泡的流動相通過檢測器時得到的響應值;這裡的氣泡可能是流動相中釋放的空氣,也可能是有機相遇水放熱而會釋放的有機溶劑分子;氣泡峰往往具有較高的響應值,區域寬度僅為數秒,出峰時間和相應值比較有規律(如下圖)。
  • 超聲波焊接機焊接產品表面出現壓痕問題的解決辦法
    打開APP 超聲波焊接機焊接產品表面出現壓痕問題的解決辦法 泰速爾 發表於 2021-01-13 14:34:46 在超聲波焊接作用中
  • 伺服器性能指標 負載(Load)分析及問題排查
    每個指標都有其獨特的意義,很多時候在線上出現問題時,往往會伴隨著某些指標的異常。大部分情況下,在問題發生之前,某些指標就會提前有異常顯示。對於這些指標的理解和查看、異常解決等,是程式設計師們重要的必備技能。本文,主要來介紹一下一個比較重要的指標——機器負載(Load),主要涉及負載的定義、查看負載方式、負載飆高排查思路等。