引言
Java程序運行中常常會遇到各種關於內存的問題,例如內存洩漏、內存溢出、內存使用率太高等問題,如果沒有合適的工具和方法,則定位問題時常常感覺難以入手。本文介紹如何使用Jmap配合MAT進行Java堆內存分析,快速定位問題。
一、使用Jmap獲取堆內存信息
1.1 作用
Jmap是Java提供的用於打印進程的堆內存信息的命令,使用這個命令可以查看堆內存的具體使用情況,打印一個進程、可執行core文件、遠程debug服務的堆內存,導出堆轉儲文件,用於離線分析。
1.2 用法
- jmap [ options ] pid
- jmap [ options ] executable core
- jmap [ options ] [ pid ] server-id@ remote-hostname-or-IP
1.3 參數說明
當進程運行於一個64位的虛擬機的話,需要加上-J-d64參數
Option選項:
- 無可選項
默認打印共享對象映射信息。 - -dump:[live,] format=b,file=filename
打印Java堆內存信息,生成名字為filename,格式為hprof的文件,live參數是可選的,如果指定了則只dump出acitve objects。
- -finalizerinfo
打印關於等待結束的對象的信息。 - -heap
打印heap的概要信息,GC使用的算法,heap的配置及wise heap的使用情況. - -histo[:live]
打印每個class的實例數目,內存佔用,類全名信息. VM的內部類名字開頭會加上前綴”*”. 如果live的參數加上後,只統計active objects。 - -clstats
打印classloader以及所加載的類的數量以及大小等信息。 - -F
當使用jmap -dump或者jamp -histo打印無響應的時候使用-F強制打印。不支持live可選項。 - -h
打印幫助信息 - -help
打印幫助信息 - -Jflag
傳遞標誌給Java虛擬機。
其他參數:
- pid Java進程ID
- executable 生成核心轉儲的java可執行文件
- core 核心文件
- remote-hostname-or-IP 遠程debug服務器的名稱或者IP
- server-id 唯一ID,假如一臺主機上多個遠程debug服務,用於區分
1.4 案例
1.4.1 打印heap信息
<code>$
jmap
-heap
71664
Attaching
to
process
ID
71664
,
please
wait...
Debugger
attached
successfully.
Server
compiler
detected.
JVM
version
is
25.161
-b12
using
thread-local
object
allocation.
Parallel
GC
with
8
thread(s)
Heap Configuration:
MinHeapFreeRatio
=
0
MaxHeapFreeRatio
=
100
MaxHeapSize
=
27262976
(26.0MB)
NewSize
=
8912896
(8.5MB)
MaxNewSize
=
8912896
(8.5MB)
OldSize
=
18350080
(17.5MB)
NewRatio
=
2
SurvivorRatio
=
8
MetaspaceSize
=
21807104
(20.796875MB)
CompressedClassSpaceSize
=
1073741824
(1024.0MB)
MaxMetaspaceSize
=
17592186044415
MB
G1HeapRegionSize
=
0
(0.0MB)
Heap Usage:
PS
Young
Generation
Eden Space:
capacity
=
6815744
(6.5MB)
used
=
2982392
(2.8442306518554688MB)
free
=
3833352
(3.6557693481445312MB)
43.75739464393029
%
used
From Space:
capacity
=
1048576
(1.0MB)
used
=
0
(0.0MB)
free
=
1048576
(1.0MB)
0.0
%
used
To Space:
capacity
=
1048576
(1.0MB)
used
=
0
(0.0MB)
free
=
1048576
(1.0MB)
0.0
%
used
PS
Old
Generation
capacity
=
18350080
(17.5MB)
used
=
0
(0.0MB)
free
=
18350080
(17.5MB)
0.0
%
used
1779
interned
Strings
occupying
158984
bytes.
/<code>
1.4.2 導出堆轉儲文件
<code>$ jmap -dump
:live,format
=b,file=71664.
hprof71664
/<code>
二、模擬內存溢出
為了模擬使用MAT進行堆內存分析地情況,在此特意構建一個內存溢出的情況,導出這個進程的堆轉儲文件,代碼如下:
<code>public
class
OutOfMemory
{public
static
void
main
(String[] args
) throws InterruptedException { Vector v=new
Vector(5
);for
(int
i=1
;i<1000000
; i++) { Object o=new
Object(); v.add
(o); } System.out
.println("finished"
); } } /<code>
導出堆轉儲文件,除了以上使用jmap命令以外,也可以使用JVM參數指定當發生OutOfMemory時自動導出堆轉儲文件。例如以上代碼執行時加上以下JVM參數
<code>-Xms25m
-Xmx25m
-XX
:+HeapDumpOnOutOfMemoryError
-XX
:+PrintGCDetails
/<code>
執行此程序,導出了堆轉儲文件java_pid71932.hprof
<code>java
.lang
.OutOfMemoryError
:Java
heap
space
Dumping
heap
to
java_pid71932
.hprof
...Exception
in
thread
"main
"java
.lang
.OutOfMemoryError
:Java
heap
space
at
java
.util
.Arrays
.copyOf
(Arrays
.java
:3210)
at
java
.util
.Arrays
.copyOf
(Arrays
.java
:3181)
at
java
.util
.Vector
.grow
(Vector
.java
:266)
at
java
.util
.Vector
.ensureCapacityHelper
(Vector
.java
:246)
at
java
.util
.Vector
.add
(Vector
.java
:782)
at
tech
.liujintao
.leetcode
.OutOfMemory
.main
(OutOfMemory
.java
:12)
Heap
dump
file
created
[12430601 bytes in 0.028 secs]
/<code>
三、使用MAT分析堆內存
MAT(Memory Analyzer Tool)是Eclipse的一個插件,也提供單獨運行的版本。主要用於進行堆轉儲文件的分析,其使用方便簡單、功能強大,能夠清晰地展示堆內存中各類對象的大小、所佔的比例、可能出現內存問題的報表、線程棧等信息,為問題定位提供強大地輔助。
3.1 查看堆內存概要
打開MAT軟件,點擊"File"->“Open Heap Dump”,選擇對應的hprof文件載入堆轉儲文件,選擇Leak Suspect,進入Overview頁面
通過上圖可以看到當前佔用的總的堆內存為6.8M,其中最大的對象佔用的內存為6.3M,下面還有多個功能模塊:Actions、Reports和Step By Step。
3.2 Leak Suspects
點擊Leak Suspects,這個報表是MAT分析出來的可能導致內存洩漏、內存溢出的問題點分析,如下圖所示
圖上顯示,main線程保持了本地變量,這個變量佔用了91.64%的堆內存,此對象對應的類是java.lang.Object,由system class loader加載,很明顯,此對象佔用了那麼大的內存並且沒有被回收,可能存在問題。
那麼是哪個類加載了這個對象呢?這個時候選擇點擊"See stacktrace"查看棧軌跡
可以看到問題點代碼是OutOfMemory.java類第12行,那麼就對應查看對應類的代碼排查即可。
點擊"Details"可以查看更加詳細的內容,例如可以查看到問題點代碼的最短路徑
可以看到java.lang.Object是由main線程中Vector對象保持的。其中Shallow Heap和Retained Heap展示了對象的大小。
- Shallow Heap是指對象本身堆內存大小,不包含其引用的對象
- Retained Heap是指當前對象大小+當前對象可直接或間接引用到的對象的大小總和,並且排除被GC Roots直接或者間接引用的對象,可以看作如果對象被GC以後能釋放出的堆內存的大小。
Details中還有支配樹視圖Dominator Tree,用於查看受當前對象支配的對象中哪個佔用的Retained Heap比較大,例如下圖展示了當前對象“java.lang.Thread @ 0xffc59ab0 main”支配下的對象佔用的堆內存情況。
由於加載的對象很多,所以為了方便查看,根據類進行了堆內存的分類
3.3 Histogram視圖
該視圖以Class類的維度展示每個Class類的實例存在的個數、 佔用的Shallow Heap和 Retained Heap 大小,可以用於協助判斷哪些實例對象大量駐留於堆內存中,為定位問題提供參考。
更加詳細地,可以右鍵某個對象,選擇"List objects"=> “with outgoing references"或者"with incoming references”,前者代表的是當前對象引用了哪些對象,後者是當前對象被哪些對象引用了,這就方便進行追蹤。
3.4 Group分組視圖
在 Histogram視圖 和 Domiantor Tree視圖時,默認是按照對象的維度進行分組,點擊工具欄的分組功能,可以按照類、ClassLoader、包進行分組,更加方便定位到問題代碼,具體如下圖所示
3.5 Thread視圖
Thread視圖直觀地展示出當前所有的線程,包括線程的名字、線程所佔用的堆內存的大小,線程下的本地變量、classloader等信息,具體如下圖所示,除了進行內存分析,還支持堆線程的分析,功能相當強大。
通過以上視圖,已經能夠為堆內存地分析提供極多的參考信息,快速定位內存溢出等問題。