JDK 工具之 jmap 详解

Last Modified: 2023/09/03

概述

jmap 命令用于输出与内存相关的统计信息,可以作用于运行中的虚拟机或者 core 文件。

JDK8 引入了 Java Mission Control、Java Flight Recorder 和 jcmd 等实用工具,用于诊断 JVM 和 Java 应用程序的问题。建议使用最新的工具 jcmd 来代替 jmap 以获得更强的的诊断功能和更低的性能开销。

注:以下命令行中的 <pid> 表示某个 jvm 进程的 pid。另外本文混用 java 进程和 jvm 进程。

输出 JVM 加载共享对象信息

如果 jmap 在没有任何命令行选项的情况下与进程或 core 文件一起使用,那么它将打印已加载的共享对象列表 可以使用 jps 获取指定 java 进程的 pid, 然后使用下面命令可以查看加载的共享库:

jmap <pid>

输出大概类似下面这样:

0x0000555d96e00000	8K     /home/saltyfish/Public/jdk1.8.0_301/bin/java
...
0x00007f3257df3000	153K   /usr/lib/x86_64-linux-gnu/libpthread-2.31.so
0x00007f3257e18000	50K    /usr/lib/x86_64-linux-gnu/libnss_files-2.31.so
0x00007f3257e2f000	186K   /usr/lib/x86_64-linux-gnu/ld-2.31.so

此外,JDK7 引入了选项 -dump:format=b,file=filename,它使得 jmap 可以将 Java 堆以二进制 HPROF 格式转储到指定的文件中。然后可以使用 jhat 工具对该文件进行分析。

如果 jmap pid 命令由于进程挂起而无响应,可以使用 -F 选项(仅适用于 Oracle Solaris 和 Linux 操作系统)来强制使用 Serviceability Agent。

统计对象的个数和内存占用

这个就很有用了,可以用于诊断内存泄露,如果某些对象的个数明显不正常或者持续增加,那么很有可能便是内存泄露。

jmap -histo <pid>

输出样例如下:

num     #instances         #bytes  class name
----------------------------------------------
   1:           638        4272752  [I
   2:          1464        1218728  [B
   3:          5816         616208  [C
   4:          4593         110232  java.lang.String
   5:           672          76784  java.lang.Class
   6:          1281          56824  [Ljava.lang.Object;
   7:           623          24920  java.util.LinkedHashMap$Entry
   8:           318          16320  [Ljava.lang.String;
   9:           391          12512  java.util.HashMap$Node
  ...

num 是序号,instances 表示对象实例个数,bytes 表示占用的字节数,class name 表示是类名。从上面的输出可以看出内存排名前三的分别是 [I[B[C,分别表示整形数组、字节数组和字符数组。[ 半边括号表示数组。

注意,运行该命令的时候应用程序线程会停止:jmap -histo 会停止 Java 线程,而 jmap -heap 会停止整个JVM 进程。暂停的持续时间可能会相当长,尤其是堆内存比较大的时候,遍历 4GB 的堆可能需要几秒钟的时间

其实这还不算慢的,当使用 -F 得时候,工作机制就不同了,会使用 ptrace 读 jvm 内存,一次一个字节的读,这会相当的慢,慢到坑爹。具体可以参考:jmap -F / jstack -F

导出 Heap dump 文件

可以借助 jmap 导出 heap dump 文件,之后可以使用更专业的工具,例如 jhat/MAT 来分析内存泄露问题。 下面的命令会导出 heap dump 并存储到 myapp.hprof:

jmap -dump:format=b,file=myapp.hprof <pid>

生产环境使用需要注意,该命令导致 stop the world,因此需要谨慎,不过如果部署了多台应用服务器,可以将有问题的服务器踢下线,然后使用 jmap 来获取 heap dump。

使用 jcmd 也可以完成同样的功能,命令如下:

jcmd <pid> GC.heap_dump </path/to/save>.hprof

jmap 默认 dump 所有的对象,jcmd 默认 dump live-objects。因此一般情况下 jcmd 更快。

统计 classloader

前面的是统计对堆内存,对于 Meta space,则可以使用使用 jmap -clsstats 命令查看各个加载器加载了多少个 class,以及 class 占用的 bytes。

jmap -clstats <pid>

输出样例如下,内容有删减:

class_loader        classes	bytes	parent_loader	alive?	type

<bootstrap>         588	    1101559	null  	live	<internal>
0x000000076d6600e0  4       5106	0x000000076d63d7a8	live	sun/misc/Launcher$AppClassLoader@0x00000007c000f958
...
有问题吗?点此反馈!

温馨提示:反馈需要登录