內(nèi)存泄漏排查攻略之:Show me your Memory
java 語言有個神奇的地方,那就是你時不時會去關(guān)注下內(nèi)存。(當然了,任何牛逼的同學都應該關(guān)注內(nèi)存)
今天我們就來這么個問題現(xiàn)場吧:某應用運行了一段時間后,ecs監(jiān)控報警了,內(nèi)存比較高了,怎么辦?隨著時間的推移,發(fā)現(xiàn)內(nèi)存越來越高緩緩增長,怎么辦?
凡事講究證據(jù),報警系統(tǒng)說內(nèi)存緊張就緊張嗎,還得自己去驗一下。
如何確認內(nèi)存問題?這太重要了!其實只要給我看你內(nèi)存里所有東西(Show me your Memory),我豈有找不到問題根源的道理?以下是幾種查看內(nèi)存問題的方法供諸君參考:
1:top 等查看系統(tǒng)內(nèi)存概況
top:顯示所有進程運行情況,按M鍵按照內(nèi)存大小排序,立馬看到罪魁禍首。具體命令請參考網(wǎng)上資料。
top簡要使用方法如下:
使用格式:top [-] [d] [p] [q] [c] [C] [S] [s] [n]參數(shù)說明:d:指定每兩次屏幕信息刷新之間的時間間隔。當然用戶可以使用s交互命令來改變之。p:通過指定監(jiān)控進程ID來僅僅監(jiān)控某個進程的狀態(tài)。q:該選項將使top沒有任何延遲的進行刷新。如果調(diào)用程序有超級用戶權(quán)限,那么top將以盡可能高的優(yōu)先級運行。S:指定累計模式。s:使top命令在安全模式中運行。這將去除交互命令所帶來的潛在危險。i:使top不顯示任何閑置或者僵死進程。c:顯示整個命令行而不只是顯示命令名。常用命令說明:Ctrl+L:擦除并且重寫屏幕K:終止一個進程。系統(tǒng)將提示用戶輸入需要終止的進程PID,以及需要發(fā)送給該進程什么樣的信號。一般的終止進程可以使用15信號;如果不能正常結(jié)束那就使用信號9強制結(jié)束該進程。默認值是信號15。在安全模式中此命令被屏蔽。i:忽略閑置和僵死進程。這是一個開關(guān)式命令。q:退出程序r:重新安排一個進程的優(yōu)先級別。系統(tǒng)提示用戶輸入需要改變的進程PID以及需要設置的進程優(yōu)先級值。輸入一個正值將使優(yōu)先級降低,反之則可以使該進程擁有更高的優(yōu)先權(quán)。默認值是10。S:切換到累計模式。s:改變兩次刷新之間的延遲時間。系統(tǒng)將提示用戶輸入新的時間,單位為s。如果有小數(shù),就換算成m s。輸入0值則系統(tǒng)將不斷刷新,默認值是5 s。需要注意的是如果設置太小的時間,很可能會引起不斷刷新,從而根本來不及看清顯示的情況,而且系統(tǒng)負載也會大大增加。f或者F:從當前顯示中添加或者刪除項目。o或者O:改變顯示項目的順序l:切換顯示平均負載和啟動時間信息。m:切換顯示內(nèi)存信息。t:切換顯示進程和CPU狀態(tài)信息。c:切換顯示命令名稱和完整命令行。M:根據(jù)駐留內(nèi)存大小進行排序。P:根據(jù)CPU使用百分比大小進行排序。T:根據(jù)時間/累計時間進行排序。W:將當前設置寫入~/.toprc文件中。
另外在內(nèi)存查看方面,還可以使用 free用于快速直接查看內(nèi)存,還可以看到有多少是系統(tǒng)緩存;(系統(tǒng)緩存一般不被計入真正已使用內(nèi)存中)
2. jmx 快速發(fā)現(xiàn)jvm中的內(nèi)存異常項
jmx,如果開啟了jmx,則我們可以直接通過jvisualvm查看內(nèi)存,線程監(jiān)控情況,還可以查看其他jmx指標;
從這里你可以,看到內(nèi)存的變化趨勢,垃圾回收,cpu變化趨勢等等,很多直觀的問題完全可以在這一環(huán)節(jié)發(fā)現(xiàn)。
另外,你可以通過采集cpu和采集內(nèi)存的方式,發(fā)現(xiàn)代碼中的瓶頸點。
可以說,jmx是我們進行代碼優(yōu)化或者參數(shù)調(diào)優(yōu)的絕對王者工具。

性能問題,可以適當進行CPU/內(nèi)存采樣,以快速發(fā)現(xiàn)瓶頸點!
建議新增插件:
Btrace Workbench 用于遠程調(diào)試,也許有用;
BufferMonitor 用于查看堆外內(nèi)存情況,其實可能不準;
Threads Inspector 用于快速查看各線程情況;
VisualJVM-MBeans 用于查看 jmx 暴露出來的 指標信息,可作為業(yè)務監(jiān)控使用;
3. jmap dump 詳細分析jvm的內(nèi)存使用情況
jmap dump,發(fā)現(xiàn)內(nèi)存異常,而其他方面沒啥思路時,那就jvm內(nèi)存dump下來,慢慢分析。
dump整個內(nèi)存下來,全量分析,jmap -dump:format=b,file=/tmp/a.dump . 然后就可以使用jvm內(nèi)存分析工具進行分析了,如 mat 。分析工具的技巧可能還是需要去掌握下的,不過我這里簡單提兩個點,一個是看得到的堆內(nèi)存,一個是不可達的堆內(nèi)存,分析時就注意這兩點。一般可達堆內(nèi)存是很好分析的,不可達堆內(nèi)存則要憑借一定的經(jīng)驗才能發(fā)現(xiàn)問題了。
對于快速查詢,則直接在服務器上使用 jmap -heap 就可以查看了。
jmap -dump:format=b,file=/tmp/a.dump <pid> # dump倒是堆內(nèi)存jmap -heap <pid> # 直接在服務器上查看堆的使用情況
會一些OQL查詢語言,將會對你的排查如虎添翼!
4. lsof 列舉出正在使用的文件,看看是否能發(fā)現(xiàn)一些端倪
lsof,這個工具用于排查是否存在很在很多超出預料的文件的情況,比如打開某文件未關(guān)閉,建立很多的socket連接等等。當然,發(fā)現(xiàn)問題只能靠眼力勁了。
lsof -p <pid> #查看進程打開的文件情況。lsof 使用詳細介紹參考網(wǎng)上資料: https://www.cnblogs.com/sparkbj/p/7161669.html
5. pmap 查看進程內(nèi)存概要
pmap,用于查看進程的內(nèi)存映像信息, 發(fā)現(xiàn)內(nèi)存中大塊的占用所在,以及分析內(nèi)存可能存在的異常。
從中,你可以看到哪些內(nèi)存上面占用了多少內(nèi)存,正常的內(nèi)存如 JVM 所在內(nèi)存段,應該是和你的堆內(nèi)存一致的,而其他內(nèi)存段,則是你看不到的地方,這些地方將是你排查內(nèi)存泄漏的方向。
簡要命令下:pmap [ -x | -d ] [ -q ] pids...結(jié)果樣例如下:[root@abtest ~]# pmap -x 2746627466: /usr/local/jdk1.8.0_211/bin/java -Dzookeeper.log.dir=. -Dzookeeper.root.logger=INFO,CONSOLE -cp /opt/zookeeper/zookeeper-3.4.14/bin/../zookeeper-server/target/classes:/opt/zookeeper/zookeeper-3.4.14/bin/../build/classes:/opt/zookeeper/zookeeper-3.4.14/bin/../zookeeper-server/target/lib/*.jar:/opt/zookeeper/zookeeper-3.4.14/bin/../build/lib/*.jar:/opt/zookeeper/zookeeper-3.4.14/bin/../lib/slf4j-log4j12-1.7.25.jar:/opt/zookeeper/zookeeper-3.4.14/bin/../lib/slf4j-api-1.7.25.jar:/opt/zookeeper/zookeeper-3.4.1Address Kbytes RSS Dirty Mode Mapping0000000000400000 4 4 0 r-x-- java0000000000600000 4 4 4 r---- java0000000000601000 4 4 4 rw--- java...00007fff10253000 136 36 36 rw--- [ stack ]00007fff102ce000 8 4 0 r-x-- [ anon ]ffffffffff600000 4 0 0 r-x-- [ anon ]---------------- ------- ------- -------total kB 5421732 100608 87672
命令操作詳情請參考網(wǎng)上資料:: https://www.cnblogs.com/txw1958/archive/2012/07/26/linux-pmap.html
6. NMT, nativeMemoryTracking jvm 的內(nèi)存追蹤工具
nmt,這個jdk8以后,jvm提供的內(nèi)存跟蹤工具,nativeMemoryTracking, 可以用于排查內(nèi)存方案的問題。
-XX:NativeMemoryTracking=summary # 開啟NMT追蹤jcmd 1 VM.native_memory summary # 查看當前的內(nèi)存概況jcmd 1 VM.native_memory baseline # 創(chuàng)建基準 baselinejcmd 1 VM.native_memory summary.diff # 一段時間后,比對內(nèi)存差異,可以用于發(fā)現(xiàn)內(nèi)存的走向問題,如下[root@abtest ~]# jcmd 5545 VM.native_memory summary.diff5545:Native Memory Tracking:Total: reserved=5942859KB +2339KB, committed=4104347KB +1519KB- Java Heap (reserved=4194304KB, committed=3645440KB)(mmap: reserved=4194304KB, committed=3645440KB)- Class (reserved=1109328KB +2056KB, committed=66640KB +264KB)(classes #11172 +2)(malloc=1360KB +8KB #15153 +331)(mmap: reserved=1107968KB +2048KB, committed=65280KB +256KB)- Thread (reserved=133119KB, committed=133119KB)(thread #130)(stack: reserved=132544KB, committed=132544KB)(malloc=422KB #652)(arena=152KB #256)- Code (reserved=255919KB +247KB, committed=37519KB +1219KB)(malloc=6319KB +247KB #8248 +270)(mmap: reserved=249600KB, committed=31200KB +972KB)- GC (reserved=209075KB +8KB, committed=188707KB +8KB)(malloc=20659KB +8KB #28406 +320)(mmap: reserved=188416KB, committed=168048KB)- Compiler (reserved=277KB +5KB, committed=277KB +5KB)(malloc=146KB +5KB #592 +9)(arena=131KB #6)- Internal (reserved=12648KB, committed=12648KB)(malloc=12616KB #39090 +5)(mmap: reserved=32KB, committed=32KB)- Symbol (reserved=16444KB +8KB, committed=16444KB +8KB)(malloc=12569KB +8KB #121265 +2)(arena=3876KB #1)- Native Memory Tracking (reserved=3362KB +15KB, committed=3362KB +15KB)(malloc=18KB #204 +2)(tracking overhead=3344KB +15KB)- Arena Chunk (reserved=192KB, committed=192KB)(malloc=192KB)- Unknown (reserved=8192KB, committed=0KB)(mmap: reserved=8192KB, committed=0KB)
如上結(jié)果,我們可以得得出些結(jié)論,隨著時間的推移, code 部分的占用空間增加了最多(JIT), Compiler 也增加一些,而堆內(nèi)存則一直保持不變!
7. perf,這是一個性能監(jiān)控調(diào)優(yōu)工具,但是我們也可能從中發(fā)現(xiàn)內(nèi)存問題點
可以先捕獲數(shù)據(jù),然后進行性能分析,然后得到可疑的點。
幫助信息如下:
usage: perf [--version] [--help] [OPTIONS] COMMAND [ARGS]The most commonly used perf commands are:annotate Read perf.data (created by perf record) and display annotated codearchive Create archive with object files with build-ids found in perf.data filebench General framework for benchmark suitesbuildid-cache Manage build-id cache.buildid-list List the buildids in a perf.data filec2c Shared Data C2C/HITM Analyzer.config Get and set variables in a configuration file.data Data file related processingdiff Read perf.data files and display the differential profileevlist List the event names in a perf.data fileftrace simple wrapper for kernel's ftrace functionalityinject Filter to augment the events stream with additional informationkallsyms Searches running kernel for symbolskmem Tool to trace/measure kernel memory propertieskvm Tool to trace/measure kvm guest oslist List all symbolic event typeslock Analyze lock eventsmem Profile memory accessesrecord Run a command and record its profile into perf.datareport Read perf.data (created by perf record) and display the profilesched Tool to trace/measure scheduler properties (latencies)script Read perf.data (created by perf record) and display trace outputstat Run a command and gather performance counter statisticstest Runs sanity tests.timechart Tool to visualize total system behavior during a workloadtop System profiling tool.probe Define new dynamic tracepointstrace strace inspired toolSee 'perf help COMMAND' for more information on a specific command.
簡單示例:
perf record -g -e cpu-clock -p 5545 # 記錄進程 5545 的相關(guān)性能信息perf report -i perf.data # 讀取剛剛記錄的數(shù)據(jù),可以顯示出種操作的占用情況,如下Samples: 908 of event 'cpu-clock', Event count (approx.): 227000000Children Self Command Shared Object Symbol+ 32.27% 0.00% java libpthread-2.17.so [.] start_thread+ 32.27% 0.00% java libjvm.so [.] java_start+ 26.54% 0.00% java libjvm.so [.] ConcurrentG1RefineThread::run+ 26.54% 0.11% java libjvm.so [.] ConcurrentG1RefineThread::run_young_rs_sampling+ 25.77% 5.62% java libjvm.so [.] YoungList::rs_length_sampling_next+ 22.58% 0.55% java [kernel.kallsyms] [k] tracesys+ 11.01% 0.00% java perf-5545.map [.] 0x00007f553ec1e981+ 10.79% 0.44% java libjvm.so [.] JVM_Sleep+ 9.36% 0.55% java libjvm.so [.] G1CollectorPolicy::update_incremental_cset_info+ 8.70% 0.55% java libjvm.so [.] os::sleep+ 8.26% 0.00% java [unknown] [k] 0xee83b0ac00709650+ 8.15% 0.99% java libpthread-2.17.so [.] pthread_cond_timedwait@@GLIBC_2.3.2+ 7.93% 0.00% java perf-5545.map [.] 0x00007f553f7f7d30

如果運氣碰巧的話,你有可能能查到某些異常的操作,從而推斷出問題所在。
8. gdb 調(diào)試工具dump出可疑內(nèi)存
gdb, linux下強大的調(diào)試工具,但是我們不用它來調(diào)試,我們只用來輸出內(nèi)存的內(nèi)容。即dump內(nèi)存,前面用到的jmap dump只能看到jvm的內(nèi)存信息,而gdb則可以看所有的,當然我們會用來看其他部分的內(nèi)存。
gdb attach <pid> # 先連接到進程中gdb dump memory /path/dump.bin 0x0011 0x0021 # dump 出內(nèi)存段的信息,具體要 dump 的內(nèi)存段地址,可以借助之前pmap 排查的結(jié)果,以及 cat /proc/<pid>/maps 中指示的地址段得出strings /path/dump.bin | less # 查看內(nèi)存內(nèi)容, 相信你能從中發(fā)現(xiàn)一些不一樣的東西
以上,足夠你排查出你懷疑的內(nèi)存泄露問題了。如果不能,說明你還用好工具,多練練!

騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)
面試:史上最全多線程面試題 !
最新阿里內(nèi)推Java后端面試題
JVM難學?那是因為你沒認真看完這篇文章

關(guān)注作者微信公眾號 —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識以及最新面試寶典


看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力
作者:等你歸去來
出處:https://www.cnblogs.com/yougewe/p/11334342.html
