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中進行內存申請和分配。

二、命令介紹

Usage:
    jmap [option] <pid>
        (to connect to running process)
    jmap [option] <executable <core>
        (to connect to a core file)
    jmap [option] [server_id@]<remote server IP or hostname>
        (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
    -clstats             to print class loader 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、jmap -heap [pid]

展示pid的整體堆信息。

首先啓動一個tomcat,然後使用如下命令獲取tomcat的進程ID。

ps -ef|grep tomcat

在這裏插入圖片描述
然後執行:

jmap -heap 86038

輸出內容:

Attaching to process ID 86038, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.121-b13

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

Heap Configuration:         # 堆內存初始化配置
   MinHeapFreeRatio         = 0                        # -XX:MinHeapFreeRatio設置JVM堆最小空閒比率  
   MaxHeapFreeRatio         = 100                      # -XX:MaxHeapFreeRatio設置JVM堆最大空閒比率  
   MaxHeapSize              = 2147483648 (2048.0MB)    # -XX:MaxHeapSize=設置JVM堆的最大大小
   NewSize                  = 44564480 (42.5MB)        # -XX:NewSize=設置JVM堆的‘新生代’的默認大小
   MaxNewSize               = 715653120 (682.5MB)      # -XX:MaxNewSize=設置JVM堆的‘新生代’的最大大小
   OldSize                  = 89653248 (85.5MB)        # -XX:OldSize=設置JVM堆的‘老生代’的大小
   NewRatio                 = 2                        # -XX:NewRatio=:‘新生代’和‘老生代’的大小比率
   SurvivorRatio            = 8                        # -XX:SurvivorRatio=設置年輕代中Eden區與Survivor區的大小比值
   MetaspaceSize            = 21807104 (20.796875MB)   
   CompressedClassSpaceSize = 1073741824 (1024.0MB)    
   MaxMetaspaceSize         = 17592186044415 MB        
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space:           # Eden區內存分佈
   capacity = 426770432 (407.0MB)
   used     = 79056192 (75.39385986328125MB)
   free     = 347714240 (331.60614013671875MB)
   18.524289892698096% used
From Space:           # 其中一個Survivor區的內存分佈
   capacity = 30932992 (29.5MB)
   used     = 0 (0.0MB)
   free     = 30932992 (29.5MB)
   0.0% used
To Space:             # 另一個Survivor區的內存分佈
   capacity = 31457280 (30.0MB)
   used     = 0 (0.0MB)
   free     = 31457280 (30.0MB)
   0.0% used
PS Old Generation
   capacity = 72351744 (69.0MB)
   used     = 21741336 (20.734153747558594MB)
   free     = 50610408 (48.265846252441406MB)
   30.049498184867527% used

16180 interned Strings occupying 2074344 bytes.

2、jmap -histo[:live] [pid]

展示class的內存情況。

jmap -histo 86038

執行結果:

 num     #instances         #bytes  class name
----------------------------------------------
   1:          7287       46167552  [I
   2:         78166       33933008  [B
   3:        221419       25746168  [C
   4:        116110        2786640  java.lang.String
   5:         11708         886224  [Ljava.lang.Object;
   6:         18869         603808  java.util.HashMap$Node
   7:         24275         582600  java.lang.StringBuilder
   8:          6464         568832  java.lang.reflect.Method
   9:          4715         541352  java.lang.Class
  10:          4847         418760  [S
  11:          1686         391288  [Ljava.util.HashMap$Node;

注:

  • instances:實例數;
  • bytes:內存佔用大小;
  • classs name:類名。

它基本是按照使用使用大小逆序排列的。

jmap -histo:live 86038

獲取所有生存的對象的內存情況。

注:

  • 該命令獲取的結果與jmap -histo [pid]獲取結果一致;
  • 執行jmap -histo:live [pid]時,JVM會先觸發gc,然後再統計信息

從打印結果可看出,類名中存在[C、[B等內容,只知道它佔用了那麼大的內存,但不知道由什麼對象創建的。下一步需要將其他dump出來,使用內存分析工具進一步明確它是由誰引用的、由什麼對象。

另外可以執行如下命令將打印內容保存到文件中。

jmap -histo:live 86038>a.log

通過對多次打印內容的對比,可以對比出GC回收了哪些對象。

3、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)然後使用命令

// 如果只dump heap中的存活對象,則加上選項-live。
jmap -dump:format=b,file=/path/heap.bin [pid]

四、總結

  • 1、如果程序內存不足或者頻繁GC,很有可能存在內存泄露情況,這時候就要藉助Java堆Dump查看對象的情況。

  • 2、要製作堆Dump可以直接使用jvm自帶的jmap命令

  • 3、可以先使用jmap -heap命令查看堆的使用情況,看一下各個堆空間的佔用情況。

  • 4、使用jmap -histo:[live]查看堆內存中的對象的情況。如果有大量對象在持續被引用,並沒有被釋放掉,那就產生了內存泄露,就要結合代碼,把不用的對象釋放掉。

  • 5、也可以使用 jmap -dump:format=b,file=命令將堆信息保存到一個文件中,再借助jhat命令查看詳細內容

  • 6、在內存出現泄露、溢出或者其它前提條件下,建議多dump幾次內存,把內存文件進行編號歸檔,便於後續內存整理分析。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章