相关概念

性能监控

一种以非强行或者入侵方式收集或查看应用运营性能数据的活动。

监控通常是指一种在生产、质量评估或者开发环境下实施的带有预防或主动性的活动。

当应用相关干系人提出性能问题却没有提供足够多的线索时,首先我们需要进行性能监控,随后是性能分析。

  • GC 频繁
  • CPU load 过高
  • OOM
  • 内存泄漏
  • 死锁
  • 程序响应时间较长

性能分析

一种以侵入方式收集运行性能数据的活动,它会影响应用的吞吐量或响应性。

性能分析是针对性能问题的答复结果,关注的范围通常比性能监控更加集中。

性能分析很少在生产环境下进行,通常是在质量评估、系统测试或者开发环境下进行,是性能监控之后的步骤。

  • 打印 GC 日志,通过 GCview 或者 GCEasy 来分析日志信息
  • 灵活运用,命令行工具,jstack、jmap、jinfo等
  • dump 出堆文件,使用内存分析工具分析文件
  • 使用阿里 Arthas,或 jconsole、JVisualVM 来实时查看 JVM 状态
  • jstack 查看堆栈信息

性能调优

一种为改善应用响应性或吞吐量而更改参数、源代码、属性配置的活动,性能调优是在性能监控、性能分析之后的活动。目的是以较小的内存占用,获得较高的吞吐量或改善响应时间,减少 GC 的频率和 Full GC 的次数

  • 适当增加内存,根据业务背景选择垃圾回收器

  • 优化代码,控制内存使用

  • 增加机器,分散节点压力

  • 合理设置线程池线程数量

  • 使用中间件提高程序效率,比如缓存,消息队列等

命令行工具

jps

jps(Java Process Status):显示指定系统内所有的 HotSpot 虚拟机进程(查看虚拟机进程信息),可用于查询正在运行的虚拟机进程。

对本地虚拟机进程来说,进程的本地虚拟机 ID 与操作系统的进程ID 是一致的,是唯一的。

jps [-q] [-mlvV] []

  • -q:仅仅显示本地虚拟机唯一 ID。不显示主类的名称

  • -l:输出应用程序主类的全类名,如果进程执行的是 jar 包,则输出 jar 包的完整路径

  • -m:输出虚拟机进程启动时传递给主类 main() 的参数

  • -v:列出虚拟机进程启动时的 JVM 参数

使用参数 -XX:-UsePerfData , 那么 jps 及 jstat 将无法从一个叫 PerfData 的共享文件获取数据,即探知不到该进程的信息

jstat

jstat(JVM Statistics Monitoring Tool):用于监视虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。

jstat -

  • -class:显示 ClassLoader 的相关信息:类的装载数量、卸载数量及占用空间类、装载所消耗的时间等
  • -t:程序持续时间
  • :指定毫秒重复执行
  • :执行次数
  • -h:设置每隔指定条输出信息后打印表头

eg:每隔一秒打印一次类加载信息和程序执行时间,执行 10 次后终止

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
C:\Users\user>jps
20440 Test
4972 Jps

C:\Users\user>jstat -class -t 20440 1000 10
Timestamp Loaded Bytes Unloaded Bytes Time
23.2 581 1144.2 0 0.0 0.08
24.2 581 1144.2 0 0.0 0.08
25.2 581 1144.2 0 0.0 0.08
26.2 581 1144.2 0 0.0 0.08
27.3 581 1144.2 0 0.0 0.08
28.3 581 1144.2 0 0.0 0.08
29.3 581 1144.2 0 0.0 0.08
30.3 581 1144.2 0 0.0 0.08
31.3 581 1144.2 0 0.0 0.08
32.3 581 1144.2 0 0.0 0.08

JIT 相关:

  • -compiler:显示 JIT 编译器编译过的方法、耗时等信息
  • -printcompilation:输出已经被 JIT 编译的方法

GC 相关:

  • -gc:显示与 GC 相关的堆信息。包括 Eden 区、两个 Survivor区、老年代永久代等的容量、早用空间、GC时间合计等信息
  • -gcutil:与 -gc 基本相同,但输出的为已使用空间占总空间的百分比
  • -gccause:与 -gcutil 功能一样,但是会额外输出导致最后一次或当前正在发生的 GC 产生的原因
  • -capacity:与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
  • -gcnew:新生代 GC 状况
  • -gcnewcapacity:与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间
  • -gold:老年代 GC 状况

jinfo

jinfo(Configuralion Info for Java):实时查看和修改 JVM 配置参数

jinfo [option]

  • no option:输出全部的参数和系统属性
  • -flag name:输出对应名称的参数
  • -flags:查看全部的参数
  • -sysprops:查看系统属性
1
2
3
4
5
6
7
C:\Users\user>jinfo -flags 3740
Attaching to process ID 3740, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.202-b08
Non-default VM flags: -XX:CICompilerCount=12 -XX:InitialHeapSize=10485760 -XX:MaxHeapSize=10485760 -XX:MaxNewSize=3145728 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=3145728 -XX:OldSize=7340032 -XX:+PrintGCDetails -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC
Command line: -Xms9m -Xmx9m -XX:+PrintGCDetails -javaagent:D:\Softs\IntelliJ IDEA 2022.3.2\lib\idea_rt.jar=65418:D:\Softs\IntelliJ IDEA 2022.3.2\bin -Dfile.encoding=UTF-8
  • -flag [±]name:开启或者关闭对应名称的参数。只有被标记为 manageable 的参数才可以被动态修改
  • -flag name=value:设定对应名称的参数

并非所有参数都支持动态修改。参数只有被标记为 manageable 的 flag 可以被实时修改。其实,这个修改能力是极其有限的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
C:\Users\user>java -XX:+PrintFlagsFinal -version | findstr manageable
intx CMSAbortablePrecleanWaitMillis = 100 {manageable}
intx CMSTriggerInterval = -1 {manageable}
intx CMSWaitDuration = 2000 {manageable}
bool HeapDumpAfterFullGC = false {manageable}
bool HeapDumpBeforeFullGC = false {manageable}
bool HeapDumpOnOutOfMemoryError = false {manageable}
ccstr HeapDumpPath = {manageable}
uintx MaxHeapFreeRatio = 100 {manageable}
uintx MinHeapFreeRatio = 0 {manageable}
bool PrintClassHistogram = false {manageable}
bool PrintClassHistogramAfterFullGC = false {manageable}
bool PrintClassHistogramBeforeFullGC = false {manageable}
bool PrintConcurrentLocks = false {manageable}
bool PrintGC = false {manageable}
bool PrintGCDateStamps = false {manageable}
bool PrintGCDetails = false {manageable}
bool PrintGCID = false {manageable}
bool PrintGCTimeStamps = false {manageable}
java version "1.8.0_202"
Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.202-b08, mixed mode)

java -XX+PrintFlagslnitial PID:查看所有 JVM 参数启动的初始值。

java -XX:+PrintFlagsFinal PID:查看所有 JVM 参数的最终值。

java -XX+PrintCommandLineFlags PID:查看已经被用户或者 JVM 设置过的详细参数名称和值。

jmap

jmap(JVM Memory Map):导出内存映像文件/内存使用情况

jmap [option] (to connect to running process)
jmap [option] <executable (to connect to a core file)

  • -dump:生成 dump 文件,-dump:live 只保存堆中存活的对象
  • -heap:输出整个堆空间的详细信息,包括 GC 的使用、堆配置信息,以及内存的使用信息等
  • -histo:输出堆空间中对象的统计信息,包括类、实例数量和合计容量
  • -permstat:以 ClassLoader 为统计口径输出永久代的内存状态信息(仅 linux/solaris 平台有效)
  • -finalizerinfo:显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象(仅 linux/solaris 平台有效)
  • -F:当虚拟机进程对-dump 选项没有任何响应时,强制执行生成 dump 文件(仅 linux/solaris 平台有效)

由 jmap 导出的堆快照必定是安全点位置的。这可能导致基于该堆快照的分析结果存在偏差。如果某个线程长时间无法跑到安全点, jmap 将一直等下去。

手动生成 dump 文件:

  • jmap -dump:format=b,file=<filename.hprof>

  • jmap -dump:live,format=b,file=<filename.hprof> // 存活对象

通过 VM 参数自动生成 dump 文件:

  • -XX:+HeapDumpOnOutOfMemoryError:在程序发生 OOM 时,导出应用程序的当前堆快照

  • -XX:HeapDumpPath=<filename.hprof>:可以指定堆快照的保存位置

查看各区大小:

jmap -heap pid

所有类型使用的内存:

jmap -histo pid

jhat

jhat(JVM Heap Analysis Tool):分析 dump 文件。

在 7000 端口启动一个 http 服务器,在 JDK9、JDK10 中已经被删除,官方建议用 VisualVM 代替

jstack

jstack(JVM stlack Trace):查看所有线程信息

jstack [option]

  • -F:当正常输出的请求不被响应时,强制输出线程堆栈
  • -I:额外显示关于锁的附加信息
  • -m:如果调用到本地方法,可以显示 C/C++的堆栈
  • -h:帮助操作

jcmd

多功能命令行,可以用来实现前面除了 jstat 之外所有命令的功能。比如:用它来导出堆、内存使用、查看 Java 进程、导出线程信息、执行 GC、JVM 运行时间等。

jcmd -l:列出所有的 JVM 进程

jcmd pid help:针对指定的进程,列出支持的所有命令

jcmd pid 具体命令:显示指定进程的指令命令数据。

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
C:\Users\user>jcmd 20660 help
20660:
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>'.
C:\Users\user>jcmd 20660 GC.heap_info
20660:
PSYoungGen total 2560K, used 2414K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 94% used [0x00000000ffd00000,0x00000000ffee17f8,0x00000000fff00000)
from space 512K, 95% used [0x00000000fff00000,0x00000000fff7a020,0x00000000fff80000)
to space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
ParOldGen total 7168K, used 374K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 5% used [0x00000000ff600000,0x00000000ff65d8c8,0x00000000ffd00000)
Metaspace used 4156K, capacity 4642K, committed 4864K, reserved 1056768K
class space used 455K, capacity 462K, committed 512K, reserved 1048576K

GUI 工具

JConsole

能够检查堆中各部分内存和非堆内存的使用情况、线程信息、死锁、类的加载数量、VM 概要信息等。

Visual VM

本地连接:

监控本地 Java 进程的 CPU、类、线程等

远程连接:

  1. 确定远程服务器的 IP 地址
  2. 添加 JMX(通过 JMX 技术具体监控远程服务器哪个 Java 进程)
  3. 修改 bin/catalina.sh 文件,连接远程的 tomcat
  4. 在 …/conf 中添加 jmxremote.access 和 jmxremote.password 文件
  5. 将服务器地址改成公网 IP 地址
  6. 设置阿里云安全策略和防火墙策略
  7. 启动 tomcat,查看 tomcat 启动日志和端口监听
  8. JMX 中输入端口号、用户名、密码登录