選單

線上記憶體溢位分析

線上記憶體溢位分析

系統變慢,開啟頁面變卡,使用top命令檢視cpu和記憶體情況後,發現在使用系統時,CPU 飆高。

針對此問題,展開排查。

排查產生原因

首先,使用 arthas 工具或者使用 jstack 檢視 java 執行中的執行緒狀態,檢視哪些執行緒佔用cpu過高,在使用時,發現為gc執行緒。

然後,使用 jstat 或者 arthas 中的 dashboard 命令 進行確認,發現堆中新生代老年代空間已滿,每過十幾秒就在進行一次full gc。所以,可以分析出CPU飆高的原因為記憶體溢位。

記憶體溢位原因分析

因為要進行記憶體溢位情況,需要拿出堆轉儲資訊,所以,在無人使用系統時,使用 jmap 命令獲取堆轉儲資訊:

jmap -dump:format=b,file=heapdump。hprof pid

需要注意的是,使用 jmap 時,會使 JVM 處於 STW(stop the world) 狀態,所以儘量不要在生產上有人使用時使用該命令,否則會導致非常長的時間停頓。

在拿到堆轉儲資訊後,使用MAT(Memory Analyzer Tool)工具進行分析,裝載檔案後,得到如下圖中的內容:

線上記憶體溢位分析

發現DefaultSVNRepositoryPoll引出的物件佔用了3。6個G的空間(整個JVM分配空間為4個G),所以可以確定,是這個類生產 RunnableSecheduleFuture 物件的問題。

對 DefaultSVNRepositoryPool 進行分析,找問題

在前面已經分析出是DefaultSVNRepositoryPool引出的記憶體溢位,所以直接在IDEA中搜索到這個類,進入到該類,檢視是否有相關 RunnableSecheduleFuture 方法。

首先確認哪裡引用到了該類

一切還要從SVN上傳檔案說起,SVN上傳檔案有幾個步驟:

//1。 判斷路徑是否存在;SVNClientManipulator rp = new SVNClientManipulator(user);//2。 得到版本庫;SVNRepository rps = rp。getRepository(svnDao。getSvnUrlByRepository(repository) + repository + “/” + newPath);//3。 判斷是否存在;SVNNodeKind nodeKind = rps。checkPath(“”, -1);if (nodeKind == SVNNodeKind。NONE) {//如果存在則更新路徑; svnConfigDevelopService。addDir(svnDao。getSvnUrlByRepository(repository) + repository + “/” + newPath, desc, rp, true);}importFolder = newPath;// 4。 上傳檔案String msg = svnCommonService。addFileToSVN(svnFilePath, gdFileName, repository, “/” + importFolder + “/”, user, desc);

其中對獲取SVNClientManipulator時,其實是獲取一個client客戶端,整個過程可以用一個簡單的sequence圖來說明:

線上記憶體溢位分析

然後對DefaultSVNRepositoryPool內部排查

在引用到的 DefaultSVNRepositoryPool 的構造方法內,可以看到

線上記憶體溢位分析

這裡有兩個Timer,作為全域性的變數,可以看到作用是從10秒開始,每10秒執行一次 TimeOutTask() 方法。而這裡的 myScheduledTimeoutTask 正是 ScheduledFuture 型別的。

如果不確認該類是否為溢位的物件,可以再進入到scheduleWithFixedDelay中。可以看到返回的正是

RunnableScheduledFuture

物件。

線上記憶體溢位分析

此時就可以由剛才的seq圖往回推

最終問題有可能出現在在 svnCommonService 中建立了過多的 SVNClientManipulator 。

SVN上傳程式碼排查

是否為上述分析的原因,需要在svnCommonService程式碼中確認。

對現有的SVN上傳程式碼過程分析

svnFileJsonArray 將json解析為array,迴圈每個陣列中的內容,進行檔案上傳。

首先

SVNClientManipulator的建立發生在迴圈內

,也就是說一次請求上傳多個檔案,則會建立多個客戶端。此處為導致問題的主要原因。

其次,在程式碼內沒有發現同步區域,而且大部分內容是需要查詢表、插入表,在高併發的情況下可能會出現 mysql 的 lock wait 異常,最終導致檔案無法上傳的錯誤。

最後,現有因為程式碼是一個請求建立一個執行緒,n次請求就產生n個執行緒,這在高請求量的情況下出現問題的機率非常大。

基於以上三點進行程式碼上的修改

問題程式碼修改

針對

SVNClientManipulator的建立發生在迴圈內

將json中取出一人或者直接傳進一個處理人,建立client,減少client的建立次數

針對一次請求建立一個執行緒,修改為使用執行緒池,預設執行緒池的coreSize為8,核心數*2

針對同步程式碼塊,使用sycronized進行同步程式碼塊。

進階修改:

上述修改後,可以滿足過程,但是實際上大部分程式碼會使用sycronized進行包裹,同時進行的執行緒只有一個,所以提前建立多執行緒是佔用記憶體的時間的。

線上記憶體溢位分析

最終修改為:

使用單執行緒處理請求,單執行緒處理業務程式碼,避免了加鎖和解鎖的消耗

僅僅在上傳檔案時,使用執行緒池進行檔案上傳

插曲:為什麼DefaultSVNRepositoryPool沒有回收掉

這裡涉及到客戶端連線SVN的狀態,預設連線狀態時 keepAlive 的,但是不需要手動進行連線的關閉。

一個客戶端啟動一個全域性的Timer,這個Timer每10秒檢測一下是否可以關閉連線,可以關閉連線的條件是 這個連線在 60 秒內沒有再次訪問過SVN。

但是,因為task是需要執行緒進行執行的,當建立非常多的pool時,timer可能取不到CPU時間片來執行task,所以就在一直等待,導致鏈上的所有物件,虛擬機器都無法進行回收,最終導致記憶體溢位。

線上記憶體溢位分析

其他

一、JVM調優引數

線上分為三臺機器,而應用佔其中一臺,總記憶體為16g,針對此環境修改JAVA_OPTS

JAVA_OPTS=“-server -Xms8192m -Xmx8192m -XX:PermSize=1024M -XX:MaxPermSize=1024M -Duser。language=zh -Djava。util。Arrays。useLegacyMergeSort=true -Djava。awt。headless=true -Xloggc:/app/okit/java/gc-%t。log -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=50M -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:HeapDumpPath=。/java_dump。hprof -XX:+HeapDumpOnOutOfMemoryError”

針對其中引數含義解釋:

-XX:+UseGCLogFileRotation GCLog檔案輸出

-XX:NumberOfGCLogFiles=5 GCLog檔案數量

-XX:GCLogFileSize=20M GCLog檔案大小

-XX:+PrintGCTimeStamps 列印GC耗時

-XX:+PrintGCDetails 列印GC回收的細節

-XX:HeapDumpPath=./java_pid.hprof :堆記憶體快照的儲存檔案路徑。檔名一般為java__heapDump.hprof。

-XX:+HeapDumpOnOutOfMemoryError 在OOM時,自動輸出一個dump檔案

二、JDK自帶工具使用

jps

JPS(Java Virtual Machine Process Status Tool),可以顯示進行中的Java執行緒。

使用方式:jps [options] [hostid]

jstat -gc

jstat(Java Virtual Machine statistics monitoring tool),能夠檢視JVM的使用情況

使用方式:jstat [ generalOption | outputOptions vmid [ interval [ s|ms ] [ count ] ] ]

如: jstat -gc -h3 31736 1000 10

jstack

jstack(Java stack trace)是Java的堆疊分析工具。

兩個功能:

針對活著的程序做本地的或遠端的執行緒dump;

針對core檔案做執行緒dump。

使用方式:jstack [ option ] pid

可將堆疊輸出到指定檔案中:jstack -l PID >> jstack。out

jmap

jmap(Java memory map),它可以生成 java 程式的 dump 檔案, 也可以檢視堆內物件示例的統計資訊、檢視 ClassLoader 的資訊以及 finalizer 佇列。

jmap -dump:format=b,file=heapdump。hprof pid

JVisualVM

用來監測JVM記憶體和執行緒使用情況,可以遠端連線

三、其他分析工具

MAT

用來分析堆轉儲資訊,能夠分析記憶體溢位問題

Arthas

可以實現JDK中工具所有功能,更直觀。還能夠線上

熱部署。