jmap - Java 内存映像工具

jmap(Memory Map for Java) 命令用于生成堆转储快照(一般称为 heapdump 或 dump 文件)。目前获取 Java 堆转储快照的方式有以下几种:

  1. 使用 -XX:+HeapDumpOnOutOfMemoryError 参数让 Java 虚拟机在出行 OOM 之后自动生成 dump 文件。如下面测试程序:

    import java.util.ArrayList;
    import java.util.List;
    
    public class JMapOom {
      private static final int _1M = 1024 * 1024;
    
      public static void main(String[] args) throws InterruptedException {
        List<Byte[]> list = new ArrayList<>();
        while (true) {
          list.add(new Byte[_1M]);
          Thread.sleep(1000);
        }
      }
    }
    

    使用命令 java -XX:+HeapDumpOnOutOfMemoryError -Xmx20m JMapOom 运行上面代码,很快就会出现 oom 并自动生成 dump 文件:

    java.lang.OutOfMemoryError: Java heap space
    Dumping heap to java_pid9661.hprof ...
    Heap dump file created [34443383 bytes in 0.068 secs]
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
        at JMapOom.main(JMapOom.java:10)
    
  2. 使用 -XX:+HeapDumpOnCtrlBreak( 1.4.2_12 或 1.5.0_14 及更高版本,且 1.6 之前的版本) 参数然后用 Ctrl+Break 键让虚拟机生成 dump 文件

  3. 在 linux 系统下使用 kill -3 命令发出进程退出信号,拿到 dump 文件,如我们先用命令 java -Xmx200m JMapOom(这里把内存设置大些为了避免程序过早由于内存不足自动退出),然后我们找到其进程号,执行 kill -3 pid ,看下控制台:

    2018-04-24 15:28:08
    Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.152-b16 mixed mode):
    
    "Service Thread" #9 daemon prio=9 os_prio=0 tid=0x00007fc9100e5000 nid=0x2717 runnable [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C1 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007fc9100c7800 nid=0x2716 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007fc9100c5800 nid=0x2715 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fc9100c3800 nid=0x2714 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fc9100c0800 nid=0x2713 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fc9100bf000 nid=0x2712 waiting on condition [0x0000000000000000]
       java.lang.Thread.State: RUNNABLE
    
    "Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fc91008c000 nid=0x2711 in Object.wait() [0x00007fc8f97ff000]
       java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000ff0084f0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000000ff0084f0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
    
    "Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fc910087800 nid=0x2710 in Object.wait() [0x00007fc8f9900000]
       java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000ff000720> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000ff000720> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
    
    "main" #1 prio=5 os_prio=0 tid=0x00007fc910009000 nid=0x2706 waiting on condition [0x00007fc916d51000]
       java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at JMapOom.main(JMapOom.java:11)
    
    "VM Thread" os_prio=0 tid=0x00007fc91007f800 nid=0x270f runnable 
    
    "GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fc91001e800 nid=0x2707 runnable 
    
    "GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fc910020000 nid=0x2708 runnable 
    
    "GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fc910022000 nid=0x2709 runnable 
    
    "GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fc910024000 nid=0x270a runnable 
    
    "GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007fc910025800 nid=0x270b runnable 
    
    "GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007fc910027800 nid=0x270c runnable 
    
    "GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007fc910029000 nid=0x270d runnable 
    
    "GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007fc91002b000 nid=0x270e runnable 
    
    "VM Periodic Task Thread" os_prio=0 tid=0x00007fc9100ea000 nid=0x2718 waiting on condition 
    
    JNI global references: 6
    
    Heap
     PSYoungGen      total 59904K, used 18927K [0x00000000fbd80000, 0x0000000100000000, 0x0000000100000000)
      eden space 51712K, 27% used [0x00000000fbd80000,0x00000000fcb7fef8,0x00000000ff000000)
      from space 8192K, 56% used [0x00000000ff000000,0x00000000ff47c020,0x00000000ff800000)
      to   space 8192K, 0% used [0x00000000ff800000,0x00000000ff800000,0x0000000100000000)
     ParOldGen       total 136704K, used 45064K [0x00000000f3800000, 0x00000000fbd80000, 0x00000000fbd80000)
      object space 136704K, 32% used [0x00000000f3800000,0x00000000f64020b0,0x00000000fbd80000)
     Metaspace       used 2626K, capacity 4486K, committed 4864K, reserved 1056768K
      class space    used 286K, capacity 386K, committed 512K, reserved 1048576K
    

jmap 的作用并不仅仅是为了获取 dump 文件,它还可以查询 finalize 执行队列、Java 堆和永久代的详细信息,如空间使用率、当前用的是哪种收集器等。

使用 jmap -help 查看 jmap 的使用说明:

[root@localhost ~]# jmap -help
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

下面是命令选项的说明:

选项 说明
<none> 打印与 Solaris pmap 相同的信息
-heap 显示 Java 堆的详细信息,如使用哪种回收期、参数配置、分代状况等。只在 Linux/Solaris 平台下有效
-histo[:live] 显示堆中对象统计信息,包括类、实例数量、合计容量
-clstats 打印类加载器的统计信息
-finalizerinfo 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。只在 Linux/Solaris 平台下有效
-dump:<dump-options> 生成 Java 堆转储快照,格式为: -dump:[live,]format=b,file=<:filename>,其中 live 子参数说明是否只 dump 出存活的对象
-F 当虚拟机进程对 -dump 选项没有响应时,可使用这个选项强制生成 dump 快照。只在 Linux/Solaris 平台下有效
-h/help 显示命令帮助信息
-J<flag> 传递参数给 jmap 启动的 jvm

文章摘自《深入理解Java虚拟机》第二版 周志明著,仅作为学习记录,书籍中用到的案例代码及描述有部分修改,但未改变原意。