java命令--jmap命令使用

OkidoGreen發表於2020-04-05

參考:
1、http://blog.csdn.net/fenglibing/article/details/6411953
2、http://www.cnblogs.com/o-andy-o/category/488695.html
3、效能問題的查詢
4、Java命令學習系列(三)——Jmap

 

jdk安裝後會自帶一些小工具,jmap命令(Java Memory Map)是其中之一。主要用於列印指定Java程式(或核心檔案、遠端除錯伺服器)的共享物件記憶體對映或堆記憶體細節。

jmap命令可以獲得執行中的jvm的堆的快照,從而可以離線分析堆,以檢查記憶體洩漏,檢查一些嚴重影響效能的大物件的建立,檢查系統中什麼物件最多,各種物件所佔記憶體的大小等等。可以使用jmap生成Heap Dump。 

java memory = direct memory(直接記憶體) + jvm memory(MaxPermSize +Xmx)

1)直接記憶體跟堆
直接記憶體則是一塊由程式本身管理的一塊記憶體空間,它的效率要比標準記憶體池要高,主要用於存放網路通訊時資料緩衝和磁碟資料交換時的資料緩衝。 
DirectMemory容量可以通過 -XX:MaxDirectMemorySize指定,如果不指定,則預設為與Java堆的最大值(-Xmx指定)一樣。但是,在OSX上的最新版本的 JVM,對直接記憶體的預設大小進行修訂,改為“在不指定直接記憶體大小的時預設分配的直接記憶體大小為64MB”,可以通過 -XX:MaxMemorySize來顯示指定直接記憶體的大小。 
2)堆(Heap)和非堆(Non-heap)記憶體

按照官方的說法:“Java 虛擬機器具有一個堆,堆是執行時資料區域,所有類例項和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。”“在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)”。
可以看出JVM主要管理兩種型別的記憶體:堆和非堆。簡單來說堆就是Java程式碼可及的記憶體,是留給開發人員使用的;非堆就是JVM留給自己用的,
所以方法區、JVM內部處理或優化所需的記憶體(如JIT編譯後的程式碼快取)、每個類結構(如執行時常數池、欄位和方法資料)以及方法和構造方法的程式碼都在非堆記憶體中。

3)棧與堆

棧解決程式的執行問題,即程式如何執行,或者說如何處理資料;堆解決的是資料儲存的問題,即資料怎麼放、放在哪兒。

在Java中一個執行緒就會相應有一個執行緒棧與之對應,這點很容易理解,因為不同的執行緒執行邏輯有所不同,因此需要一個獨立的執行緒棧。而堆則是所有執行緒共享的。棧因為是執行單位,因此裡面儲存的資訊都是跟當前執行緒(或程式)相關資訊的。包括區域性變數、程式執行狀態、方法返回值等等;而堆只負責儲存物件資訊。

Java的堆是一個執行時資料區,類的(物件從中分配空間。這些物件通過new、newarray、anewarray和multianewarray等 指令建立,它們不需要程式程式碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,因為它是在執行時 動態分配記憶體的,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在執行時動態分配記憶體,存取速度較慢。 棧的優勢是,存取速度比堆要快,僅次於暫存器,棧資料可以共享。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類 型的變數(,int, short, long, byte, float, double, boolean, char)和物件控制程式碼。

執行緒佔用大小在MaxPermSize中進行記憶體申請和分配

 

什麼是堆Dump

堆Dump是反應Java堆使用情況的記憶體映象,其中主要包括系統資訊、虛擬機器屬性、完整的執行緒Dump、所有類和物件的狀態等。 一般,在記憶體不足、GC異常等情況下,我們就會懷疑有記憶體洩露。這個時候我們就可以製作堆Dump來檢視具體情況。分析原因。

基礎知識

Java虛擬機器的記憶體組成以及堆記憶體介紹 Java GC工作原理 常見記憶體錯誤:

outOfMemoryError 年老代記憶體不足。
outOfMemoryError:PermGen Space 永久代記憶體不足。
outOfMemoryError:GC overhead limit exceed 垃圾回收時間佔用系統執行時間的98%或以上。

 

一、介紹

列印出某個java程式(使用pid)記憶體內的,所有‘物件’的情況(如:產生那些物件,及其數量)。它的用途是為了展示java程式的記憶體對映資訊,或者堆記憶體詳情。 

可以輸出所有記憶體中物件的工具,甚至可以將VM 中的heap,以二進位制輸出成文字。

 

jmap -heap:format=b pid   bin格式  javaversion 1.5

jmap -dump:format=b,file=filename pid javaversion >1.6

 

jmap -dump:format=b,file=outfile 3024可以將3024程式的記憶體heap輸出出來到outfile檔案裡,再配合MAT(記憶體分析工具(Memory Analysis Tool)或與jhat (Java Heap Analysis Tool)一起使用,能夠以影像的形式直觀的展示當前記憶體是否有問題。

 

64位機上使用需要使用如下方式:

jmap -J-d64 -heap pid

 

二、命令格式

SYNOPSIS
jmap [ option ] pid  (to connect to remote debug server)   
jmap [ option ] executable core  (to connect to remote debug server)   
jmap [ option ] [server-id@]remote-hostname-or-IP  (to connect to remote debug server)   

where <option> is one of:   

<none>               to print same info as Solaris pmap   
-heap                to print java heap summary  
-histo[:live]        to print histogram of java object heap; if the "live" suboption is specified, only count live objects      
-permstat            to print permanent generation statistics  
-finalizerinfo       to print information on objects awaiting finalization   
-dump:<dump-options> to dump java heap in hprof binary format                        
              dump-options:  
                     live         dump only live objects; if not specified,all objects in the heap are dumped.                             
              format=b     binary format  
                     file=<file>  dump heap to <file>  
                     Example: jmap -dump:live,format=b,file=heap.bin <pid>      
-F                   force. Use with -dump:<dump-options> <pid> or -histo to force a heap dump or histogram when <pid> does not respond. The "live" suboption is not supported  in this mode.  
-h | -help           to print this help message  
-J<flag>             to pass <flag> directly to the runtime system  

引數如下:  
-heap:列印jvm heap的情況  
-histo:列印jvm heap的直方圖。其輸出資訊包括類名,物件數量,物件佔用大小。  
-histo:live :同上,但是隻答應存活物件的情況  
-permstat:列印permanent generation heap情況

引數說明:

1)、options: 

executable     Java executable from which the core dump was produced.(可能是產生core dump的java可執行程式)
core 將被列印資訊的core dump檔案
remote-hostname-or-IP 遠端debug服務的主機名或ip
server-id 唯一id,假如一臺主機上多個遠端debug服務,用此選項引數標識伺服器


2)基本引數:

<no option> 如果使用不帶選項引數的jmap列印共享物件對映,將會列印目標虛擬機器中載入的每個共享物件的起始地址、對映大小以及共享物件檔案的路徑全稱。這與Solaris的pmap工具比較相似。
-dump:[live,]format=b,file=<filename> 使用hprof二進位制形式,輸出jvm的heap內容到檔案, live子選項是可選的,假如指定live選項,那麼只輸出活的物件到檔案. 
-finalizerinfo 列印正等候回收的物件的資訊.
-heap 列印heap的概要資訊,GC使用的演算法,heap的配置及wise heap的使用情況.
-histo[:live] 列印每個class的例項數目,記憶體佔用,類全名資訊. VM的內部類名字開頭會加上字首”*”. 如果live子引數加上後,只統計活的物件數量. 
-permstat 列印classload和jvm heap長久層的資訊. 包含每個classloader的名字,活潑性,地址,父classloader和載入的class數量. 另外,內部String的數量和佔用記憶體數也會列印出來. 
-F 強迫.在pid沒有響應的時候使用-dump或者-histo引數. 在這個模式下,live子引數無效. 
-h | -help 列印輔助資訊 
-J<flag> 傳遞引數給jmap啟動的jvm. 
pid 需要被列印配相資訊的java程式id,可以用jps查問

 

從安全點日誌看,從Heap Dump開始,整個JVM都是停頓的,考慮到IO(雖是寫到Page Cache,但或許會遇到background flush),幾G的Heap可能產生幾秒的停頓,在生產環境上執行時謹慎再謹慎。

live的選項,實際上是產生一次Full GC來保證只看還存活的物件。有時候也會故意不加live選項,看歷史物件。

Dump出來的檔案建議用JDK自帶的VisualVM或Eclipse的MAT外掛開啟,物件的大小有兩種統計方式:

  • 本身大小(Shallow Size):物件本來的大小。
  • 保留大小(Retained Size): 當前物件大小 + 當前物件直接或間接引用到的物件的大小總和。

看本身大小時,佔大頭的都是char[] ,byte[]之類的,沒什麼意思(用jmap -histo:live pid 看的也是本身大小)。所以需要關心的是保留大小比較大的物件,看誰在引用這些char[], byte[]。

(MAT能看的資訊更多,但VisualVM勝在JVM自帶,用法如下:命令列輸入jvisualvm,檔案->裝入->堆Dump->檢查 -> 查詢20保留大小最大的物件,就會觸發保留大小的計算,然後就可以類檢視裡瀏覽,按保留大小排序了)

 

三、使用例項

1、jmap -heap pid 展示pid的整體堆資訊

#ps -ef|grep tomcat  #獲取tomcat的pid

[root@localhost ~]# jmap -heap 27900
Attaching to process ID 27900, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 20.45-b01
using thread-local object allocation.
Mark Sweep Compact GC
Heap Configuration: #堆記憶體初始化配置
   MinHeapFreeRatio = 40     #-XX:MinHeapFreeRatio設定JVM堆最小空閒比率  
   MaxHeapFreeRatio = 70   #-XX:MaxHeapFreeRatio設定JVM堆最大空閒比率  
   MaxHeapSize = 100663296 (96.0MB)   #-XX:MaxHeapSize=設定JVM堆的最大大小
   NewSize = 1048576 (1.0MB)     #-XX:NewSize=設定JVM堆的‘新生代’的預設大小
   MaxNewSize = 4294901760 (4095.9375MB) #-XX:MaxNewSize=設定JVM堆的‘新生代’的最大大小
   OldSize = 4194304 (4.0MB)  #-XX:OldSize=設定JVM堆的‘老生代’的大小
   NewRatio = 2    #-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
   SurvivorRatio = 8  #-XX:SurvivorRatio=設定年輕代中Eden區與Survivor區的大小比值
   PermSize = 12582912 (12.0MB) #-XX:PermSize=<value>:設定JVM堆的‘持久代’的初始大小  
   MaxPermSize = 67108864 (64.0MB) #-XX:MaxPermSize=<value>:設定JVM堆的‘持久代’的最大大小  
Heap Usage:
New Generation (Eden + 1 Survivor Space): #新生代區記憶體分佈,包含伊甸園區+1個Survivor區
   capacity = 30212096 (28.8125MB)
   used = 27103784 (25.848182678222656MB)
   free = 3108312 (2.9643173217773438MB)
   89.71169693092462% used
Eden Space: #Eden區記憶體分佈
   capacity = 26869760 (25.625MB)
   used = 26869760 (25.625MB)
   free = 0 (0.0MB)
   100.0% used
From Space: #其中一個Survivor區的記憶體分佈
   capacity = 3342336 (3.1875MB)
   used = 234024 (0.22318267822265625MB)
   free = 3108312 (2.9643173217773438MB)
   7.001809512867647% used
To Space: #另一個Survivor區的記憶體分佈
   capacity = 3342336 (3.1875MB)
   used = 0 (0.0MB)
   free = 3342336 (3.1875MB)
   0.0% used
tenured generation:   #當前的Old區記憶體分佈  
   capacity = 67108864 (64.0MB)
   used = 67108816 (63.99995422363281MB)
   free = 48 (4.57763671875E-5MB)
   99.99992847442627% used
Perm Generation:     #當前的 “持久代” 記憶體分佈
   capacity = 14417920 (13.75MB)
   used = 14339216 (13.674942016601562MB)
   free = 78704 (0.0750579833984375MB)
   99.45412375710227% used

新生代的記憶體回收就是採用空間換時間的方式;
如果from區使用率一直是100% 說明程式建立大量的短生命週期的例項,使用jstat統計一下jvm在記憶體回收中發生的頻率耗時以及是否有full gc,使用這個資料來評估一記憶體配置引數、gc引數是否合理;

多次ygc後如果s區沒變化,這種情況不出意外就是擔保了,可以jstat持續觀察下;

[root@localhost ~]# jmap -heap 4951
Attaching to process ID 4951, please wait...
Debugger attached successfully.
Client compiler detected.
JVM version is 20.45-b01

using thread-local object allocation.
Parallel GC with 1 thread(s)

Heap Configuration:
   MinHeapFreeRatio = 40
   MaxHeapFreeRatio = 70
   MaxHeapSize      = 734003200 (700.0MB)
   NewSize          = 314572800 (300.0MB)
   MaxNewSize       = 314572800 (300.0MB)
   OldSize          = 4194304 (4.0MB)
   NewRatio         = 2
   SurvivorRatio    = 8
   PermSize         = 104857600 (100.0MB)
   MaxPermSize      = 104857600 (100.0MB)

Heap Usage:
PS Young Generation
Eden Space:
   capacity = 235929600 (225.0MB)
   used     = 169898184 (162.02753448486328MB)
   free     = 66031416 (62.97246551513672MB)
   72.01223754882812% used
From Space:
   capacity = 39321600 (37.5MB)
   used     = 0 (0.0MB)
   free     = 39321600 (37.5MB)
   0.0% used
To Space:
   capacity = 39321600 (37.5MB)
   used     = 0 (0.0MB)
   free     = 39321600 (37.5MB)
   0.0% used
PS Old Generation
   capacity = 419430400 (400.0MB)
   used     = 0 (0.0MB)
   free     = 419430400 (400.0MB)
   0.0% used
PS Perm Generation
   capacity = 104857600 (100.0MB)
   used     = 14140688 (13.485610961914062MB)
   free     = 90716912 (86.51438903808594MB)
   13.485610961914062% used

根據列印的結果:預設存活區與eden比率=2:8

1)檢視eden區:225M

2)兩個存活區大小:都為37.5M

3)年輕代大小:300M

4)老年代大小:400M

5)持久代大小:100M

6)最大堆記憶體大小:年輕代大小+老年代大小=700M

7)java應用程式佔用記憶體大小:最大堆記憶體大小+持久代大小=700M+100M=800M

對應java引數(在tomcat的啟動檔案裡)配置如下:

 JAVA_OPTS="-Xms700m -Xmx700m -Xmn300m -Xss1024K -XX:PermSize=100m -XX:MaxPermSize=100 -XX:+UseParallelGC -XX:ParallelGCThreads=1 -XX:+PrintGCTimeStamps
 -XX:+PrintGCDetails -Xloggc:/opt/logs/gc.log -verbose:gc -XX:+DisableExplicitGC"

注意引數配置時,上述引數為一行。

各引數意義:

  • Xms
  • Xmx
  • Xmn
  • Xss
  • -XX:PermSize
  • -XX:MaxPermSize
  • NewRatio = 2   表示新生代(e+2s):老年代(不含永久區)=1:2,指新生代佔整個堆的1/3
  • SurvivorRatio = 8  表示2個S:eden=2:8,一個S佔年輕代的1/10

2、jmap -histo pid 展示class的記憶體情況

說明:instances(例項數)、bytes(大小)、classs name(類名)。它基本是按照使用使用大小逆序排列的。 

#instance 是物件的例項個數 
#bytes 是總佔用的位元組數 
class name 對應的就是 Class 檔案裡的 class 的標識 
B 代表 byte
C 代表 char
D 代表 double
F 代表 float
I 代表 int
J 代表 long
Z 代表 boolean
前邊有 [ 代表陣列, [I 就相當於 int[]
物件用 [L+ 類名錶示

 從列印結果可看出,類名中存在[C、[B等內容,

  • 只知道它佔用了那麼大的記憶體,但不知道由什麼物件建立的。下一步需要將其他dump出來,使用記憶體分析工具進一步明確它是由誰引用的、由什麼物件。

 

3、 jmap -histo:live pid>a.log

可以觀察heap中所有物件的情況(heap中所有生存的物件的情況)。包括物件數量和所佔空間大小。 可以將其儲存到文字中去,在一段時間後,使用文字對比工具,可以對比出GC回收了哪些物件。

jmap -histo:live 這個命令執行,JVM會先觸發gc,然後再統計資訊。

 

4、dump 將記憶體使用的詳細情況輸出到檔案

jmap -dump:live,format=b,file=a.log pid

說明:記憶體資訊dump到a.log檔案中。

 這個命令執行,JVM會將整個heap的資訊dump寫入到一個檔案,heap如果比較大的話,就會導致這個過程比較耗時,並且執行的過程中為了保證dump的資訊是可靠的,所以會暫停應用。

 

 

該命令通常用來分析記憶體洩漏OOM,通常做法是:

1)首先配置JVM啟動引數,讓JVM在遇到OutOfMemoryError時自動生成Dump檔案

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path

2)然後使用命令

jmap  -dump:format=b,file=/path/heap.bin 程式ID  
如果只dump heap中的存活物件,則加上選項-live。 

 3)然後使用MAT分析工具,如jhat命令,eclipse的mat外掛。

最後在eclipse中安裝MAT外掛(http://www.eclipse.org/mat/),然後在eclipse中,file---->open,開啟這個檔案heap.bin,利用現成的OOM工具進行分析。
具體操作方法:
首先輸入網址http://www.eclipse.org/mat/previousReleases.php,然後檢視你自己的Eclipse版本,我的是Indigo的,所以點選連結“Previous Releases”,選擇Indigo版本的URLhttp://download.eclipse.org/mat/1.1.0/update-site/ 

 用jhat命令可以參看 jhat -port 5000 heapDump 在瀏覽器中訪問:http://localhost:5000/ 檢視詳細資訊

四、效能問題查詢

轉自:http://www.cnblogs.com/gaojk/articles/3886503.html

1、發現問題

1)、使用uptime命令檢視CPU的Load情況,Load越高說明問題越嚴重;

2)、使用jstat檢視FGC發生的頻率及FGC所花費的時間,FGC發生的頻率越快、花費的時間越高,問題越嚴重;

 

2、匯出資料:在應用快要發生FGC的時候把堆匯出來

1)、檢視快要發生FGC使用命令:

jmap -heap <pid>

會看到如下圖結果:

    以上截圖包括了新生代、老年代及持久代的當前使用情況,如果不停的重複上面的命令,會看到這些數字的變化,變化越大說明系統存在問題的可能性越大,特別是被紅色圈起來的老年代的變化情況。現在看到的這個值為使用率為99%或才快接近的時候,就立即可以執行匯出堆疊的操作了。

    注:這是因為我這裡沒有在jvm引數中使用"-server"引數,也沒有指定FGC的閥值,線上上的應用中通過會指定CMSInitiatingOccupancyFraction這個引數來指定當老年代使用了百分之多少的時候,通過CMS進行FGC,當然這個引數需要和這些引數一起使用“-XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:+UseCMSInitiatingOccupancyOnly”,CMSInitiatingOccupancyFraction的預設值是68,現在中文站線上的應用都是70,也就是說當老年代使用率真達到或者超過70%時,就會進行FGC。

    2)、將資料匯出:

jmap -dump:format=b,file=heap.bin <pid>

這個時候會在當前目錄以生成一個heap.bin這個二進位制檔案。

 

3、通過命令檢視大物件

也是使用jmap的命令,只不過引數使用-histo

使用:jmap -histo <pid>|less

可得到如下包含物件序號、某個物件示例數、當前物件所佔記憶體的大小、當前物件的全限定名,如下圖:

檢視物件數最多的物件,並按降序排序輸出:

執行:jmap -histo <pid>|grep alibaba|sort -k 2 -g -r|less

結果如圖:

檢視佔用記憶體最多的最象,並按降序排序輸出:

執行:jmap -histo <pid>|grep alibaba|sort -k 3 -g -r|less

結果如圖:


4、資料分析

這個時候將dump出的檔案在ECLIPSE中開啟,使用MAT進行分析(ECLIPSE需要先安裝MAT外掛),會展示如下截圖:

可以從這個圖看出這個類java.lang.ref.Finalizer佔用500多M,表示這其中很多不能夠被回物件的物件,此時點開hisgogram檢視,並通過Retained Heap進行排序,如下截圖:

從圖中可以看出,被線線框圈起來的三個物件佔用量非常大,那說明這幾個大的物件並沒有被釋放,那現在就可以有針對性的從程式碼中去找這幾個物件為什麼沒有被釋放了。

再切換到dominator_tree檢視:

這裡可以看到velocity渲染也存在著問題,以及資料庫的請求也比較多。

 

5、優化

優化的思路就是上面所列出來的問題,檢視實現程式碼中所存在問題,具體問題具體分析。

 


總結

1.如果程式記憶體不足或者頻繁GC,很有可能存在記憶體洩露情況,這時候就要藉助Java堆Dump檢視物件的情況。
2.要製作堆Dump可以直接使用jvm自帶的jmap命令
3.可以先使用jmap -heap命令檢視堆的使用情況,看一下各個堆空間的佔用情況。
4.使用jmap -histo:[live]檢視堆記憶體中的物件的情況。如果有大量物件在持續被引用,並沒有被釋放掉,那就產生了記憶體洩露,就要結合程式碼,把不用的物件釋放掉。
5.也可以使用 jmap -dump:format=b,file=<fileName>命令將堆資訊儲存到一個檔案中,再借助jhat命令檢視詳細內容
6.在記憶體出現洩露、溢位或者其它前提條件下,建議多dump幾次記憶體,把記憶體檔案進行編號歸檔,便於後續記憶體整理分析。

7.在用cms gc的情況下,執行jmap -heap有些時候會導致程式變T,因此強烈建議別執行這個命令,如果想獲取記憶體目前每個區域的使用狀況,可通過jstat -gc或jstat -gccapacity來拿到。

 

 

Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can’t attach to the process

在ubuntu中第一次使用jmap會報錯:Error attaching to process: sun.jvm.hotspot.debugger.DebuggerException: Can't attach to the process,這是oracla文件中提到的一個bug:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=7050524,解決方式如下:

  1. echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 該方法在下次重啟前有效。

  2. 永久有效方法 sudo vi /etc/sysctl.d/10-ptrace.conf 編輯下面這行: kernel.yama.ptrace_scope = 1 修改為: kernel.yama.ptrace_scope = 0 重啟系統,使修改生效。

 


windows檢視程式號方法:

由於工作管理員預設的情況下是不顯示程式id號的,所以可以通過如下方法加上。

ctrl+alt+del開啟任務管 理器,選擇‘程式’選項卡,點‘檢視’->''選擇列''->加上''PID'',就可以了。當然還有其他很好的選項。

相關文章