写点什么

JVM 分析工具

用户头像
insight
关注
发布于: 2021 年 03 月 03 日

如果要问目前最火热的 JVM 知识是什么?很多人的答案可能是“JVM 调优 ” 或者“JVM 性能优化”。但是具体需要从哪儿入手,怎么去做呢? 其实“调优”是一个诊断和处理手段,我们最终的目标是让系统的处理能力,也就是“性能”达到最优化,这个过程我们就像是一个医生,诊断和治疗“应用系统”这位病人。

我们以医生给系统看病为例,“性能优化”就是实现“把身体的大小毛病治好,身体达到最佳健康状态”的目标。那么去医院看病,医生会是怎么一个处理流程呢?医生上来会让你去做个全身体检,因为没有量化就没有改进 ,所以我们需要先了解和度量性能指标,就像是 血常规检查报告单 ,一旦成为这种标准化的指标,那么使用它得到的结果,也就是这个报告单,给任何一个医生看,都是有效的,一般也能得到一致的判断结果。

我们常用的指标可以有很多,比如 业务需求指标有:如吞吐量(QPS、TPS)、响应时间(RT)、并发数、业务成功率等,资源约束指标:如 CPU、内存、I/O 等资源的消耗情况。但作为一个 Java 系统,我们最常遇到的问题,往往是 JVM 的配置问题,而最容易去看到的指标其实是 JVM 的指标。

下面,就让我来介绍一下,如何通过 JDK 自带的一些 JVM 命令行工具来获取 JVM 的相关指标。

jps

要给系统做检查,先得挂个号,报上系统的大名给各个工具才行。那么怎么让各个工具知道我们想看的系统是哪个呢,那就需要知道系统的 PID 了。PID 就是进程控制符的意思,它是每个进程的唯一身份标识。知道了进程的 PID,才可以使用后续的工具。

那么,怎样才能知道 PID 呢?答案就是使用 jps 命令。

jps 是 JDK 提供的 Java 进程查看工具,ps 是 Process Status 的缩写, 这个用法也很简单。

直接输入 jps 命令,就能列出当前系统下,所有正在运行的 Java 进程以及进程对应的 PID 。

$ jps17680 Jps109 Main
复制代码


这样,我们就能知道我们系统的 PID 了。

当然这个命令可以再加多几个参数。比如有时候,我们想快速知道当前系统的 JVM 参数,那么可以通过如下的命令来获取:

jps -lvm
复制代码


  • l:输出主类或者 jar 的完全路径名

  • v:是输出 jvm 参数

  • m:输出 main method 的参数

jstat

挂到了号,拿到了系统的 PID 之后,首先我们给它测个心电图,在业务压力较大的时候,如果系统表现不佳,往往跟 GC 频率过高有关系,就好像如果一个人心跳过快会气喘吁吁,没法跑得快一样, GC 频率过高,就会导致 STW 的时间变长,这就会影响我们业务的性能了,这个时候,我们就需要去监控系统的 GC 情况。那么怎么来查看系统的 GC 情况呢。答案就是 jstat。

jstat 的全称是 statistics monitoring tool ,顾名思义,统计数据监控工具,就是用来对 Java 程序的资源和性能进行实时监控的。

它提供的功能还挺多的,但是我们最常用的,还是它的 GC 实时监控功能,可以说,这个工具是线上监控 GC 情况最好的工具之一。

它的使用方式也很简单:

jstat -gcutil [-t] <pid> 3s 1000
复制代码


  • -gcutil:GC 相关区域的使用率(utilization)统计

  • -t:可选,用于打印时间戳,即 JVM 启动到现在的秒数

  • 3s :采样频率,默认单位为 ms,可以使用 s 或 ms 结尾,如果使用 s,则频率就是秒。

  • 1000:采样总次数

执行这条命令之后,它就会列出下面这些列

 jstat -gcutil 20848 3000  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT 82.36   0.00  84.67   5.32  95.08  92.12      4    0.038     1    0.036    0.074 82.36   0.00  84.67   5.32  95.08  92.12      4    0.038     1    0.036    0.074 82.36   0.00  84.67   5.32  95.08  92.12      4    0.038     1    0.036    0.074 82.36   0.00  85.00   5.32  95.08  92.12      4    0.038     1    0.036    0.074 82.36   0.00  85.00   5.32  95.08  92.12      4    0.038     1    0.036    0.074
复制代码


让我简单给大家解释一下,每一列的意思

如果想看具体的数据,可以将 -gcutil 换成 -gc


jmap

如果 JVM 参数设置没问题,但却出现频繁 GC 且内存未被回收的情况的话,那就要考虑一下是否存在内存泄漏的情况了。这时候就需要祭出我们的 B 超,jmap。

jmap 是 Java Memory Map 的缩写,既然名称有一个 Memory,那么它提供的功能就必然与内存有关了。

它常用的功能有 3 个:

  • -heap pid :打印 堆内存/内存池 的配置和使用信息。

  • -histo:看哪些类占用的空间最多。

  • -dump:live,format=b,file=xxx.hprof pid:dump 当前的堆内存。

  • live:只 dump 存活对象,如果不指定这个参数,则默认会 dump 堆中的所有对象。live 参数实际上是产生一次 Full GC 来保证只看还存活的对象。

  • format=b:二进制的格式

  • file=xxxx:将 dump 的内容转储到什么文件中。

heap

$ jmap -heap 109Attaching to process ID 109, please wait...Debugger attached successfully.Server compiler detected.JVM version is 25.172-b03
using parallel threads in the new generation.using thread-local object allocation.Concurrent Mark-Sweep GC
Heap Configuration: MinHeapFreeRatio = 40 MaxHeapFreeRatio = 70 MaxHeapSize = 10737418240 (10240.0MB) NewSize = 4294967296 (4096.0MB) MaxNewSize = 4294967296 (4096.0MB) OldSize = 6442450944 (6144.0MB) NewRatio = 2 SurvivorRatio = 8 MetaspaceSize = 21807104 (20.796875MB) CompressedClassSpaceSize = 1073741824 (1024.0MB) MaxMetaspaceSize = 17592186044415 MB G1HeapRegionSize = 0 (0.0MB)
Heap Usage:New Generation (Eden + 1 Survivor Space): capacity = 3865509888 (3686.4375MB) used = 591462416 (564.0625152587891MB) free = 3274047472 (3122.374984741211MB) 15.301019351576937% usedEden Space: capacity = 3436052480 (3276.875MB) used = 579380944 (552.5407257080078MB) free = 2856671536 (2724.334274291992MB) 16.86181882763327% usedFrom Space: capacity = 429457408 (409.5625MB) used = 12081472 (11.52178955078125MB) free = 417375936 (398.04071044921875MB) 2.8131944576911336% usedTo Space: capacity = 429457408 (409.5625MB) used = 0 (0.0MB) free = 429457408 (409.5625MB) 0.0% usedconcurrent mark-sweep generation: capacity = 6442450944 (6144.0MB) used = 236824856 (225.8537826538086MB) free = 6205626088 (5918.146217346191MB) 3.676005577047666% used
67258 interned Strings occupying 7427728 bytes.
复制代码

using thread-local object allocation.:使用了 TLAB 优化


Heap Configuration

  • MinHeapFreeRatio=40:空闲堆空间的最小百分比,计算公式为:HeapFreeRatio=(CurrentFreeHeapSize/CurrentTotalHeapSize) * 100,值的区间为 0 到 100,默认值为 40。如果HeapFreeRatio < MinHeapFreeRatio,则需要进行堆扩容,扩容的时机应该在每次垃圾回收之后。

  • MaxHeapFreeRatio = 70:空闲堆空间的最大百分比,计算公式为:HeapFreeRatio =(CurrentFreeHeapSize/CurrentTotalHeapSize) * 100,值的区间为 0 到 100,默认值为 70。如果HeapFreeRatio > MaxHeapFreeRatio,则需要进行堆缩容,缩容的时机应该在每次垃圾回收之后。

  • NewRatio=2:新生代(2 个 Survivor 区和 Eden 区 )与老年代(不包括永久区)的堆空间比值,表示新生代:老年代=1:2。

histo

jmap -histo 109 | head -n 20
num #instances #bytes class name---------------------------------------------- 1: 7415063 607480456 [B 2: 7415951 499685928 [C 3: 2469534 214984112 [Ljava.lang.Object; 4: 559731 212565832 [I 5: 6801657 163239768 scala.Tuple2 6: 4852661 116463864 java.lang.String 7: 2409335 115648080 java.nio.HeapByteBuffer 8: 2521081 60505944 scala.collection.immutable.Map$Map1 9: 2520954 60502896 scala.collection.mutable.MapBuilder 10: 560451 44836144 [Lscala.collection.mutable.HashEntry; 11: 1120609 35860080 [D 12: 560212 35853568 scala.collection.immutable.VectorIterator 13: 672834 32296032 java.util.HashMap 14: 995826 31866432 java.lang.StackTraceElement 15: 560269 31375064 scala.collection.immutable.Vector
复制代码


由于该命令会打印出所有对象,因此往往很长,可以使用 head 来显示前面一部分。

其中:

[C is a char[][S is a short[][I is a int[][B is a byte[][[I is a int[][]
复制代码

上面的输出中[C 对象占用 Heap 这么多,往往跟 String 有关,String 其内部使用 final char[]数组来保存数据的。


然后再来说一下它的 live 选项,如果我们想看 JVM 中存活对象的情况,就需要加上这个 选项,这个选项会带来一次 Full GC,因此它也可以用来线上救急。


给大家看个例子,有一次内存占用过大而告警,运维进行了 dump 之后,内存下降到正常值,就是因为运行 jmap 时加上了 live 选项,由此也判断出来这次的内存占用过大是 GC 不及时导致的。


以上就是三种生产上最常用的 JVM 命令行工具,希望以后能给大家在生产问题排查时带来一点点帮助。


发布于: 2021 年 03 月 03 日阅读数: 400
用户头像

insight

关注

不要混淆行动与进展、忙碌与多产。 2018.11.17 加入

永远都是初学者

评论

发布
暂无评论
JVM 分析工具