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 -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




4、數據分析



總結
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,解決方式如下:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 該方法在下次重啟前有效。
永久有效方法 sudo vi /etc/sysctl.d/10-ptrace.conf 編輯下面這行: kernel.yama.ptrace_scope = 1 修改為: kernel.yama.ptrace_scope = 0 重啟系統,使修改生效。
windows查看進程號方法:
由於任務管理器默認的情況下是不顯示進程id號的,所以可以通過如下方法加上。
ctrl+alt+del打開任務管 理器,選擇‘進程’選項卡,點‘查看’->''選擇列''->加上''PID'',就可以了。當然還有其他很好的選項。
參考:
1、http://blog.csdn.net/fenglibing/article/details/6411953
2、http://www.cnblogs.com/o-andy-o/category/488695.html
3、性能問題的查找