==========
(接上文《JVM调试常用命令——jps、jinfo(1)》)

1.3、jmap命令(及配套的jhat)

jmap命令最大的作用是生成当前指定的Java进程的dump文件,这个dump文件有利于在正式生成环境下观察JVM运行的详细情况,这样才能着手找出可能的问题。jmap命令还可以查询finalize执行队列、Java堆的详细信息,例如查看新生代目前使用的是哪种收集器。

1.3.1、jmap命令概要

jmap命令的使用格式如下所示:

# 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

jmap可以使用如上“jmap [option] [server_id@]<remote server IP or hostname>”的命令格式监控远程服务器上的java进程信息,但是其最重要的还是option参数,我们首先介绍option参数的具体意义:

  • -<none> 这个意思是说,jmap可以不加任何option参数信息,只是指定Java进程的进程号。这种情况下,jmap命令将按照Linux操作系统进程内存分析命令pmap的相关性,输出内存分析结果。

  • -heap 改参数将输出当前指定java进程的堆内存概要信息。

  • -clstats 该参数将打印出当前java进程中,存在的每个类加载器,以及通过该类加载器已经完成加载的各类信息,包括但不限于类加载器的活动情况、已经加载的类数量、关联的父类加载器等等(class文件通过类加载器完成的载入、连接、验证初始化等过程可以在这个命令的输出详情中具体体现出来)。

  • finalizerinfo 该参数可打印出等待终结的对象信息,当Java进程在频繁进行Full GC的时候,可以通过该命令获取问题的排查依据。

  • -histo[:live] 该参数可以输出每个class的实例数目、内存占用、类全名等信息。如果live子参数加上后,只统计活的对象数量。该命令非常有用,举个例子,你可以使用该名了检查软件系统的某种设计模式是否符合设计预期。

  • -dump:<dump-options> 该参数代表了jmap最重要的功能,取得当前指定java进程堆内存中各个class实例的详细信息,并输出到指定文件。dump命令还有三个子参数分别是。live只分析输出目前有活动实例的class信息;format输出格式,默认为“b”,可以使用配套的分析软件进行分析;file子参数可以指定输出的文件,注意,如果输出文件已经存在,则可以使用-F 参数来强制执行命令。

1.3.2、jmap命令效果演示

下面我们来实际演示一下,jmap携带各种参数后的执行效果。首先使用一个jmap命令,打印出当前堆内存的概要信息,示例如下:

# jmap -heap 55236
.........这里省略一些信息
JVM version is 25.144-b01
using thread-local object allocation.
Parallel GC with 8 thread(s)
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 4213178368 (4018.0MB)
   NewSize                  = 88080384 (84.0MB)
   MaxNewSize               = 1404043264 (1339.0MB)
   OldSize                  = 176160768 (168.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 691011584 (659.0MB)
   used     = 587018448 (559.8244171142578MB)
   free     = 103993136 (99.17558288574219MB)
   84.95059440277053% used
From Space:
   capacity = 24117248 (23.0MB)
   used     = 0 (0.0MB)
   free     = 24117248 (23.0MB)
   0.0% used
To Space:
   capacity = 23592960 (22.5MB)
   used     = 0 (0.0MB)
   free     = 23592960 (22.5MB)
   0.0% used
PS Old Generation
   capacity = 167247872 (159.5MB)
   used     = 43639424 (41.6177978515625MB)
   free     = 123608448 (117.8822021484375MB)
   26.09266322981975% used
30578 interned Strings occupying 3522224 bytes.

通过打印出来的堆概要信息,我们大致可以得到包括以下描述在内的关键信息:JVM使用Parallel GC垃圾回收器,这是一种吞吐量优先的,年轻代和年老代都可以使用的并行回收器。另外我们可以看到,年轻代Eden区域的容量为659.0MB,其中已经使用了84.9%(看来不久的将来就会进行一次基于复制算法的回收动作)……

我们还可以通过以下命令,观察指定的Java进程历史上和目前存在的类加载器信息。如下所示:

# jmap -clstats 13518
Attaching to process ID 13518, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11
finding class loader instances ..done.
computing per loader stat ..done.
please wait.. computing liveness.....liveness analysis may be inaccurate ...
class_loader    classes bytes   parent_loader   alive?  type
<bootstrap>     2687    4562634   null          live    <internal>
0x00000006c7c4c7c0      1       1473    0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078aa6e2a0      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c9c78dc0      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
………………节约篇幅,这里省去一些信息                                                                                  c0767d98
0x00000006c9c937a0      1       1472    0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c7c4cba8      1       1476    0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c9c791a8      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c9c933b8      1       1485    0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c8da34a8      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c7c4b9b0      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078a951ce0      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078aaf6ad8      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c8da46a0      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c9c79fb8      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078aa80428      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
………………节约篇幅,这里省去一些信息                                                                                  c0767d98
0x000000078752ef98      1       880       null          dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c7c4d508      1       1472    0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078a953658      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c9c79b08      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x0000000788887258      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x0000000788891658      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000006c7c2af10      10148   15865581        0x00000006c7c1cab0      live    org/springframework/boot/loader/LaunchedURLClass                                                                                                             Loader@0x00000007c0060828
0x00000006c8da3e08      1       1472    0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x00000007888a1c58      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078aa38470      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
………………节约篇幅,这里省去一些信息                                                                                  c0767d98
0x00000006c8da5320      1       1472      null          dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078aa4cb58      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x0000000788ddab20      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8
0x000000078a950368      1       880     0x00000006c7c2af10      dead    sun/reflect/DelegatingClassLoader@0x00000007c0009df8

total = 289     13459   21804150            N/A         alive=16, dead=273          N/A

通过以上命令示例我们可以看到,目前指定的Java进程一共有289个类加载器,其中已失效273个,目前存活16个,以上结果中有一个名叫<bootstrap>的类加载器,这个就是我们常说来的Bootstrap ClassLoader加载器,可以看到通过这个加载器完成的class文件加载是最多的,且它之上在没有父级加载器了。

接下来在做一个示例,通过-histo:live参数检查目前系统中各个class的实例加载数量以及当前占用的内存情况。请注意,由于“jmap -histo:live”命令输出的内容非常多,所以一般来说我们会将这个命令的输出转存到指定的文件中,再进行排查。命令示例如下:

# jmap -histo:live 13518 >> ./histo_live
// 接下来可以使用vim命令,查看输出情况,并进行分析
# vim ./histo_live
 num     #instances         #bytes  class name
----------------------------------------------
   1:         83445       12138320  [C
   2:         24921        2193048  java.lang.reflect.Method
   3:         81976        1967424  java.lang.String
   4:         53592        1714944  java.util.concurrent.ConcurrentHashMap$Node
   5:          6321        1502344  [I
   6:         13505        1500784  java.lang.Class
   7:          2457        1079936  [B
   8:         19475         779000  java.util.LinkedHashMap$Entry
   9:         14483         719080  [Ljava.lang.Object;
  10:         21744         695808  java.lang.ref.WeakReference
  11:          8495         669560  [Ljava.util.HashMap$Node;
  12:         20190         646080  java.util.HashMap$Node
  13:         36296         580736  java.lang.Object
  14:         10217         572152  java.util.LinkedHashMap
  15:           314         471296  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  16:         10221         408840  java.lang.ref.SoftReference
  17:         17904         384440  [Ljava.lang.Class;
  18:         12172         292128  java.beans.MethodRef
  19:          8286         256704  [Ljava.lang.String;
  20:          4320         241920  java.beans.MethodDescriptor
  21:          9191         220584  java.util.ArrayList
  22:          7699         184776  org.springframework.core.MethodClassKey
  23:          5385         172320  java.util.LinkedList
  24:          1700         163200  org.springframework.beans.GenericTypeAwarePropertyDescriptor
  25:          2224         160128  java.beans.PropertyDescriptor
  26:          3291         157968  java.util.HashMap
  27:          1766         141280  java.lang.reflect.Constructor
  28:          1463         105336  java.lang.reflect.Field
  29:            91          87096  [J
  30:          3569          85656  java.util.LinkedList$Node
  31:          1104          79488  org.springframework.core.annotation.AnnotationAttributes
  32:          1929          77160  java.util.TreeMap$Entry
  33:          1189          76096  org.springframework.core.MethodParameter
  34:          4548          72768  java.lang.Integer
  35:          4410          70560  java.util.LinkedHashSet
  …………………… 后面的内容还有很多,为了节约篇幅就省略了。

接下来你就可以使用vim中的查看命令,检查你需要观察的相关class的实例数量。前文已经说到,使用jmap最主要的功能是生成JVM堆内存中class实例化对象的各种详细信息的Dump文件(实际上就是以上各种jmap命令形态输出结果的Dump信息表现),以下命令使用示例就可以做这个事情,如下:

# jmap -dump:live,format=b,file=./dump 13518
Dumping heap to /root/dump ...
Heap dump file created

1.3.4、jmap配套使用的jhat命令

请注意,使用“jmap -dump”命令生成的dump文件是一种二进制格式的表达,你用vim命令(或者相似命令)是无法看到分析结果的。这里就要介绍另外一种配套命令jhat了。通过jhat命令,我们可以基于jmap的dump文件启动一个Http服务,并在浏览器上检查dump信息。命令示例如下:

# jhat -port 5700 ./dump
Reading from ./dump...
Dump file created Mon Nov 19 14:46:03 CST 2018
Snapshot read, resolving...
Resolving 631850 objects...
Chasing references, expect 126 dots.......................................................
Eliminating duplicate references................................................................................
Snapshot resolved.
Started HTTP server on port 5700
Server is ready.

可以看到,服务已经启动成功了,接下来我们可以通过浏览器观察dump文件的详细信息,如下图所示:

JVM调试常用命令——jmap、jstat(2)-LMLPHP

jhat 命令非常简单,限于本文篇幅这里就不再具体介绍了,有兴趣的朋友可以查阅各种网络资料进行详细了解(这里要说明一下,其中有一个OQL对象查询语言,很有意思)。

1.4、jstat命令

1.4.1、基本参数介绍

jstat命令可以查看堆内存各部分的使用量,以及加载类的数量。命令格式如下:

Usage: jstat -help|-options
       jstat -<option> [-t] [-h<lines>] <vmid> [<interval> [<count>]]

Definitions:
  <option>      An option reported by the -options option
  <vmid>        Virtual Machine Identifier. A vmid takes the following form:
                     <lvmid>[@<hostname>[:<port>]]
                Where <lvmid> is the local vm identifier for the target
                Java virtual machine, typically a process id; <hostname> is
                the name of the host running the target Java virtual machine;
                and <port> is the port number for the rmiregistry on the
                target host. See the jvmstat documentation for a more complete
                description of the Virtual Machine Identifier.
  <lines>       Number of samples between header lines.
  <interval>    Sampling interval. The following forms are allowed:
                    <n>["ms"|"s"]
                Where <n> is an integer and the suffix specifies the units as
                milliseconds("ms") or seconds("s"). The default units are "ms".
  <count>       Number of samples to take before terminating.
  -J<flag>      Pass <flag> directly to the runtime system.

请注意以上使用示例中的option参数,在jstat命令基本的帮助信息中,并没有对option支持的参数情况进行说明,但是读者可以使用以下命令,列出jstat命令可以使用的option参数:

# jstat -options
-class # 显示加载class的数量,及所占空间等信息
-compiler # 主要显示JIT编译器已经编译过的方法、耗时等信息
-gc # 可以显示详细的gc信息,查看gc的次数,这个参数代表的各个显示列,将在后文中进行详细说明
-gccapacity # 可以显示JVM各个内存结构中的容量情况,该参数也将在后文中进行详细说明
-gcnew # 可以显示新生代中的GC情况
-gcnewcapacity # 显示新生代中和容量有关的各种情况
-gcold # 可以显示年老代中的GC情况
-gcoldcapacity # 显示年老代中和容量有关的各种情况
-gccause # 可以显示最近一次GC的原因
-gcmetacapacity # 可以显示有关元数据空间大小的统计信息
-gcutil # 主要监控GC情况中,已使用容量和总容量的各种百分比情况
-printcompilation # 输入已经被JIT编译过的方法

jstat命令非常重要,有了以上jstat命令的基本参数介绍,我们就可以开始正式使用jstat命令了。以下是一个使用-gc参数时的简单示例(监控指定进程的GC回收情况):

// 以下命令中,84256代表指定的java进程号、1000代表每一秒(1000毫秒)输出一次监控信息
# jstat -gc 84256  1000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
10752.0 10752.0  0.0    0.0   64512.0  35554.9   172032.0     0.0     4480.0 781.2  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   64512.0  35554.9   172032.0     0.0     4480.0 781.2  384.0   75.9       0    0.000   0      0.000    0.000

接着再来一个简单示例(检视指定进程中年轻代的GC情况):

jstat -gcnew 84256  1000
 S0C    S1C    S0U    S1U   TT MTT  DSS      EC       EU     YGC     YGCT
10752.0 10752.0    0.0    0.0 15  15    0.0  64512.0  35554.9      0    0.000
10752.0 10752.0    0.0    0.0 15  15    0.0  64512.0  35554.9      0    0.000
10752.0 10752.0    0.0    0.0 15  15    0.0  64512.0  35554.9      0    0.000

再来一个简单示例(检视GC操作下个内存结构中的容量情况):

jstat -gccapacity  84256 1000
 NGCMN    NGCMX     NGC     S0C   S1C       EC      OGCMN      OGCMX       OGC         OC       MCMN     MCMX      MC     CCSMN    CCSMX     CCSC    YGC    FGC
 86016.0 1371136.0  86016.0 10752.0 10752.0  64512.0   172032.0  2743296.0   172032.0   172032.0      0.0 1056768.0   4480.0      0.0 1048576.0    384.0      0     0
 86016.0 1371136.0  86016.0 10752.0 10752.0  64512.0   172032.0  2743296.0   172032.0   172032.0      0.0 1056768.0   4480.0      0.0 1048576.0    384.0      0     0
 86016.0 1371136.0  86016.0 10752.0 10752.0  64512.0   172032.0  2743296.0   172032.0   172032.0      0.0 1056768.0   4480.0      0.0 1048576.0    384.0      0     0

最后再来一个示例(监控GC情况中,已使用容量和总容量的各种百分比情况):

# jstat -gcutil 84256 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00  55.11   0.00  17.44  19.76      0    0.000     0    0.000    0.000
  0.00   0.00  55.11   0.00  17.44  19.76      0    0.000     0    0.000    0.000
  0.00   0.00  55.11   0.00  17.44  19.76      0    0.000     0    0.000    0.000

1.4.2、jstat列名介绍

额,各位读者是不是觉得完全一头雾水,以上“EC”、“EU”等列名代表什么意义呢?这里我们进行一个比较详细的列明介绍(单位都是字节):

=========================
(接下文)

11-25 18:14