RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

一次JVM GC長暫停的排查過程

OSC開源社區(qū) ? 來源:OSCHINA 社區(qū) ? 2023-01-17 10:08 ? 次閱讀

作者 | 京東云開發(fā)者-京東科技徐傳樂

背景

在高并發(fā)下,Java 程序的 GC 問題屬于很典型的一類問題,帶來的影響往往會被進一步放大。不管是「GC 頻率過快」還是「GC 耗時太長」,由于 GC 期間都存在 Stop The World 問題,因此很容易導(dǎo)致服務(wù)超時,引發(fā)性能問題。

事情最初是線上某應(yīng)用垃圾收集出現(xiàn) Full GC 異常的現(xiàn)象,應(yīng)用中個別實例 Full GC 時間特別長,持續(xù)時間約為 15~30 秒,平均每 2 周左右觸發(fā)一次;

7abbfd5a-95bf-11ed-bfe3-dac502259ad0.png7b11a638-95bf-11ed-bfe3-dac502259ad0.png

JVM 參數(shù)配置 “-Xms2048M –Xmx2048M –Xmn1024M –XX:MaxPermSize=512M”


7b3e9b0c-95bf-11ed-bfe3-dac502259ad0.png


排查過程

?分析 GC 日志

GC 日志它記錄了每一次的 GC 的執(zhí)行時間和執(zhí)行結(jié)果,通過分析 GC 日志可以調(diào)優(yōu)堆設(shè)置和 GC 設(shè)置,或者改進應(yīng)用程序的對象分配模式。

這里 Full GC 的 reason 是 Ergonomics,是因為開啟了 UseAdaptiveSizePolicy,jvm 自己進行自適應(yīng)調(diào)整引發(fā)的 Full GC。

這份日志主要體現(xiàn) GC 前后的變化,目前為止看不出個所以然來。

7b6d5aa0-95bf-11ed-bfe3-dac502259ad0.png

開啟 GC 日志,需要添加如下 JVM 啟動參數(shù):


-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/export/log/risk_pillar/gc.log

常見的 Young GC、Full GC 日志含義如下:

7b9d230c-95bf-11ed-bfe3-dac502259ad0.png
?進一步查看服務(wù)器性能指標


獲取到了 GC 耗時的時間后,通過監(jiān)控平臺獲取到各個監(jiān)控項,開始排查這個時點有異常的指標,最終分析發(fā)現(xiàn),在 5.06 分左右(GC 的時點),CPU 占用顯著提升,而 SWAP 出現(xiàn)了釋放資源、memory 資源增長出現(xiàn)拐點的情況(詳見下圖紅色框,橙色框中的變化是因修改配置導(dǎo)致,后面會介紹,暫且可忽略)

7bbe1a26-95bf-11ed-bfe3-dac502259ad0.png
JVM 用到了swap?是因為 GC 導(dǎo)致的 CPU 突然飆升,并且釋放了 swap 交換區(qū)這部分內(nèi)存到 memory?


為了驗證 JVM 是否用到 swap,我們通過檢查 proc 下的進程內(nèi)存資源占用情況


| for i in $(cd /proc;ls |grep "^[0-9]"|awk ' $0 >100') ;do awk '/Swap:/{a=a+$2} END {print '"$i"',a/1024"M"}' /proc/$i/smaps 2>/dev/null ; done | sort -k2nr | head -10 # head -10 表示 取出 前 10 個內(nèi)存占用高的進程 # 取出的第一列為進程的 id 第二列進程占用 swap 大小 | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
看到確實有用到 305MB 的 swap

7be9a54c-95bf-11ed-bfe3-dac502259ad0.png
這里簡單介紹下什么是swap?


swap 指的是一個交換分區(qū)或文件,主要是在內(nèi)存使用存在壓力時,觸發(fā)內(nèi)存回收,這時可能會將部分內(nèi)存的數(shù)據(jù)交換到 swap 空間,以便讓系統(tǒng)不會因為內(nèi)存不夠用而導(dǎo)致 oom 或者更致命的情況出現(xiàn)。


當某進程向 OS 請求內(nèi)存發(fā)現(xiàn)不足時,OS 會把內(nèi)存中暫時不用的數(shù)據(jù)交換出去,放在 swap 分區(qū)中,這個過程稱為 swap out。


當某進程又需要這些數(shù)據(jù)且 OS 發(fā)現(xiàn)還有空閑物理內(nèi)存時,又會把 swap 分區(qū)中的數(shù)據(jù)交換回物理內(nèi)存中,這個過程稱為 swap in。


為了驗證 GC 耗時與 swap 操作有必然關(guān)系,我抽查了十幾臺機器,重點關(guān)注耗時長的 GC 日志,通過時間點確認到 GC 耗時的時間點與 swap 操作的時間點確實是一致的。




進一步查看虛擬機各實例 swappiness 參數(shù),一個普遍現(xiàn)象是,凡是發(fā)生較長 Full GC 的實例都配置了參數(shù) vm.swappiness = 30(值越大表示越傾向于使用 swap);而 GC 時間相對正常的實例配置參數(shù) vm.swappiness = 0(最大限度地降低使用 swap)。


swappiness 可以設(shè)置為 0 到 100 之間的值,它是 Linux 的一個內(nèi)核參數(shù),控制系統(tǒng)在進 行 swap 時,內(nèi)存使用的相對權(quán)重。


? swappiness=0: 表示最大限度使用物理內(nèi)存,然后才是 swap 空間


? swappiness=100: 表示積極的使用 swap 分區(qū),并且把內(nèi)存上的數(shù)據(jù)及時的交換到 swap 空間里面
7c183614-95bf-11ed-bfe3-dac502259ad0.png7c4e9128-95bf-11ed-bfe3-dac502259ad0.png
對應(yīng)的物理內(nèi)存使用率和 swap 使用情況如下

7c8a277e-95bf-11ed-bfe3-dac502259ad0.png7ca52cc2-95bf-11ed-bfe3-dac502259ad0.png
至此,矛頭似乎都指向了 swap。


?問題分析


當內(nèi)存使用率達到水位線 (vm.swappiness) 時,linux 會把一部分暫時不使用的內(nèi)存數(shù)據(jù)放到磁盤 swap 去,以便騰出更多可用內(nèi)存空間;


當需要使用位于 swap 區(qū)的數(shù)據(jù)時,再將其換回內(nèi)存中,當 JVM 進行 GC 時,需要對相應(yīng)堆分區(qū)的已用內(nèi)存進行遍歷;


假如 GC 的時候,有堆的一部分內(nèi)容被交換到 swap 空間中,遍歷到這部分的時候就需要將其交換回內(nèi)存,由于需要訪問磁盤,所以相比物理內(nèi)存,它的速度肯定慢的令人發(fā)指,GC 停頓的時間一定會非常非常恐怖;


進而導(dǎo)致 Linux 對 swap 分區(qū)的回收滯后(內(nèi)存到磁盤換入換出操作十分占用 CPU 與系統(tǒng) IO),在高并發(fā) / QPS 服務(wù)中,這種滯后帶來的結(jié)果是致命的 (STW)。


?問題解決


至此,答案似乎很清晰,我們只需嘗試把 swap 關(guān)閉或釋放掉,看看能否解決問題?


如何釋放 swap?

設(shè)置 vm.swappiness=0(重啟應(yīng)用釋放 swap 后生效),表示盡可能不使用交換內(nèi)存


a、 臨時設(shè)置方案,重啟后不生效


設(shè)置 vm.swappiness 為 0


sysctl vm.swappiness=0


查看 swappiness 值


cat /proc/sys/vm/swappiness

b、 永久設(shè)置方案,重啟后仍然生效

vi /etc/sysctl.conf


添加


vm.swappiness=0


關(guān)閉交換分區(qū) swapoff –a



前提:首先要保證內(nèi)存剩余要大于等于 swap 使用量,否則會報 Cannot allocate memory!swap 分區(qū)一旦釋放,所有存放在 swap 分區(qū)的文件都會轉(zhuǎn)存到物理內(nèi)存上,可能會引發(fā)系統(tǒng) IO 或者其他問題。

a、 查看當前 swap 分區(qū)掛載在哪?

7ccda1ca-95bf-11ed-bfe3-dac502259ad0.png

b、 關(guān)停分區(qū)

7ce1bc96-95bf-11ed-bfe3-dac502259ad0.png

關(guān)閉 swap 交換區(qū)后的內(nèi)存變化見下圖橙色框,此時 swap 分區(qū)的文件都轉(zhuǎn)存到了物理內(nèi)存上

7cfb46a2-95bf-11ed-bfe3-dac502259ad0.png

關(guān)閉 Swap 交換區(qū)后,于 2.23 再次發(fā)生 Full GC,耗時 190ms,問題得到解決。


7d20758a-95bf-11ed-bfe3-dac502259ad0.png
?疑惑

1、 是不是只要開啟了 swap 交換區(qū)的 JVM,在 GC 的時候都會耗時較長呢?



2、 既然 JVM 對 swap 如此不待見,為何 JVM 不明令禁止使用呢?


3、 swap 工作機制是怎樣的?這臺物理內(nèi)存為 8g 的 server,使用了交換區(qū)內(nèi)存(swap),說明物理內(nèi)存不夠使用了,但是通過 free 命令查看內(nèi)存使用情況,實際物理內(nèi)存似乎并沒有占用那么多,反而 Swap 已占近 1G?
7d3cc97e-95bf-11ed-bfe3-dac502259ad0.png

free:除了 buff/cache 剩余了多少內(nèi)存

shared:共享內(nèi)存

buff/cache:緩沖、緩存區(qū)內(nèi)存數(shù)(使用過高通常是程序頻繁存取文件)

available:真實剩余的可用內(nèi)存數(shù)



大家可以想想,關(guān)閉交換磁盤緩存意味著什么?

其實大可不必如此激進,要知道這個世界永遠不是非 0 即 1 的,大家都會或多或少選擇走在中間,不過有些偏向 0,有些偏向 1 而已。

很顯然,在 swap 這個問題上,JVM 可以選擇偏向盡量少用,從而降低 swap 影響,要降低 swap 影響有必要弄清楚 Linux 內(nèi)存回收是怎么工作的,這樣才能不遺漏任何可能的疑點。

先來看看 swap 是如何觸發(fā)的?

Linux 會在兩種場景下觸發(fā)內(nèi)存回收,一種是在內(nèi)存分配時發(fā)現(xiàn)沒有足夠空閑內(nèi)存時會立刻觸發(fā)內(nèi)存回收;另一種是開啟了一個守護進程(kswapd 進程)周期性對系統(tǒng)內(nèi)存進行檢查,在可用內(nèi)存降低到特定閾值之后主動觸發(fā)內(nèi)存回收。

通過如下圖示可以很容易理解,詳細信息參見。

7d4ec444-95bf-11ed-bfe3-dac502259ad0.png
解答是不是只要開啟了 swap 交換區(qū)的 JVM,在 GC 的時候都會耗時較長

筆者去查了一下另外的一個應(yīng)用,相關(guān)指標信息請見下圖。

實名服務(wù)的 QPS 是非常高的,同樣能看到應(yīng)用了 swap,GC 平均耗時 576ms,這是為什么呢?

7d738eaa-95bf-11ed-bfe3-dac502259ad0.png7da27b48-95bf-11ed-bfe3-dac502259ad0.png


通過把時間范圍聚焦到發(fā)生 GC 的某一時間段,從監(jiān)控指標圖可以看到 swapUsed 沒有任何變化,也就是說沒有 swap 活動,進而沒有影響到垃級回收的總耗時。 7db93964-95bf-11ed-bfe3-dac502259ad0.png7dd4bc48-95bf-11ed-bfe3-dac502259ad0.png


通過如下命令列舉出各進程 swap 空間占用情況,很清楚的看到實名這個服務(wù) swap 空間占用的較少(僅 54.2MB) 7dea19ee-95bf-11ed-bfe3-dac502259ad0.png

另一個顯著的現(xiàn)象是實名服務(wù) Full GC 間隔較短(幾個小時一次),而我的服務(wù)平均間隔 2 周一次 Full GC 7e0d545e-95bf-11ed-bfe3-dac502259ad0.png7e349d34-95bf-11ed-bfe3-dac502259ad0.png


基于以上推測

1、 實名服務(wù)由于 GC 間隔較短,內(nèi)存中的東西根本沒有機會置換到 swap 中就被回收了,GC 的時候不需要將 swap 分區(qū)中的數(shù)據(jù)交換回物理內(nèi)存中,完全基于內(nèi)存計算,所以要快很多
2、 將哪些內(nèi)存數(shù)據(jù)置換進 swap 交換區(qū)的篩選策略應(yīng)該是類似于 LRU 算法(最近最少使用原則)

為了證實上述猜測,我們只需跟蹤 swap 變更日志,監(jiān)控數(shù)據(jù)變化即可得到答案,這里采用一段 shell 腳本實現(xiàn)

#!/bin/bash 
echo -e `date +%y%m%d%H%M%S` 
echo -e "PID		Swap		Proc_Name" 

#拿出/proc目錄下所有以數(shù)字為名的目錄(進程名是數(shù)字才是進程,其他如sys,net等存放的是其他信息) 
for pid in `ls -l /proc | grep ^d | awk '{ print $9 }'| grep -v [^0-9]` 
do 
    if [ $pid -eq 1 ];then continue;fi 
    grep -q "Swap" /proc/$pid/smaps 2>/dev/null 
    if [ $? -eq 0 ];then 
        swap=$(gawk '/Swap/{ sum+=$2;} END{ print sum }' /proc/$pid/smaps) #統(tǒng)計占用的swap分區(qū)的 大小 單位是KB 
        proc_name=$(ps aux | grep -w "$pid" | awk '!/grep/{ for(i=11;i<=NF;i++){ printf("%s ",$i); }}') #取出進程的名字 
        if [ $swap -gt 0 ];then #判斷是否占用swap 只有占用才會輸出 
            echo -e "${pid}	${swap}	${proc_name100}" 
    fi 
   fi
done | sort -k2nr | head -10 | gawk -F'	' '{ #排序取前 10 
    pid[NR]=$1; 
    size[NR]=$2; 
    name[NR]=$3; 
} 
END{ 
    for(id=1;id<=length(pid);id++) 
    { 
    if(size[id]<1024) 
        printf("%-10s	%15sKB	%s
",pid[id],size[id],name[id]); 
    else if(size[id]<1048576) 
        printf("%-10s	%15.2fMB	%s
",pid[id],size[id]/1024,name[id]);
    else 
    printf("%-10s	%15.2fGB	%s
",pid[id],size[id]/1048576,name[id]); 
    } 
}'



由于上面圖中 2022.3.2 1900 至 2022.3.2 1900 發(fā)生了一次 Full GC,我們重點關(guān)注下這一分鐘內(nèi) swap 交換區(qū)的變化即可,我這里每 10s 做一次信息采集,可以看到在 GC 時點前后,swap 確實沒有變化 7e6fd58e-95bf-11ed-bfe3-dac502259ad0.png


通過上述分析,回歸本文核心問題上,現(xiàn)在看來我的處理方式過于激進了,其實也可以不用關(guān)閉 swap,通過適當降低堆大小,也是能夠解決問題的。

這也側(cè)面的說明,部署 Java 服務(wù)的 Linux 系統(tǒng),在內(nèi)存分配上并不是無腦大而全,需要綜合考慮不同場景下 JVM 對 Java 永久代 、Java 堆 (新生代和老年代)、線程棧、Java NIO 所使用內(nèi)存的需求。




總結(jié)



綜上,我們得出結(jié)論,swap 和 GC 同一時候發(fā)生會導(dǎo)致 GC 時間非常長,JVM 嚴重卡頓,極端的情況下會導(dǎo)致服務(wù)崩潰。


主要原因是:JVM 進行 GC 時,需要對對應(yīng)堆分區(qū)的已用內(nèi)存進行遍歷,假如 GC 的時候,有堆的一部分內(nèi)容被交換到 swap 中,遍歷到這部分的時候就須要將其交換回內(nèi)存;更極端情況同一時刻因為內(nèi)存空間不足,就需要把內(nèi)存中堆的另外一部分換到 SWAP 中去,于是在遍歷堆分區(qū)的過程中,會把整個堆分區(qū)輪流往 SWAP 寫一遍,導(dǎo)致 GC 時間超長。線上應(yīng)該限制 swap 區(qū)的大小,如果 swap 占用比例較高應(yīng)該進行排查和解決,適當?shù)臅r候可以通過降低堆大小,或者添加物理內(nèi)存。

因此,部署 Java 服務(wù)的 Linux 系統(tǒng),在內(nèi)存分配上要慎重。


以上內(nèi)容希望可以起到拋轉(zhuǎn)引玉的作用,如有理解不到位的地方煩請指出。

審核編輯:湯梓紅
聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學習之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • Linux
    +關(guān)注

    關(guān)注

    87

    文章

    11292

    瀏覽量

    209327
  • 服務(wù)器
    +關(guān)注

    關(guān)注

    12

    文章

    9123

    瀏覽量

    85324
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2966

    瀏覽量

    104702
  • JVM
    JVM
    +關(guān)注

    關(guān)注

    0

    文章

    158

    瀏覽量

    12220
  • 日志
    +關(guān)注

    關(guān)注

    0

    文章

    138

    瀏覽量

    10639

原文標題:一次JVM GC長暫停的排查過程

文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    波特率漂移導(dǎo)致通信異常的故障排查過程

    示波器的協(xié)議解碼功能大家都不生疏,你是否有過波形看起來正常,協(xié)議參數(shù)、解碼設(shè)置都正確,卻無法正常解碼的經(jīng)歷呢?本文以UART協(xié)議為例,分享由于波特率漂移導(dǎo)致通信異常的故障排查過程。
    的頭像 發(fā)表于 01-08 13:51 ?6401次閱讀
    波特率漂移導(dǎo)致通信異常的故障<b class='flag-5'>排查過程</b>

    labview如何做到一次觸發(fā)采集一次

    最近在做個電壓測試模塊,要求是在個時間段內(nèi),出現(xiàn)個上升沿觸發(fā)采集,并且只采集一次,采集次數(shù)為出現(xiàn)上升沿的次數(shù),采集時間,采樣率及單
    發(fā)表于 08-07 10:16

    請問單例對象會被jvmgc時回收嗎?

    單例對象會被jvmgc時回收嗎?
    發(fā)表于 11-09 07:02

    看看基于JDK中自帶JVM工具的用法

    查詢很多參數(shù)的默認值;該命令還可以在運行時動態(tài)調(diào)整部分參數(shù),只是很少被使用;3、jstat命令jstat :以指定的頻率輸出JVM的監(jiān)控指標,下述命令輸出內(nèi)存占用和GC相關(guān)信息,每隔3秒輸出一次,連續(xù)
    發(fā)表于 11-16 15:30

    Native Memory Tracking 詳解(4):使用 NMT 協(xié)助排查內(nèi)存問題案例

    ; diff 功能來觀察究竟是哪塊區(qū)域內(nèi)存直增長。比如在一次案例中發(fā)現(xiàn):Native Memory Tracking:Total: reserved=8149334KB +1535794KB
    發(fā)表于 11-24 14:19

    一次呼叫典型流程

    一次呼叫典型流程:一次呼叫典型流程B.1 概述本章將分別給出主叫流程和被叫流程的例子,以示一次通話過程中UTRAN的典型流程。B.2 主叫流程主叫流程是指UE呼叫其它用戶
    發(fā)表于 11-28 17:57 ?26次下載

    一次過程的等值電路

    一次過程的等值電路 圖 一次過程的等值電路 在電動機端子上安裝阻抗匹配器可
    發(fā)表于 07-18 11:24 ?1699次閱讀
    <b class='flag-5'>一次</b>波<b class='flag-5'>過程</b>的等值電路

    DC-DC電源故障排查過程和總結(jié),珍貴的經(jīng)驗!資料下載

    電子發(fā)燒友網(wǎng)為你提供DC-DC電源故障排查過程和總結(jié),珍貴的經(jīng)驗!資料下載的電子資料下載,更有其他相關(guān)的電路圖、源代碼、課件教程、中文資料、英文資料、參考設(shè)計、用戶指南、解決方案等資料,希望可以幫助到廣大的電子工程師們。
    發(fā)表于 04-25 08:54 ?75次下載
    DC-DC電源故障<b class='flag-5'>排查過程</b>和總結(jié),珍貴的經(jīng)驗!資料下載

    JVM CPU使用率飆高問題的排查分析過程

    問題現(xiàn)象 排查過程 問題現(xiàn)象 首先,我們起看看通過 VisualVM 監(jiān)控到的機器 CPU 使用率圖: 如上圖所示,在 下午3:45 分之前,CPU 的使用率明顯飆高,最高飆到近 100
    的頭像 發(fā)表于 10-10 16:31 ?2307次閱讀

    glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程

    本文記錄一次glibc導(dǎo)致的堆外內(nèi)存泄露的排查過程。
    的頭像 發(fā)表于 09-01 09:43 ?716次閱讀
    glibc導(dǎo)致的堆外內(nèi)存泄露的<b class='flag-5'>排查過程</b>

    JVM些重要參數(shù)

    篇內(nèi)容就只有滿滿的干貨,可以說是拿來即用。下面我們廢話少說,走起。 1.GC算法種類 目前OpenJDK中有以下幾種常見的GC算法。 Serial GC Parallel
    的頭像 發(fā)表于 09-25 15:56 ?457次閱讀

    jvm內(nèi)存溢出故障排查

    溢出故障排查的方法和步驟。 確認內(nèi)存溢出錯誤 首先,我們需要確認應(yīng)用程序是否確實發(fā)生了內(nèi)存溢出錯誤。內(nèi)存溢出通常會被JVM報告為OutOfMemoryError。這是個致命錯誤,暗示著JVM
    的頭像 發(fā)表于 12-05 11:04 ?815次閱讀

    記錄一次K8s pod被殺的排查過程

    今天下午運維反饋說我們這個pod天重啟了8,需要排查下原因。看Kiban日志,jvm沒有
    的頭像 發(fā)表于 01-18 09:57 ?742次閱讀

    記錄一次使用easypoi時與源碼博弈的過程

    、背景介紹 最近剛剛接手了保險線之聲平臺的開發(fā)和維護工作,第個需要修復(fù)的問題是:平臺的事件導(dǎo)出成excel功能在經(jīng)過一次上線之后突然不可用了,于是就開始了幾輪痛苦的
    的頭像 發(fā)表于 07-03 16:33 ?332次閱讀
    記錄<b class='flag-5'>一次</b>使用easypoi時與源碼博弈的<b class='flag-5'>過程</b>

    一次JSF異步調(diào)用引起的接口可用率降低

    前言 本文記錄了由于JSF異步調(diào)用超時引起的接口可用率降低問題的排查過程,主要介紹了排查思路和JSF異步調(diào)用的流程,希望可以幫助大家了解JSF的異步調(diào)用原理以及提供些問題排查思路。本
    的頭像 發(fā)表于 08-05 13:40 ?245次閱讀
    記<b class='flag-5'>一次</b>JSF異步調(diào)用引起的接口可用率降低
    RM新时代网站-首页