JVM系列-使用jmap和MAT進行堆內存分析

引言

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.

hprof

71664

/<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頁面

JVM系列-使用jmap和MAT進行堆內存分析

通過上圖可以看到當前佔用的總的堆內存為6.8M,其中最大的對象佔用的內存為6.3M,下面還有多個功能模塊:Actions、Reports和Step By Step。

3.2 Leak Suspects

點擊Leak Suspects,這個報表是MAT分析出來的可能導致內存洩漏、內存溢出的問題點分析,如下圖所示

JVM系列-使用jmap和MAT進行堆內存分析

圖上顯示,main線程保持了本地變量,這個變量佔用了91.64%的堆內存,此對象對應的類是java.lang.Object,由system class loader加載,很明顯,此對象佔用了那麼大的內存並且沒有被回收,可能存在問題。

那麼是哪個類加載了這個對象呢?這個時候選擇點擊"See stacktrace"查看棧軌跡

JVM系列-使用jmap和MAT進行堆內存分析

可以看到問題點代碼是OutOfMemory.java類第12行,那麼就對應查看對應類的代碼排查即可。

點擊"Details"可以查看更加詳細的內容,例如可以查看到問題點代碼的最短路徑

JVM系列-使用jmap和MAT進行堆內存分析

可以看到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”支配下的對象佔用的堆內存情況。

JVM系列-使用jmap和MAT進行堆內存分析

由於加載的對象很多,所以為了方便查看,根據類進行了堆內存的分類

JVM系列-使用jmap和MAT進行堆內存分析

3.3 Histogram視圖

該視圖以Class類的維度展示每個Class類的實例存在的個數、 佔用的Shallow Heap和 Retained Heap 大小,可以用於協助判斷哪些實例對象大量駐留於堆內存中,為定位問題提供參考。

更加詳細地,可以右鍵某個對象,選擇"List objects"=> “with outgoing references"或者"with incoming references”,前者代表的是當前對象引用了哪些對象,後者是當前對象被哪些對象引用了,這就方便進行追蹤。

JVM系列-使用jmap和MAT進行堆內存分析

3.4 Group分組視圖

在 Histogram視圖 和 Domiantor Tree視圖時,默認是按照對象的維度進行分組,點擊工具欄的分組功能,可以按照類、ClassLoader、包進行分組,更加方便定位到問題代碼,具體如下圖所示

JVM系列-使用jmap和MAT進行堆內存分析

3.5 Thread視圖

Thread視圖直觀地展示出當前所有的線程,包括線程的名字、線程所佔用的堆內存的大小,線程下的本地變量、classloader等信息,具體如下圖所示,除了進行內存分析,還支持堆線程的分析,功能相當強大。

JVM系列-使用jmap和MAT進行堆內存分析

通過以上視圖,已經能夠為堆內存地分析提供極多的參考信息,快速定位內存溢出等問題。


分享到:


相關文章: