乐趣区

深入理解JVM虚拟机9JVM监控工具与诊断实践

转自 https://juejin.im/post/59e6c1…
jvm 优化必知系列——监控工具
微信公众号【Java 技术江湖】一位阿里 Java 工程师的技术小站。作者黄小斜,专注 Java 相关技术:SSM、SpringBoot、MySQL、分布式、中间件、集群、Linux、网络、多线程,偶尔讲点 Docker、ELK,同时也分享技术干货和学习经验,致力于 Java 全栈开发!(关注公众号后回复”Java“即可领取 Java 基础、进阶、项目和架构师等免费学习资料,更有数据库、分布式、微服务等热门技术学习视频,内容丰富,兼顾原理和实践,另外也将赠送作者原创的 Java 学习指南、Java 程序员面试指南等干货资源)

9eedaaa588bef997bef63a7160fa349134bdb78c

这是 jvm 优化系列第二篇:

jvm 优化——垃圾回收

通过上一篇的 jvm 垃圾回收知识,我们了解了 jvm 对内存分配以及垃圾回收是怎么来处理的。理论是指导实践的工具,有了理论指导,定位问题的时候,知识和经验是关键基础,数据可以为我们提供依据。

在常见的线上问题时候,我们多数会遇到以下问题:

内存泄露
某个进程突然 cpu 飙升
线程死锁
响应变慢 … 等等其他问题。
如果遇到了以上这种问题,在线下可以有各种本地工具支持查看,但到线上了,就没有这么多的本地调试工具支持,我们该如何基于监控工具来进行定位问题?

我们一般会基于数据收集来定位,而数据的收集离不开监控工具的处理,比如:运行日志、异常堆栈、GC 日志、线程快照、堆快照等。经常使用恰当的分析和监控工具可以加快我们的分析数据、定位解决问题的速度。以下我们将会详细介绍。

一、jvm 常见监控工具 & 指令
1、jps:jvm 进程状况工具
jps [options] [hostid]
如果不指定 hostid 就默认为当前主机或服务器。

命令行参数选项说明如下:

-q 不输出类名、Jar 名和传入 main 方法的参数 – l 输出 main 类或 Jar 的全限名 -m 输出传入 main 方法的参数 – v 输出传入 JVM 的参数
例如:

2、jstat: jvm 统计信息监控工具
jstat 是用于见识虚拟机各种运行状态信息的命令行工具。它可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、jit 编译等运行数据,它是线上定位 jvm 性能的首选工具。

命令格式:

jstat [generalOption | outputOptions vmid [interval[s|ms] [count]] ] generalOption – 单个的常用的命令行选项,如 -help, -options, 或 -version。outputOptions - 一个或多个输出选项,由单个的 statOption 选项组成,可以和 -t, -h, and - J 等选项配合使用。
参数选项:

Option

Displays

Ex

class

用于查看类加载情况的统计

jstat -class pid: 显示加载 class 的数量,及所占空间等信息。

compiler

查看 HotSpot 中即时编译器编译情况的统计

jstat -compiler pid: 显示 VM 实时编译的数量等信息。

gc

查看 JVM 中堆的垃圾收集情况的统计

jstat -gc pid: 可以显示 gc 的信息,查看 gc 的次数,及时间。其中最后五项,分别是 young gc 的次数,young gc 的时间,full gc 的次数,full gc 的时间,gc 的总时间。

gccapacity

查看新生代、老生代及持久代的存储容量情况

jstat -gccapacity: 可以显示,VM 内存中三代(young,old,perm)对象的使用和占用大小

gccause

查看垃圾收集的统计情况(这个和 -gcutil 选项一样),如果有发生垃圾收集,它还会显示最后一次及当前正在发生垃圾收集的原因。

jstat -gccause: 显示 gc 原因

gcnew

查看新生代垃圾收集的情况

jstat -gcnew pid:new 对象的信息

gcnewcapacity

用于查看新生代的存储容量情况

jstat -gcnewcapacity pid:new 对象的信息及其占用量

gcold

用于查看老生代及持久代发生 GC 的情况

jstat -gcold pid:old 对象的信息

gcoldcapacity

用于查看老生代的容量

jstat -gcoldcapacity pid:old 对象的信息及其占用量

gcpermcapacity

用于查看持久代的容量

jstat -gcpermcapacity pid: perm 对象的信息及其占用量

gcutil

查看新生代、老生代及持代垃圾收集的情况

jstat -util pid: 统计 gc 信息统计

printcompilation

HotSpot 编译方法的统计

jstat -printcompilation pid: 当前 VM 执行的信息

例如:

查看 gc 情况执行:jstat-gcutil 27777

3、jinfo:java 配置信息
命令格式:

jinfo[option] pid
比如: 获取一些当前进程的 jvm 运行和启动信息。

4、jmap: java 内存映射工具
jmap 命令用于生产堆转存快照。打印出某个 java 进程(使用 pid)内存内的,所有‘对象’的情况(如:产生那些对象,及其数量)。

命令格式:

jmap [option] pid jmap [option] executable core jmap [option] [server-id@]remote-hostname-or-IP
参数选项:

-dump:[live,]format=b,file=<filename> 使用 hprof 二进制形式, 输出 jvm 的 heap 内容到文件 =. live 子选项是可选的,假如指定 live 选项, 那么只输出活的对象到文件. -finalizerinfo 打印正等候回收的对象的信息. -heap 打印 heap 的概要信息,GC 使用的算法,heap 的配置及 wise heap 的使用情况. -histo[:live] 打印每个 class 的实例数目, 内存占用, 类全名信息. VM 的内部类名字开头会加上前缀”*”. 如果 live 子参数加上后, 只统计活的对象数量. -permstat 打印 classload 和 jvm heap 长久层的信息. 包含每个 classloader 的名字, 活泼性, 地址, 父 classloader 和加载的 class 数量. 另外, 内部 String 的数量和占用内存数也会打印出来. -F 强迫. 在 pid 没有相应的时候使用 -dump 或者 -histo 参数. 在这个模式下,live 子参数无效. -h | -help 打印辅助信息 -J 传递参数给 jmap 启动的 jvm.
例如:

使用 jmap -heap pid 查看进程堆内存使用情况,包括使用的 GC 算法、堆配置参数和各代中堆内存使用情况:

使用 jmap -histo[:live] pid 查看堆内存中的对象数目、大小统计直方图。

5、jhat:jvm 堆快照分析工具
jhat 命令与 jamp 搭配使用,用来分析 map 生产的堆快存储快照。jhat 内置了一个微型 http/Html 服务器,可以在浏览器找那个查看。不过建议尽量不用,既然有 dumpt 文件,可以从生产环境拉取下来,然后通过本地可视化工具来分析,这样既减轻了线上服务器压力,有可以分析的足够详尽(比如 MAT/jprofile/visualVm) 等。

6、jstack:java 堆栈跟踪工具
jstack 用于生成 java 虚拟机当前时刻的线程快照。线程快照是当前 java 虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。

命令格式:

jstack [option] pid jstack [option] executable core jstack [option] [server-id@]remote-hostname-or-IP
参数:

- F 当’jstack [-l] pid’没有相应的时候强制打印栈信息 - l 长列表. 打印关于锁的附加信息, 例如属于 java.util.concurrent 的 ownable synchronizers 列表. - m 打印 java 和 native c/c++ 框架的所有栈信息. -h | -help 打印帮助信息 pid 需要被打印配置信息的 java 进程 id, 可以用 jps 查询.
后续的查找耗费最高 cpu 例子会用到。

二、可视化工具
对 jvm 监控的常见可视化工具,除了 jdk 本身提供的 Jconsole 和 visualVm 以外,还有第三方提供的 jprofilter,perfino,Yourkit,Perf4j,JProbe,MAT 等。这些工具都极大的丰富了我们定位以及优化 jvm 方式。

这些工具的使用,网上有很多教程提供,这里就不再过多介绍了。对于 VisualVm 来说,比较推荐使用,它除了对 jvm 的侵入性比较低以外,还是 jdk 团队自己开发的,相信以后功能会更加丰富和完善。jprofilter 对于第三方监控工具,提供的功能和可视化最为完善,目前多数 ide 都支持其插件,对于上线前的调试以及性能调优可以配合使用。

另外对于线上 dump 的 heap 信息,应该尽量拉去到线下用于可视化工具来分析,这样分析更详细。如果对于一些紧急的问题,必须需要通过线上监控,可以采用 VisualVm 的远程功能来进行,这需要使用 tool.jar 下的 MAT 功能。

三、应用
1、cpu 飙升
在线上有时候某个时刻,可能会出现应用某个时刻突然 cpu 飙升的问题。对此我们应该熟悉一些指令,快速排查对应代码。

1. 找到最耗 CPU 的进程

指令:top

2. 找到该进程下最耗费 cpu 的线程

指令:top -Hp pid

3. 转换进制

printf“%xn”15332 // 转换 16 进制(转换后为 0x3be4)
4. 过滤指定线程,打印堆栈信息

指令: jstack pid |grep ‘threadPid’ -C5 –color jstack 13525 |grep ‘0x3be4’ -C5 –color // 打印进程堆栈 并通过线程 id,过滤得到线程堆栈信息。

可以看到是一个上报程序,占用过多 cpu 了(以上例子只为示例,本身耗费 cpu 并不高)

2、线程死锁
有时候部署场景会有线程死锁的问题发生,但又不常见。此时我们采用 jstack 查看下一下。比如说我们现在已经有一个线程死锁的程序,导致某些操作 waiting 中。

1. 查找 java 进程 id

指令:top 或者 jps

2. 查看 java 进程的线程快照信息
指令:jstack -l pid

从输出信息可以看到,有一个线程死锁发生,并且指出了那行代码出现的。如此可以快速排查问题。

3、OOM 内存泄露
java 堆内的 OOM 异常是实际应用中常见的内存溢出异常。一般我们都是先通过内存映射分析工具(比如 MAT)对 dump 出来的堆转存快照进行分析,确认内存中对象是否出现问题。

当然了出现 OOM 的原因有很多,并非是堆中申请资源不足一种情况。还有可能是申请太多资源没有释放,或者是频繁频繁申请,系统资源耗尽。针对这三种情况我需要一一排查。

OOM 的三种情况:

1. 申请资源(内存)过小,不够用。

2. 申请资源太多,没有释放。

3. 申请资源过多,资源耗尽。比如:线程过多,线程内存过大等。

1. 排查申请申请资源问题。

指令:jmap -heap 11869
查看新生代,老生代堆内存的分配大小以及使用情况,看是否本身分配过小。

从上述排查,发现程序申请的内存没有问题。

2. 排查 gc

特别是 fgc 情况下,各个分代内存情况。

指令:jstat -gcutil 11938 1000 每秒输出一次 gc 的分代内存分配情况,以及 gc 时间

3. 查找最费内存的对象

指令: jmap -histo:live 11869 | more

上述输出信息中,最大内存对象才 161kb, 属于正常范围。如果某个对象占用空间很大,比如超过了 100Mb,应该着重分析,为何没有释放。

注意,上述指令:

jmap -histo:live 11869 | more 执行之后,会造成 jvm 强制执行一次 fgc,在线上不推荐使用,可以采取 dump 内存快照,线下采用可视化工具进行分析,更加详尽。jmap -dump:format=b,file=/tmp/dump.dat 11869 或者采用线上运维工具,自动化处理,方便快速定位,遗失出错时间。
4. 确认资源是否耗尽

pstree 查看进程线程数量
​netstat 查看网络连接数量
或者采用:

ll /proc/${PID}/fd | wc -l // 打开的句柄数
ll /proc/${PID}/task | wc -l(效果等同 pstree -p | wc -l)// 打开的线程数

以上就是一些常见的 jvm 命令应用。

一种工具的应用并非是万能钥匙,包治百病,问题的解决往往是需要多种工具的结合才能更好的定位问题,无论使用何种分析工具,最重要的是熟悉每种工具的优势和劣势。这样才能取长补短,配合使用。

退出移动版