記一次java應用內存不斷增長問題解決過程

1、在解決問題之前,先看下jvm堆內存結構,如下圖所示:

對於Java應用,虛擬機管理的內存,可以參考如下圖所示:

一般對於一個應用來說,如果內存使用過大,可以從兩塊來分析,第一:堆內存,第二:堆外內存。下面着重從這兩個方面闡述下對最近遇到的內存問題進行分析並解決的過程。

問題詳情:

本人負責了一個應用,主要就是定時調度一批接口任務,同時提供一些對外接口,功能很簡單,在使用的過程中,逐漸發現了幾個內存問題。

1、oom問題,發現一段時間後,應用進程就沒了,查看日誌提示內存溢出,內存被喫光了,不夠分配下個資源使用的空間了。

2、第一個問題解決後,添加了幾個功能,發現又出現一些問題:應用運行一段時間後,發現,內存隨着時間不段推移,內存佔用也越來越多,一般4天后,內使用幾乎達到90%,很嚇人。

第一個問題解決過程:

oom問題,一般就是程序代碼處理不當造成的,有些資源沒有釋放或者一直在循環開闢某些內存資源。分析這個問題,我使用了本地jvm的一個工具,找到對應的jdk/bin/jvisualvm 工具,它是用來JVM監測、故障排除、分析工具,主要以圖形化界面的方式提供運行於指定虛擬機的Java應用程序的詳細信息。

1、本地啓動程序後,同時打開jvisualvm,在jvisualvm工具可視化頁面有多個選項tab,可以根據需求,選擇對應的tab進行使用,而我主要對profiler進行了使用,同時發現了問題,進而解決了問題,如下圖所示:

這裏排查問題,主要對快照功能進行了使用,每隔一段時間進行一次快照(每次快照前,先進行一次手動gc),然後兩次快照進行對比差值,進而發現內存不斷增長的是哪些對象。

如下圖所示,選擇第一張和最後一張對比圖:

通過以上方法,不斷去嘗試,一般幾個小時候,就能對比出哪些是一直佔用內存,且gc也不生效的對象,從而定位類名以及方法,去解決即可。下圖是解決後內存的使用情況:

運行個半個月是沒有問題的,後面也沒有出現oom。

第二個問題解決過程

第二個問題,不好定位,解決的時間比較長,需要長時間不斷觀察,纔會徹底解決。內存一直再漲,但是漲的速度並沒有很快,所以用第一種方法不大好分析,因爲本地數據並不多,不好分析。通過後面的分析結果,這裏涉及兩塊內存:堆內存,堆外內存。

這次內存不斷增長問題,並沒有導致應用不可用,出現oom等現象,純粹是自己隔段時間觀察的,下面是解決問題的過程。

1、jvm參數優化

應用運行一段時間,對應用java堆內存的使用進行觀察,這裏要清楚堆內存結構,通過命令:

jmap -heap pid,可以查看堆內存使用情況,比如:

排查內存問題,jvm優化也能解決一部分問題,所以我先通過優化,再進一步觀察,優化的原則,可以參考下圖:

通過jvm優化,在gc時間可接受的情況下,配置jvm參數,在觀察一段時間。

2、堆內存使用分析

通過jvm優化後,使用命令 jstat -gc pid,查看gc次數,發現gc時間相比之前高了一些,因爲應用對實時性要求不高,可以接受,xmx配置比之前多了一倍,應用運行時間長了很多,但是還是會發現,內存在漲的問題,自己繼續分析。

通過top命令發現,運行幾天後,res內存超過配置的xmx,隨着時間推移,res還是再漲。通過jmap -heap pid命令查看堆內存使用情況,發現堆內存佔用並不多,遠小於res值,說明這裏res的內存值多半是堆外內存在佔用,於是開始轉戰對堆外內存的分析。

3、堆外內存分析

在分析堆外內存時,在網上搜了很多。在分析堆外內存時,使用到了jcmd命令,Native Memory Tracking (NMT) 是Hotspot VM用來分析VM內部內存使用情況的一個功能。我們可以利用jcmd(jdk自帶)這個工具來訪問NMT的數據。

a、在使用jcmd之前,需要配置一些jvm參數,如下如所示:

即:-XX:NativeMemoryTracking=[off | summary | detail] 配置該jvm參數,重啓應用即可。

b、通過以上操作,使用命令 jcmd pid VM.native_memory detail scale=MB > a.txt 查看內存的一個使用情況,如下圖所示:

這裏包含了幾個部分,在分析這些內存的時候,需要搞明白啥意思,具體可以參考文章:

https://blog.csdn.net/Developlee/article/details/100691997

c、搞清楚每一項代表的含義,那接下來就是結合自己的代碼,分析哪塊可能有問題。對於我問題,通過 jcmd pid VM.native_memory baseline 設置基準線,耐心多次 jcmd pid VM.native_memory detail.diff scale=MB > a_diff.txt 看差值,然後得出有可能哪裏是問題所在,比如下面是差值的一個截圖,這是未解決問題前的一個比較,問題很明顯:

從這裏就可以看出,線程一直在增加,且資源一直未釋放,但是有哪些線程一直在創建申請資源呢,一時也摸不着頭緒,所以要知道哪些線程,需要進一步分析。

d、在分析以上問題時,需要查看dump文件,可以通過Jprofile 或者 eclipse mat來分析內存佔用情況。

通過jmap -dump pid > m.hprof 發現文件實在是太大,達到3g多,而且還佔用着內存,根本從linux上轉移不出來,也就無法通過本地idear插件Jprofile 查看,於是使用工具MemoryAnalyzer,根據系統屬性,官網下載後,解壓即可。

官網地址:http://www.eclipse.org/mat/downloads.php

下載後將包傳到linux服務器上解壓。

MemoryAnalyzer.ini 配置文件可以修改最大的內存,默認1G基本夠用了。

我在使用後,發現java不可用,所以在上面基礎上需要配置下面的兩部分:-vm -startup,下面是我後來添加的配置:

然後,在linux執行分析命令執行命令

./ParseHeapDump.sh m.hprof  org.eclipse.mat.api:suspects org.eclipse.mat.api:overview org.eclipse.mat.api:top_components。

m.hprof就是jvm的dump文件,在mat目錄下會生成3份.zip結尾的報告和一些m.相關的文件,將生成的m.hprof相關的文件都下載到本地磁盤:

1)使用瀏覽器

解壓縮以.zip結尾的文件,解壓後

使用瀏覽器打開index.html文件內容,查看分析報告,結合前面的分析,着重看下thread的使用情況如下圖:

異常thread1:

異常thread2:

 看到這裏,其實如果對代碼熟悉的話,立即知道哪裏出問題了,後面修改後,應用發佈,在繼續觀察。

e、按上面修改後,再次觀察內存使用,整體發現,會比之前好很多,但是通過jcmd再次對比差值,thread還是再增加,於是再重複d步驟的分析方法,其實這裏,如果本地有Jprofile的話,完全可以用該工具打開m.hprof,這次我用本地的Jprofile分析,如下圖所示,看到這裏,基本知道是啥問題了,修改代碼,再次發佈,繼續跟蹤。

d、經過以上幾個步驟,觀察內存使用情況,發現整體優化非常明顯,如下jcmd對比圖:

看到這裏,真實喜出望外,觀察幾天後,內存基本穩定了,目前運行良好。

內存問題分析路漫漫,希望這篇文章能夠幫助到大家O(∩_∩)O哈哈~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章