天天看點

記憶體洩漏排查攻略之:Show me your Memory

  java 語言有個神奇的地方,那就是你時不時會去關注下記憶體。(當然了,任何牛逼的同學都應該關注記憶體)

  今天我們就來這麼個問題現場吧:某應用運作了一段時間後,ecs監控報警了,記憶體比較高了,怎麼辦?随着時間的推移,發現記憶體越來越高緩緩增長,怎麼辦?

  凡事講究證據,報警系統說記憶體緊張就緊張嗎,還得自己去驗一下。

如何确認記憶體問題?這太重要了! 其實隻要給我看你記憶體裡所有東西(Show me your Memory),我豈有找不到問題根源的道理?以下是幾種檢視記憶體問題的方法供諸君參考:

1. top 等檢視系統記憶體概況

  top:顯示所有程序運作情況,按M鍵按照記憶體大小排序,立馬看到罪魁禍首。具體指令請參考網上資料。

  top簡要使用方法如下:

使用格式:
top [-] [d] [p] [q] [c] [C] [S] [s] [n]
參數說明:
d:指定每兩次螢幕資訊重新整理之間的時間間隔。當然使用者可以使用s互動指令來改變之。
p:通過指定監控程序ID來僅僅監控某個程序的狀态。
q:該選項将使top沒有任何延遲的進行重新整理。如果調用程式有超級使用者權限,那麼top将以盡可能高的優先級運作。
S:指定累計模式。
s:使top指令在安全模式中運作。這将去除互動指令所帶來的潛在危險。
i:使top不顯示任何閑置或者僵死程序。
c:顯示整個指令行而不隻是顯示指令名。

常用指令說明:
Ctrl+L:擦除并且重寫螢幕
K:終止一個程序。系統将提示使用者輸入需要終止的程序PID,以及需要發送給該程序什麼樣的信号。一般的終止程序可以使用15信号;如果不能正常結束那就使用信号9強制結束該程序。預設值是信号15。在安全模式中此指令被屏蔽。
i:忽略閑置和僵死程序。這是一個開關式指令。
q:退出程式
r:重新安排一個程序的優先級别。系統提示使用者輸入需要改變的程序PID以及需要設定的程序優先級值。輸入一個正值将使優先級降低,反之則可以使該程序擁有更高的優先權。預設值是10。
S:切換到累計模式。
s:改變兩次重新整理之間的延遲時間。系統将提示使用者輸入新的時間,機關為s。如果有小數,就換算成m s。輸入0值則系統将不斷重新整理,預設值是5 s。需要注意的是如果設定太小的時間,很可能會引起不斷重新整理,進而根本來不及看清顯示的情況,而且系統負載也會大大增加。
f或者F:從目前顯示中添加或者删除項目。
o或者O:改變顯示項目的順序
l:切換顯示平均負載和啟動時間資訊。
m:切換顯示記憶體資訊。
t:切換顯示程序和CPU狀态資訊。
c:切換顯示指令名稱和完整指令行。
M:根據駐留記憶體大小進行排序。
P:根據CPU使用百分比大小進行排序。
T:根據時間/累計時間進行排序。
W:将目前設定寫入~/.toprc檔案中。      

  另外在記憶體檢視方面,還可以使用 free用于快速直接檢視記憶體,還可以看到有多少是系統緩存;(系統緩存一般不被計入真正已使用記憶體中)

2. jmx 快速發現jvm中的記憶體異常項

  jmx,如果開啟了jmx,則我們可以直接通過jvisualvm檢視記憶體,線程監控情況,還可以檢視其他jmx名額;

  從這裡你可以,看到記憶體的變化趨勢,垃圾回收,cpu變化趨勢等等,很多直覺的問題完全可以在這一環節發現。

  另外,你可以通過采集cpu和采集記憶體的方式,發現代碼中的瓶頸點。

  可以說,jmx是我們進行代碼優化或者參數調優的絕對王者工具。

記憶體洩漏排查攻略之:Show me your Memory

  性能問題,可以适當進行CPU/記憶體采樣,以快速發現瓶頸點!

  建議新增插件: 

    Btrace Workbench  用于遠端調試,也許有用;

    BufferMonitor 用于檢視堆外記憶體情況,其實可能不準;

    Threads Inspector 用于快速檢視各線程情況;

    VisualJVM-MBeans 用于檢視 jmx 暴露出來的 名額資訊,可作為業務監控使用;

3. jmap dump 詳細分析jvm的記憶體使用情況

  jmap dump,發現記憶體異常,而其他方面沒啥思路時,那就jvm記憶體dump下來,慢慢分析。

  dump整個記憶體下來,全量分析,jmap -dump:format=b,file=/tmp/a.dump . 然後就可以使用jvm記憶體分析工具進行分析了,如 mat 。分析工具的技巧可能還是需要去掌握下的,不過我這裡簡單提兩個點,一個是看得到的堆記憶體,一個是不可達的堆記憶體,分析時就注意這兩點。一般可達堆記憶體是很好分析的,不可達堆記憶體則要憑借一定的經驗才能發現問題了。

  對于快速查詢,則直接在伺服器上使用 jmap -heap 就可以檢視了。

jmap -dump:format=b,file=/tmp/a.dump <pid> # dump倒是堆記憶體
jmap -heap <pid> # 直接在伺服器上檢視堆的使用情況      

       會一些OQL查詢語言,将會對你的排查如虎添翼! 

4. lsof 列舉出正在使用的檔案,看看是否能發現一些端倪

  lsof,這個工具用于排查是否存在很在很多超出預料的檔案的情況,比如打開某檔案未關閉,建立很多的socket連接配接等等。當然,發現問題隻能靠眼力勁了。

lsof -p <pid> #檢視程序打開的檔案情況。      

  lsof 使用詳細介紹參考網上資料: https://www.cnblogs.com/sparkbj/p/7161669.html

5. pmap 檢視程序記憶體概要

  pmap,用于檢視程序的記憶體映像資訊, 發現記憶體中大塊的占用所在,以及分析記憶體可能存在的異常。

  從中,你可以看到哪些記憶體上面占用了多少記憶體,正常的記憶體如 JVM 所在記憶體段,應該是和你的堆記憶體一緻的,而其他記憶體段,則是你看不到的地方,這些地方将是你排查記憶體洩漏的方向。

簡要指令下:
    pmap [ -x | -d ] [ -q ] pids...
結果樣例如下:
[root@abtest ~]# pmap -x 27466 
27466:   /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.1
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000       4       4       0 r-x-- java
0000000000600000       4       4       4 r---- java
0000000000601000       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      

  指令操作詳情請參考網上資料:: https://www.cnblogs.com/txw1958/archive/2012/07/26/linux-pmap.html

6. NMT,  nativeMemoryTracking jvm 的記憶體追蹤工具

  nmt,這個jdk8以後,jvm提供的記憶體跟蹤工具,nativeMemoryTracking, 可以用于排查記憶體方案的問題。 

-XX:NativeMemoryTracking=summary     # 開啟NMT追蹤
jcmd 1 VM.native_memory summary        # 檢視目前的記憶體概況
jcmd 1 VM.native_memory baseline    # 建立基準 baseline
jcmd 1 VM.native_memory summary.diff    # 一段時間後,比對記憶體差異,可以用于發現記憶體的走向問題,如下
[root@abtest ~]# jcmd 5545 VM.native_memory summary.diff
5545:

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)      

  如上結果,我們可以得得出些結論,随着時間的推移, code 部分的占用空間增加了最多(JIT), Compiler 也增加一些,而堆記憶體則一直保持不變!

7. perf,這是一個性能監控調優工具,但是我們也可能從中發現記憶體問題點

  可以先捕獲資料,然後進行性能分析,然後得到可疑的點。

幫助資訊如下:

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 code
   archive         Create archive with object files with build-ids found in perf.data file
   bench           General framework for benchmark suites
   buildid-cache   Manage build-id cache.
   buildid-list    List the buildids in a perf.data file
   c2c             Shared Data C2C/HITM Analyzer.
   config          Get and set variables in a configuration file.
   data            Data file related processing
   diff            Read perf.data files and display the differential profile
   evlist          List the event names in a perf.data file
   ftrace          simple wrapper for kernel's ftrace functionality
   inject          Filter to augment the events stream with additional information
   kallsyms        Searches running kernel for symbols
   kmem            Tool to trace/measure kernel memory properties
   kvm             Tool to trace/measure kvm guest os
   list            List all symbolic event types
   lock            Analyze lock events
   mem             Profile memory accesses
   record          Run a command and record its profile into perf.data
   report          Read perf.data (created by perf record) and display the profile
   sched           Tool to trace/measure scheduler properties (latencies)
   script          Read perf.data (created by perf record) and display trace output
   stat            Run a command and gather performance counter statistics
   test            Runs sanity tests.
   timechart       Tool to visualize total system behavior during a workload
   top             System profiling tool.
   probe           Define new dynamic tracepoints
   trace           strace inspired tool

 See 'perf help COMMAND' for more information on a specific command.      

  簡單示例:

perf record -g -e cpu-clock -p 5545        # 記錄程序 5545 的相關性能資訊
    perf report -i perf.data                # 讀取剛剛記錄的資料,可以顯示出種操作的占用情況,如下
Samples: 908  of event 'cpu-clock', Event count (approx.): 227000000
  Children      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      
記憶體洩漏排查攻略之:Show me your Memory

  如果運氣碰巧的話,你有可能能查到某些異常的操作,進而推斷出問題所在。

8. gdb 調試工具dump出可疑記憶體

  gdb, linux下強大的調試工具,但是我們不用它來調試,我們隻用來輸出記憶體的内容。即dump記憶體,前面用到的jmap dump隻能看到jvm的記憶體資訊,而gdb則可以看所有的,當然我們會用來看其他部分的記憶體。

gdb attach <pid>                    # 先連接配接到程序中
    gdb dump memory /path/dump.bin 0x0011  0x0021    # dump 出記憶體段的資訊,具體要 dump 的記憶體段位址,可以借助之前pmap 排查的結果,以及 cat /proc/<pid>/maps 中訓示的位址段得出
    strings /path/dump.bin | less # 檢視記憶體内容, 相信你能從中發現一些不一樣的東西
      

  

  以上,足夠你排查出你懷疑的記憶體洩露問題了。如果不能,說明你還用好工具,多練練!

唠叨: 有心者,共勉之!

不要害怕今日的苦,你要相信明天,更苦!