線上記憶體溢位分析
系統變慢,開啟頁面變卡,使用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中工具所有功能,更直觀。還能夠線上
熱部署。