一、前言
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來拿到。