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来查看具体情况。分析原因。
基础知识
常见内存错误:
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的名字,活泼性,地址,⽗classl
oader和加载的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,
Debugger attached successfully.
Client compiler detected.
JVM version is20.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,
Debugger attached successfully.
Client compiler detected.java64位
JVM version is20.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:MaxP ermSize=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插件(/mat/),然后在eclipse中,file---->open,打开这个⽂件heap.bin,利⽤现成的OOM⼯具进⾏分析。
具体操作⽅法:
⾸先输⼊⽹址/mat/previousReleases.php,然后查看你⾃⼰的Eclipse版本,我的是Indigo的,所以点击链接“Previous Releases”,选择Indigo版本的URL/mat/1.1.0/update-site/ ⽤jhat命令可以参看jhat -port 5000 heapDump在浏览器中访问:localhost:5000/查看详细信息
四、性能问题查
转⾃:wwwblogs/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插件),会展⽰如下截图:
可以从这个图看出这个类f.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:,解决⽅式如下:
1. echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope 该⽅法在下次重启前有效。
2. 永久有效⽅法 sudo vi /etc/sysctl.f 编辑下⾯这⾏: kernel.yama.ptrace_scope = 1 修改为: kernel.yama.ptrace_scope = 0 重启系统,使修改⽣效。
windows查看进程号⽅法:
由于任务管理器默认的情况下是不显⽰进程id号的,所以可以通过如下⽅法加上。
ctrl+alt+del打开任务管理器,选择‘进程’选项卡,点‘查看’->''选择列''->加上''PID'',就可以了。当然还有其他很好的选项。
参考:
1、blog.csdn/fenglibing/article/details/6411953
2、wwwblogs/o-andy-o/category/488695.html
3、
4、