JVM 监控与诊断工具

😏 前方多图多代码块预警!!!

前言

Java 虚拟机是一个复杂的系统,如果这个复杂的系统是一个黑盒子,那一旦出现了问题那将是非常棘手的,我们将没有任何的方式方法来定位问题,这是不可接受的。因此 JDK 内置了很多的工具去诊断分析问题,这里面不仅包括一些命令行工具还有一些图形化工具,比如命令行有常见的 jpsjinfojstatjmap等,图形化工具也有 jconsolejvisualvmjmc等。当然实际上生产上这类工具用的还是比较少的。这些工具了就好比是菜刀队了,而正常情况下我们定位问题都是有机枪大炮的。在工具完备准备充分的情况下,我们通常会使用 Prometheus 配上集成可视化工具 Grafana 或 DataDog。出现问题之后,会使用一些分析工具,如 Eclipse MAT再结合一些在线工具进行数据分析如 GCEasy 和 FastThread。

为什么需要监控,监控的又是什么?

我们的重要工作之一就是要保证我们写出来的程序是正确的且健壮的,不能因为一些小小的问题代码就变得不可用,这是不能接受的。但是我们如何发现我们的系统出现了异样呢?很简单就是监控它,类比到人类世界,就是我们的小区保安是如何增强我们的安全感的呢?也是通过监控的手段,只不过保安监控的是小区内部的情况,而我们监控的是JVM的运行指标。这些指标包括一些常见的运行时数据,比如我们前面提到的堆栈的占用数据,以及我们后面会提到的GC有关数据,当然还有我们服务器本身的内存和 CPU 负载情况等。只有有了这些数据,我们才能还原异常情况发生时的系统环境。这就是像是还原凶杀现场一样刺激。分享一个真实的案例,我们公司之前的系统是没有监控的,所有的服务就是一个单体系统并且整体代码质量也不高,有一次上线一个版本然后第二天下午业务高峰的时候,JVM 突然开始宕机所有服务全部挂掉。当时我们是单体服务,JVM 也没有配置任何的可以导出堆栈信息的JVM参数,但是业务是在高峰期不能停,所以我们只有重启,重启完过了10分钟继续挂掉,然后接着重启,服务长时间处于不可用状态。这就像是凶案现场凶手作案后,凶杀现场却一丝痕迹都没留下,不仅杀伤力巨大而且侮辱性极强。 所以再回到这个问题上来,为什么需要监控,因为我们要保证系统的稳定运行、需要对系统进行性能分析、需要在系统出现异常时保留足够的信息,系统的监控必不可少。

JDK 内置工具

我们这里主要要监控的是JVM,虽然一般第一直觉想到的都是一些高大上的监控工具,但是JDK本身也提供了很多的工具。这里面不仅有命令行工具还有一些图形化工具。

命令行工具

以下的测试全部基于 jdk11 输出结果,测试使用 jdk8 时基本全部翻车。

jps

jps 查看当前系统中的 Java 进程。这个命令存在用户权限隔离,也就是 root 用户能看到所有的 Java 进程,其他的用户只能看到自己的 Java 的进程。这里可以看到我启动了 HelloServer 和 CxfDemoApplication 其中前面的数字 5455 和 5350 是他们的pid

1
2
3
4
5
6
7
8
9
10
11
12
13
14
daiwei@daiweideMacBook-Pro ~ % jps -help
usage: jps [-help]
jps [-q] [-mlvV] [<hostid>]

Definitions:
<hostid>: <hostname>[:<port>]

daiwei@daiweideMacBook-Pro ~ % jps
5780 Jps
5350 CxfDemoApplication
1560 RemoteMavenServer36
394
5454 Launcher
5455 HelloServer

jinfo

这个命令的全程是 Java Configuration Info,所以它的主要作用是实时查看和调整JVM配置参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
daiwei@daiweideMacBook-Pro ~ % jinfo -help
Usage:
jinfo [option] <pid>
(to connect to running process)
jinfo [option] <executable <core>
(to connect to a core file)
jinfo [option] [server_id@]<remote server IP or hostname>
(to connect to remote debug server)

where <option> is one of:
-flag <name> to print the value of the named VM flag
-flag [+|-]<name> to enable or disable the named VM flag
-flag <name>=<value> to set the named VM flag to the given value
-flags to print VM flags
-sysprops to print Java system properties
<no option> to print both of the above
-h | -help to print this help message
daiwei@daiweideMacBook-Pro ~ % jinfo -flalgs 8386
Java System Properties:
#Thu May 20 23:25:31 CST 2021
java.runtime.name=Java(TM) SE Runtime Environment
sun.boot.library.path=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib
java.vm.version=25.231-b11
gopherProxySet=false
java.vm.vendor=Oracle Corporation
java.vendor.url=http\://java.oracle.com/
path.separator=\:
java.vm.name=Java HotSpot(TM) 64-Bit Server VM
file.encoding.pkg=sun.io
user.country=CN
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=unknown
java.vm.specification.name=Java Virtual Machine Specification
user.dir=/Users/daiwei/github/thinking-in-code
java.runtime.version=1.8.0_231-b11
java.awt.graphicsenv=sun.awt.CGraphicsEnvironment
java.endorsed.dirs=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/endorsed
os.arch=x86_64
java.io.tmpdir=/var/folders/4_/dbqw6z6100535snfk5508v080000gn/T/
line.separator=\n
java.vm.specification.vendor=Oracle Corporation
os.name=Mac OS X
sun.jnu.encoding=UTF-8
java.library.path=/Users/daiwei/Library/Java/Extensions\:/Library/Java/Extensions\:/Network/Library/Java/Extensions\:/System/Library/Java/Extensions\:/usr/lib/java\:.
sun.nio.ch.bugLevel=
java.specification.name=Java Platform API Specification
java.class.version=52.0
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
os.version=10.16
user.home=/Users/daiwei
user.timezone=Asia/Shanghai
java.awt.printerjob=sun.lwawt.macosx.CPrinterJob
file.encoding=UTF-8
java.specification.version=1.8
java.class.path=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/deploy.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/cldrdata.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/dnsns.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jaccess.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jfxrt.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/localedata.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/nashorn.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunec.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/zipfs.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/javaws.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfxswt.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/management-agent.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/plugin.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/ant-javafx.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/dt.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/javafx-mx.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/jconsole.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/packager.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/sa-jdi.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/tools.jar\:/Users/daiwei/github/thinking-in-code/thinking-in-grpc/target/classes\:/Users/daiwei/.m2/repository/io/grpc/grpc-netty-shaded/1.34.1/grpc-netty-shaded-1.34.1.jar\:/Users/daiwei/.m2/repository/io/grpc/grpc-core/1.34.1/grpc-core-1.34.1.jar\:/Users/daiwei/.m2/repository/com/google/android/annotations/4.1.1.4/annotations-4.1.1.4.jar\:/Users/daiwei/.m2/repository/io/perfmark/perfmark-api/0.19.0/perfmark-api-0.19.0.jar\:/Users/daiwei/.m2/repository/io/grpc/grpc-protobuf/1.34.1/grpc-protobuf-1.34.1.jar\:/Users/daiwei/.m2/repository/io/grpc/grpc-api/1.34.1/grpc-api-1.34.1.jar\:/Users/daiwei/.m2/repository/io/grpc/grpc-context/1.34.1/grpc-context-1.34.1.jar\:/Users/daiwei/.m2/repository/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar\:/Users/daiwei/.m2/repository/com/google/protobuf/protobuf-java/3.14.0/protobuf-java-3.14.0.jar\:/Users/daiwei/.m2/repository/com/google/api/grpc/proto-google-common-protos/1.17.0/proto-google-common-protos-1.17.0.jar\:/Users/daiwei/.m2/repository/io/grpc/grpc-protobuf-lite/1.34.1/grpc-protobuf-lite-1.34.1.jar\:/Users/daiwei/.m2/repository/com/google/guava/guava/29.0-android/guava-29.0-android.jar\:/Users/daiwei/.m2/repository/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar\:/Users/daiwei/.m2/repository/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar\:/Users/daiwei/.m2/repository/org/checkerframework/checker-compat-qual/2.5.5/checker-compat-qual-2.5.5.jar\:/Users/daiwei/.m2/repository/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar\:/Users/daiwei/.m2/repository/com/google/errorprone/error_prone_annotations/2.3.4/error_prone_annotations-2.3.4.jar\:/Users/daiwei/.m2/repository/org/codehaus/mojo/animal-sniffer-annotations/1.18/animal-sniffer-annotations-1.18.jar\:/Users/daiwei/.m2/repository/io/grpc/grpc-stub/1.34.1/grpc-stub-1.34.1.jar\:/Users/daiwei/.m2/repository/com/google/protobuf/protobuf-java-util/3.12.0/protobuf-java-util-3.12.0.jar\:/Users/daiwei/.m2/repository/com/google/code/gson/gson/2.8.6/gson-2.8.6.jar\:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
user.name=daiwei
java.vm.specification.version=1.8
sun.java.command=io.daiwei.grpc.HelloServer
java.home=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre
sun.arch.data.model=64
user.language=zh
java.specification.vendor=Oracle Corporation
awt.toolkit=sun.lwawt.macosx.LWCToolkit
java.vm.info=mixed mode
java.version=1.8.0_231
java.ext.dirs=/Users/daiwei/Library/Java/Extensions\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext\:/Library/Java/Extensions\:/Network/Library/Java/Extensions\:/System/Library/Java/Extensions\:/usr/lib/java
sun.boot.class.path=/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/sunrsasign.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar\:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/classes
java.vendor=Oracle Corporation
file.separator=/
java.vendor.url.bug=http\://bugreport.sun.com/bugreport/
sun.io.unicode.encoding=UnicodeBig
sun.cpu.endian=little
sun.cpu.isalist=

VM Flags:
-XX:CICompilerCount=4 -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=4294967296 -XX:MaxNewSize=1431306240 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=89128960 -XX:OldSize=179306496 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC

VM Arguments:
jvm_args: -javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51341:/Applications/IntelliJ IDEA.app/Contents/bin -Dfile.encoding=UTF-8
java_command: io.daiwei.grpc.HelloServer
java_class_path (initial): /Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_231.jdk/Contents/Home
Launcher Type: SUN_STANDARD

jstat

这是我个人比较喜欢的一个命令,也是用的稍微多那么一些的命令,就像这个名字一样,jstat 可以输出一些 jvm 的状态信息。其中主要用它可以实时地输出 GC 有关数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
daiwei@daiweideMacBook-Pro ~ % 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.
-? -h --help Prints this help message.
-help Prints this help message.

daiwei@daiweideMacBook-Pro ~ % jstat -options
-class # 类加载(Class loader)信息统计
-compiler # JIT即时编译器相关统计信息
-gc # GC相关的堆内存信息,用法 jstat -gc -h 10 -t 864 1s 20
-gccapacity # 各个内存池分代空间的容量
-gccause # 看上次GC,本次GC(如果正在GC中)的原因,其他输出和 -gcutil 的选项一致
-gcmetacapacity # 元数据区大小统计
-gcnew # 年轻代的统计信息,(NEW = YOUNG = Eden + S0 + S1)
-gcnewcapacity # 年轻代空间大小统计
-gcold # 老年代和元数据区的行为统计
-gcoldcapacity # 老年代的空间统计
-gcutil # GC相关区域的使用率(utillization)统计
-printcompilation # 打印JVM编译统计信息


jstat -gcutil 11064 1000 15
S0 S1 E O M CCS YGC YGCT FGC FGCT CGC CGCT GCT
37.50 0.00 26.20 5.81 97.40 94.92 156 0.200 0 0.000 - - 0.200
37.50 0.00 99.77 5.81 97.40 94.92 156 0.200 0 0.000 - - 0.200
43.75 0.00 0.00 6.00 96.34 94.93 164 0.209 0 0.000 - - 0.209
0.00 43.75 0.00 6.18 96.34 94.93 171 0.216 0 0.000 - - 0.216
50.00 0.00 49.84 6.34 96.34 94.93 178 0.223 0 0.000 - - 0.223
0.00 37.50 53.75 6.49 96.34 94.93 185 0.231 0 0.000 - - 0.231
0.00 50.00 0.00 6.69 96.34 94.93 193 0.239 0 0.000 - - 0.239
25.00 0.00 14.03 6.80 96.34 94.93 200 0.246 0 0.000 - - 0.246
50.00 0.00 32.01 6.93 96.34 94.93 206 0.253 0 0.000 - - 0.253
50.00 0.00 47.99 7.08 96.34 94.93 212 0.259 0 0.000 - - 0.259
50.00 0.00 27.80 7.21 96.34 94.93 218 0.266 0 0.000 - - 0.266
37.50 0.00 57.97 7.34 96.34 94.93 224 0.272 0 0.000 - - 0.272
37.50 0.00 57.97 7.34 96.34 94.93 224 0.272 0 0.000 - - 0.272
37.50 0.00 57.97 7.34 96.34 94.93 224 0.272 0 0.000 - - 0.272
37.50 0.00 57.97 7.34 96.34 94.93 224 0.272 0 0.000 - - 0.272

daiwei@daiweideMacBook-Pro ~ % jstat -gc 11064 1000 15
S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT CGC CGCT GCT
512.0 512.0 192.0 0.0 31232.0 19405.0 175104.0 12854.1 20736.0 19977.0 2816.0 2673.3 224 0.272 0 0.000 - - 0.272
512.0 512.0 192.0 0.0 31232.0 19405.1 175104.0 12854.1 20736.0 19977.0 2816.0 2673.3 224 0.272 0 0.000 - - 0.272
512.0 512.0 192.0 0.0 31232.0 19405.1 175104.0 12854.1 20736.0 19977.0 2816.0 2673.3 224 0.272 0 0.000 - - 0.272
512.0 512.0 160.0 0.0 31232.0 5617.8 175104.0 13354.2 20736.0 20071.3 2816.0 2674.4 230 0.279 0 0.000 - - 0.279
512.0 512.0 0.0 224.0 31232.0 10477.4 175104.0 13674.2 20736.0 20073.0 2816.0 2674.4 237 0.286 0 0.000 - - 0.286
512.0 512.0 256.0 0.0 31232.0 21876.8 175104.0 13986.2 20736.0 20073.0 2816.0 2674.4 244 0.294 0 0.000 - - 0.294
512.0 512.0 224.0 0.0 31232.0 0.0 175104.0 14306.2 20736.0 20073.0 2816.0 2674.4 252 0.303 0 0.000 - - 0.303
512.0 512.0 0.0 256.0 31232.0 11824.0 175104.0 14658.2 20736.0 20073.0 2816.0 2674.4 259 0.310 0 0.000 - - 0.310
512.0 512.0 0.0 256.0 31232.0 0.0 175104.0 15042.2 20736.0 20073.8 2816.0 2674.4 267 0.318 0 0.000 - - 0.318
512.0 512.0 256.0 0.0 31232.0 6867.3 175104.0 15330.2 20736.0 20073.8 2816.0 2674.4 274 0.326 0 0.000 - - 0.326
512.0 512.0 224.0 0.0 31232.0 15562.0 175104.0 15562.2 20736.0 20073.8 2816.0 2674.4 280 0.332 0 0.000 - - 0.332
512.0 512.0 0.0 160.0 31232.0 0.0 175104.0 15842.2 20736.0 20073.8 2816.0 2674.4 287 0.340 0 0.000 - - 0.340
512.0 512.0 0.0 192.0 31232.0 7477.3 175104.0 16090.2 20736.0 20074.2 2816.0 2674.4 293 0.346 0 0.000 - - 0.346
512.0 512.0 224.0 0.0 31232.0 21846.6 175104.0 16138.2 20736.0 20074.2 2816.0 2674.4 294 0.347 0 0.000 - - 0.347
512.0 512.0 224.0 0.0 31232.0 21846.6 175104.0 16138.2 20736.0 20074.2 2816.0 2674.4 294 0.347 0 0.000 - - 0.347

上面是一次持续 10 秒的服务压测,可以看到一些数据持续不断的变化。-gc 和-gcutil 的参数都是一样的 11064 是 pid,1000 是打印时间间隔单位 ms,15 是打印次数,打印15次。

gcutil 的 输出参数:

  • S0 0 号 Survivor 区的使用百分比(S0 和 S1 总有一个是空的)。
  • S1 1 号 Survivor 区的使用百分比。
  • E Eden 区,也就是新生代的使用百分比。
  • O Old 区老年代的使用百分比。
  • M Meta区元数据区的使用百分比。
  • CSS 压缩class空间(Compress class space)的使用百分比。
  • YGC youngGC 次数
  • YGCT youngGC 总耗时,单位秒
  • FGC fullGC 的次数,可以看到 fullGC 一次都没有。
  • FGCT fullGC 的总数时间,一次FullGC 都没有,所以FullGC 时间为 0
  • CGC concurrentGC 并发垃圾收集次数,因为 jdk8 的默认回收器是 parallelGC 没有并发阶段所以这里是 -
  • CGCT concurrentGC 收集总时间。
  • GCT 所有 GC 加在一起的时间。

其实 -gcutil 和 -gc 输出逻辑是一致的,只是 -gc 输出的实际占用的大小单位 kb,并且 -gc 输出的都是 XXUXXC 这代表着 XX UsageXX Capacity 的意思。

jmap

用于输出系统 JVM 堆信息。用的最多的两个命令 -histo-dump 。其中-histo(柱状图)输出当前堆中的对象并按照占用空间从大到小排序。-dump dump 当前堆信息。jmap 在 jdk8 中还有一个 -heap 命令,但是在这一版的 jdk11 中移除了,但是我们还是有办法输出 -heap 的内容,别着急往下看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
daiwei@daiweideMacBook-Pro ~ % jmap -help
Usage:
jmap -clstats <pid>
to connect to running process and print class loader statistics
jmap -finalizerinfo <pid>
to connect to running process and print information on objects awaiting finalization
jmap -histo[:live] <pid>
to connect to running process and print histogram of java object heap
if the "live" suboption is specified, only count live objects
jmap -dump:<dump-options> <pid>
to connect to running process and dump java heap
jmap -? -h --help
to print this help message

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>

daiwei@daiweideMacBook-Pro ~ % jmap -histo 33208

num #instances #bytes class name
----------------------------------------------
1: 106522 4260880 java.lang.ref.Finalizer
2: 106222 4248880 java.util.WeakHashMap$Entry
3: 106200 3398400 java.lang.ref.WeakReference
4: 4569 2491656 [I
5: 67343 2154976 com.sun.jna.Memory
6: 46271 1698896 [Ljava.lang.Object;
7: 28666 1330904 [C
8: 13698 1271480 [B
9: 31081 1243240 com.sun.jna.NativeString$StringMemory
10: 14391 1036744 [J
11: 31081 745944 com.sun.jna.NativeString
12: 7526 613808 [Ljava.util.HashMap$Node;
13: 46 527904 [Ljava.util.WeakHashMap$Entry;
14: 20227 485448 java.lang.String
15: 6157 443304 java.lang.reflect.Field
16: 7534 361632 java.util.HashMap
17: 12098 290352 java.util.ArrayList
18: 7771 248672 com.sun.jna.Structure$AutoAllocated
19: 15131 242096 java.lang.Integer
20: 7074 226368 java.util.HashMap$Node
21: 3969 190512 [Loshi.hardware.CentralProcessor$TickType;
22: 2987 167272 java.util.LinkedHashMap
23: 1277 146176 java.lang.Class
24: 5978 144200 [Ljava.lang.reflect.Field;
......
517: 1 16 sun.util.resources.LocaleData
518: 1 16 sun.util.resources.LocaleData$LocaleDataResourceBundleControl
Total 710496 29890984

daiwei@daiweideMacBook-Pro ~ % jmap -dump:format=b,file=dump.hprof 34471
Heap dump file created

jstack

jmap 可以输出堆空间的信息,jstack 就是输出栈空间的信息。最主要使用的-l即打印 jvm 中的锁信息,-e打印 jvm 中的线程信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
daiwei@daiweideMacBook-Pro ~ % jstack -help
Usage:
jstack [-l][-e] <pid>
(to connect to running process)

Options:
-l long listing. Prints additional information about locks
-e extended listing. Prints additional information about threads
-? -h --help -help to print this help message

daiwei@daiweideMacBook-Pro ~ % jstack -l 34979
2021-05-22 21:49:23
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.231-b11 mixed mode):

"Attach Listener" #11 daemon prio=9 os_prio=31 tid=0x00007ff5270d5800 nid=0xa803 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"Service Thread" #10 daemon prio=9 os_prio=31 tid=0x00007ff526079800 nid=0x3e03 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

"C1 CompilerThread3" #9 daemon prio=9 os_prio=31 tid=0x00007ff526040800 nid=0x3c03 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE

Locked ownable synchronizers:
- None

.....

"main" #1 prio=5 os_prio=31 tid=0x00007ff528809000 nid=0xf03 waiting on condition [0x0000700001c52000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at io.daiwei.TestCPULoadMain.main(TestCPULoadMain.java:21)

Locked ownable synchronizers:
- None

"VM Thread" os_prio=31 tid=0x00007ff528840800 nid=0x5003 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007ff52880a800 nid=0x2607 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007ff528816000 nid=0x2503 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007ff528816800 nid=0x2303 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007ff528817000 nid=0x2a03 runnable

"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007ff528817800 nid=0x5403 runnable

"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007ff528818800 nid=0x5203 runnable

"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007ff528819000 nid=0x2c03 runnable

"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007ff528819800 nid=0x2e03 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007ff528875000 nid=0x5503 waiting on condition

JNI global references: 375

jcmd

什么你说命令太多记不住?没关系。jcmd 是一个命令的聚合,里面有很多的 option,基本上前面的命令都可以用 jcmd 输出,来看看下面的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
daiwei@daiweideMacBook-Pro ~ % jcmd -help
Usage: jcmd <pid | main class> <command ...|PerfCounter.print|-f file>
or: jcmd -l
or: jcmd -h

command must be a valid jcmd command for the selected jvm.
Use the command "help" to see which commands are available.
If the pid is 0, commands will be sent to all Java processes.
The main class argument will be used to match (either partially
or fully) the class used to start Java.
If no options are given, lists Java processes (same as -l).

PerfCounter.print display the counters exposed by this process
-f read and execute commands from the file
-l list JVM processes on the local machine
-? -h --help print this help message

daiwei@daiweideMacBook-Pro ~ % jcmd 34979
34979:
The following commands are available:
JFR.stop
JFR.start
JFR.dump
JFR.check
VM.native_memory
VM.check_commercial_features
VM.unlock_commercial_features
ManagementAgent.stop
ManagementAgent.start_local
ManagementAgent.start
VM.classloader_stats
GC.rotate_log
Thread.print
GC.class_stats
GC.class_histogram
GC.heap_dump
GC.finalizer_info
GC.heap_info
GC.run_finalization
GC.run
VM.uptime
VM.dynlibs
VM.flags
VM.system_properties
VM.command_line
VM.version
help

For more information about a specific command use 'help <command>'.

# help 命令
daiwei@daiweideMacBook-Pro ~ % jcmd 34979 help GC.heap_info
34979:
GC.heap_info
Provide generic Java heap information.

Impact: Medium

Permission: java.lang.management.ManagementPermission(monitor)

Syntax: GC.heap_info

# cmd 命令使用 这里就是前面提到的,jmap 中移除的命令,输出堆内存信息。
daiwei@daiweideMacBook-Pro ~ % jcmd 34979 GC.heap_info
34979:
PSYoungGen total 76288K, used 47314K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
eden space 65536K, 55% used [0x000000076ab00000,0x000000076cebc930,0x000000076eb00000)
from space 10752K, 99% used [0x000000076eb00000,0x000000076f578010,0x000000076f580000)
to space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
ParOldGen total 175104K, used 4582K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
object space 175104K, 2% used [0x00000006c0000000,0x00000006c0479a48,0x00000006cab00000)
Metaspace used 7205K, capacity 7438K, committed 7552K, reserved 1056768K
class space used 759K, capacity 838K, committed 896K, reserved 1048576K

图形化工具

图形化工具可以以图表的形式更直观的把监控数据展现出来,JDK自带的图形化工具主要包括 jconsolejvisualvmjmc。这些工具都提供了丰富的功能,方便开发者对 JVM 进行监控。

jmc 在 JDK11 以上版被被移除,独立作为一个程序包提供。 JDK 11 依旧有 jmc 这个命令但是打不开。 jmc 我们下载了独立软件包,进行操作。同时下面的所有栗子都基于 jdk11 进行测试。

jconsole

在命令行输入jconsole即可打开,打开后可以选择本地 JVM 直接连接也可以选择,JMX 的方式远程连接 JVM。下面这张图就是打开本地 JVM 监控的概览页面,其中图中有6个 tab 页,分别是概览内存线程VM概要Mbean,从概述这个页面上能看到堆内存使用量、线程、类、CPU 占用率的系统指标数据。

  • 堆内存使用量 这里是前面提到的 Java 堆内存的使用情况。
  • 线程 当前 JVM 中活跃的线程数。
  • JVM 加载的类的个数。
  • CPU占用率 当前物理机的 CPU 使用情况。

图中还有多个时间范围的选项可以选择,包括但不限于 1分钟、5分钟、10分钟、30分钟、1小时、2小时 …… 1天、7天 …… 1个月 …… 1年。

第二个面板是内存 这个部分提供了很多的图表,展示了各个内存部分的使用情况。我这里被监控应用使用的是 jdk8 ,GC是默认的 parallelGC 所以这里展示 PS XXX 内存图表,还有一些 MetaSpace、CodeCache、CCS 非堆部分内存使用量图 。右上角有个执行GC的按钮,这个按钮可以直接触发 JVM 的 fullGC,右下的数据就是整个堆按照堆和非堆逻辑划分后,各个部分内存使用情况的柱状图,我们堆逻辑内存区的命名非堆也是从这来的。左下的详细信息则是堆空间 GC 的简单统计。

第三个面板是线程有关信息,上半部分是一个折线图,展示JVM中活跃和峰值的线程数量,下半部分是活跃的状态以及堆栈追踪。其中面板的下半部分有个死锁检测的按钮,可以用来检测当前JVM 中是否存在死锁。

第四个面板是类加载情况,上半部分是类加载数量的折线图,下半部分是详细数据的输出。

VM概述 这个部分展示虚拟机内部的一些概要信息,包括线程、堆栈内存、物理机的一些和JVM的参数等。

MBean 的是一些 manage Bean 的一些详情信息。

jvisualvm

jvisualvm 和 jconsole 都是 JDK 打包的监控图形化工具。这个监控工具我感觉用下来比 jconsole 更友好一些,jconsole 有个功能他基本都有,并且 jvisualvm 还增加了抽样器VM dump分析的功能,可以直接对运行的 JVM 进行取样分析。jvisualvm 的左边部分是应用程序的菜单栏,除了本地,我们还可以分析远程的JVM,VM核心 dump等,可以对本地或者取样出的 JVM 数据进行分析。我们这里选择 TestCPULoadMain 的 JVM 实例进行分析。在右边是详情页面,上面有几个tab页面,有概述监控线程抽样器Profiler,其中抽样器是用来做抽样分析,可以抽样堆空间数据和线程的数据,Profiler 用来进行性能抽样分析。

这里很直接的感受到 jvisualvm 的一个优点,jvisualvm 是彩色的!!jvisualvm在监控页面把CPU堆/MetaSpace线程这四个的折线图全部展示出来了,而不是像 jconsole 需要一个个选择切换。其中监控的项我们也可以根据需要自定义勾选。这里处理基本的监控数据展示,页面上还有两个按钮分别是执行垃圾回收堆Dump。和jconsole一样,执行垃圾回收可以直接触发 JVM 的fullGC堆Dump可以直接dump当前堆空间。相比于 jconsole 友好太多。

同时 jvisualvm 对于线程的监控也非常友好,也都是彩色的。同时也提供了线程Dump 的按钮,用于dump 当前的线程。

上面这两个面板都有 Dump 操作,点击 dump 之后可以直接跳转到一个新的面板直接展示 dump 后的数据。

抽样器的顾名思义,在JVM 运行期间,对某个时间段进行抽样,抽样目标的数据信息,抽样的对象包括CPU内存,抽样完成后点击快照即可生成由抽样的数据生成的分析快照,并且可以进行多次采样或者增量采样来适应不同的抽样需求,同时在抽样过程中也能进行 GC 操作和 Dump 操作。最后的 profiler 和 抽样器功能类似用于 JVM 性能的抽样分析。

jmc(java mission controller)

Oracle Java Mission Control(JMC)是一组功能强大的工具,可以在Oracle JDK上运行并与Oracle Java SE Embedded 8虚拟机(VM)进行交互。这套工具提供了对Java SE Embedded 8 VM的高级,简单的Java监视和管理,适用于开发和生产环境。这个工具相较于前面的 jvisualvm 更加强大。有更多的监控项,在 jvisualvm 我们提到了一个抽样的功能,而这个功能在 jmc 直接升级成了 java飞行记录(JFR) 。JMC 在 jdk 8 之后的版本都是以独立软件包的形式提供,传送门 ===>下载地址

jmc 我之前怎么尝试都是翻车都没有成功的打开,但是这次我打开了。记录下我的环境 (macos BigSur 11.3.1 (20E241) + jdk 1.8.0.231)

jms 的Mbean 概览页面是三个仪表盘,分别是堆内存使用情况, CPU 使用率,最近一次 youngGC 对象存活的比例。

JFRJVM 内置的数据收集引擎,正如这个名字一样 JFR(Java flight recoder) 会在运行期间收集JVM和Java 应用的运行数据,帮助开发者诊断分析 Java 应用。使用 JFR 要先进行 jfr 文件的录制,然后 jmc 对 jfr 文件进行分析并以图表的形式展示出来。其中左边的分析结果的菜单树,而右边则是分析结果的图表展示,展示的数据内容非常的详细。jfr 不仅可以通过 jmc 进行录制,还可以通过前面提到的jcmd命令进行录制。录制完成之后导入到 jmc 中进行分析即可。JFR 可以在测试环境和个人电脑上免费使用,但是如果用于生产服务器需要商业许可证,JFR虽然好,但是录制过程对中对性能还是会有一定的影响,大约 2% 左右,这个需要注意。

可视化监控集成工具

前面介绍了JDK 自带的一些监控工具,其中包括命令行的工具和一些图形化的工具,上面的除了 JFR 我们在生产环境可以使用,其他的基本上都用不上。因为生产环境的服务器不是谁都能连上的,基本上都是运维管理,那我们还有其他的手段去方便地监控吗?有的,我们生产使用的是 grafana + prometheus 的组合。下图是监控的 JVM dashbord 是对 JVM 堆空间和线程的监控。最上面的是CPU 使用率,绿色表示System CPU 使用率,黄色表示JVM 的 CPU 使用率。下面的两个图表则表示堆空间和非堆部分的空间使用情况。

下面这张图主要展示了线上真实的JVM GC的情况,可以看到堆空间曲线随着时间是呈现一个锯齿状的。而非堆部分则是一直都是平直的,这是垃圾回收器在堆中工作的结果。下面的两个折线图则分别是GC次数和GC时长的统计图,但看到这个图的时候我有一点疑惑的地方,堆空间能清楚的看到每次垃圾的回收产生的曲线下降,但是为什么他下面的记录GC次数的图中却没有呢?后来我查了线上的的GC日志,确认是GC count中漏记了。有哪位大神知道这是为什么,帮我答疑解惑一下。这里还有一个细节,就是GC count和GC time图表下面的PS MarkSweepPS Scavenge ,这分别代表这 Parallel GC 的老年代回收器和新生代回收器。

JVM dashbord 还有很多的图表,这些都是可以配置的,但是都是对堆栈线程的监控。当然 grafana 还可以集成其他类型的监控,比如主机资源、es和http请求等监控内容非常丰富。但是grafana 或者 datadog 这种监控集成工具也就是只有展示的功能,它并不具备像 JFR 或 jvisualvm 抽样分析的功能。如果真的出现了问题还是要dump内存线程或者开启JFR进行排查分析。

内存泄漏分析实例

这里我们使用一段会不断产生垃圾的代码,然后我们设置 JVM 的启动参数为 -Xmx128m -Xms128m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=./heap.hprof (jdk8)。这段参数限制的堆空间的大小为128m,并且在堆空间溢出时dump堆内存到./heap.hprof中。垃圾生成代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class GCLogAnalysis {

private static Random random = new Random();
public static void main(String[] args) {
// 当前毫秒时间戳
long startMillis = System.currentTimeMillis();
// 持续运行毫秒数; 可根据需要进行修改
long timeoutMillis = TimeUnit.SECONDS.toMillis(10);
// 结束时间戳
long endMillis = startMillis + timeoutMillis;
LongAdder counter = new LongAdder();
System.out.println("正在执行...");
// 缓存一部分对象; 进入老年代
int cacheSize = 2000;
Object[] cachedGarbage = new Object[cacheSize];
// 在此时间范围内,持续循环
while (System.currentTimeMillis() < endMillis) {
// 生成垃圾对象
Object garbage = generateGarbage(100*1024);
counter.increment();
int randomIndex = random.nextInt(2 * cacheSize);
if (randomIndex < cacheSize) {
cachedGarbage[randomIndex] = garbage;
}
}
System.out.println("执行结束!共生成对象次数:" + counter.longValue());
}

// 生成对象
private static Object generateGarbage(int max) {
int randomSize = random.nextInt(max);
int type = randomSize % 4;
Object result = null;
switch (type) {
case 0:
result = new int[randomSize];
break;
case 1:
result = new byte[randomSize];
break;
case 2:
result = new double[randomSize];
break;
default:
StringBuilder builder = new StringBuilder();
String randomString = "randomString-Anything";
while (builder.length() < randomSize) {
builder.append(randomString);
builder.append(max);
builder.append(randomSize);
}
result = builder.toString();
break;
}
return result;
}
}

如果一切顺利堆空间溢出就可以在项目根目录下就可以得到一个heap.hprof文件,这就是我们分析的目标文件了。前面我们提到的jmc可以用来分析.hprof文件。这里我们使用Eclipse MAT进行分析。以我们的经验一般堆栈溢出都是内存泄漏,所以我们选择泄漏分析报告。

打开后直接就能看到 MAT 根据内存数据分析推测出的内存泄漏的原因,下图可以看到Problem Suspect 1占到了99.69%的内存,虽然这里只是刚刚打开.hprof 文件但基本上可以推测出内存泄漏的原因了。

在 Suspect Report 中有很多的选项,包括一些线程堆积对象信息。在这个部分基本可以推断出系统发生内存溢出的原因了。也就是我们垃圾生成类的主线程产生了太多的垃圾,导致较小的内存溢出了。

如果到这里还是不能确定问题发生的根源没关系。MAT 还有一些其他的分析方式,我们一起来看 MAT 的首页信息。首先最上面是一个饼状图,然后下面有各种各样的操作和报告可以选择,这里面包括对象实例的柱状图对象的树形结构打印开销最大的几个对象重复类分析等,还有一些分析报告可以选择。功能方面虽然分析的内容没有 JFR 全面,但是通常来说这么多信息已经足够帮助我们分析出内存泄漏的根源了。

线上分析工具

FastThread

fastThread 是一个优秀的Java dump线程分析工具,只要把线程 dump 上传,即可进行在线分析,分析结果都是以图表展示,可展示项非常多并支持结果 pdf 导出,是一个非常优秀的在线分析工具。这里就不具体展开了,感兴趣的同学可以自己动手实操试试。传送门 ==> fastThread

GCEasy

GCEasy 是一款优秀可视化的 GC 日志分析工具,使用前需要把日志信息上传然后进行分析,分析结果以图表的方式展示,同样也支持结果 pdf 导出。GCEasy 和 fastThread 是同一家的产品,两者图表风格是一致的。感兴趣的同学可以动手实操试试,很有意思。传送门 ==> GCEasy

总结

我之前都不是很重视 JVM 的监控,但是那次线上事故之后我意识到JVM监控的重要性。这一小节我们一起从JDK自带的监控工具开始梳理,其中命令行工具包括常用的 jpsjstatckjmapjstatjcmd 等,图形化工具包括 jconsolejvisualvm和后来独立软件包的jmc,其中jmc中的jfr基本可以满足监控的需求。jdk 提供的工具在理想情况下是能够正常监控的,但是有些时候是无法正常使用了,会报无法 attach 的异常,还有我们也平时不会有生产环境服务器的权限。所以仅仅使用 JDK 自带的工具进行监控是不太现实的。那我们要如何进行监控呢?我们使用可视化集成工具,我们演示的是 grafana 通过集成 prometheus 实现对 JVM 基本信息的监控。基本可以满足我们对于线上服务大体运行情况的监控。如果我们线上遇到了问题我们要怎么处理呢?紧接着我们演示了一个内存泄漏的分析案例,使用Eclipse MAT对一个内存溢出的.hprof文件进行简单实例分析。最后我们简单介绍了两个在线分析工具 fastThread 和 GCEasy。JVM 监控还有一个很好用的开源工具Arthas,感兴趣的同学可以动手实践。

这一小节有大量的动手实践,再加上我最近也挺忙的,这一小节我写了好久好久😅😅。终于要进入 JVM 很精彩的GC模块了,梳理完GC模块什么JVM调优,GC分析就都不是事了。加油,你的努力终究会照亮你的生活,晚安~