==========
(接上文《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文件的详细信息,如下图所示:
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”等列名代表什么意义呢?这里我们进行一个比较详细的列明介绍(单位都是字节):
列名项 | 列含义说明 | 列名项 | 列含义说明 |
---|---|---|---|
E | 年轻代中Eden(伊甸园)已使用的占当前容量百分比 | O | 年老代已使用的占当前容量百分比 |
M | 元空间(MetaspaceSize)已使用的占当前容量百分比 | P | 持久代已使用的占当前容量百分比 (这个参数在JDK 1.8之前才有效) |
S0 | 年轻代中第一个survivor(幸存区)已使用的占当前容量百分比 | S1 | 年轻代中第二个survivor(幸存区)已使用的占当前容量百分比 |
S0C | 年轻代中第一个survivor(幸存区)的容量 | S1C | 年轻代中第二个survivor(幸存区)的容量 |
S0U | 年轻代中第一个survivor(幸存区)目前已使用空间 | S1U | 年轻代中第二个survivor(幸存区)目前已使用空间 |
EC | 年轻代中Eden(伊甸园)的容量 | EU | 年轻代中Eden(伊甸园)目前已使用空间 |
OC | 年老代的容量 | OU | 年老代目前已使用空间 |
MC | 当前元数据空间大小 | MU | 当前元数据空间已使用大小 |
CCS | 压缩使用比例 | ||
CCSC | 当前压缩类空间大小 | CCSU | 当前压缩类空间已使用大小 |
YGC | 年轻代垃圾回收次数 | YGCT | 年轻代垃圾回收消耗时间 |
FGC | 老年代GC次数 | FGCT | 老年代垃圾回收消耗时间 |
TT | 持有次数限制 | MTT | 最大持有次数限制 |
GCT | 垃圾回收消耗总时间 | DSS | 当前需要survivor(幸存区)的容量 (字节)(Eden区已满) |
NGCMN | 年轻代(young)中初始化(最小)的大小 | NGCMX | 年轻代(young)的最大容量 |
NGC | 年轻代(young)中当前的容量 | ||
OGCMN | 年老代中初始化(最小)的大小 | OGCMX | 年老代的最大容量 |
OGC | 年老代当前新生成的容量 | ||
MCMN | 最小元数据容量 | MCMX | 最大元数据容量 |
CCSMN | 最小压缩类空间大小 | CCSMX | 最大压缩类空间大小 |
=========================
(接下文)