写点什么

java 性能分析与问题定位 实战

用户头像
try catch
关注
发布于: 2021 年 05 月 19 日

今天我们来聊下生产环境排查、定位问题的工具和方法。

常用命令

jdk 提供的工具类,可以用来获取 java 进程的内存、线程、垃圾回收等信息。


  • jstack —— 获取线程堆栈信息:

`jstack -l 7055 > store-back.jstatck`

  • jmap —— 获取堆中的对象信息(类的实例等)

`jmap -dump:format=b,file=store-back.hprof 12131`说明:需要使用eclipse MAT或者 jhat 工具配合,解析 dump 下来的内存文件。

  • OOM 时自动 dump 方式,在 java 启动脚本上添加 jvm 命令:

`-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${目录}`

  • jstat —— jvm 的统计信息,包含内存使用情况、垃圾回收时间、类加载等信息:

`jstat -gcutil 21891 250 7` 说明:21891 进程号; 250ms 采样间隔时间,单位毫秒; 7 采集次数

APM 监控工具

通常,排查生产环境的问题,我们不会远程连到服务器,使用 jstack、jmap 命令获取相关信息,因为这样效率太低,而且生产环境有诸多限制。我比较习惯使用 APM(Application Performance Monitor)工具来快速定位和排查问题,比如pinpoint就是一款非常优秀的 APM 工具。


  • 在 pinpoint 首页,我们可以看到应用程序之间的拓扑图,调用次数(一般生产环境采样率为 10%),如下图。

  • 图片右上角部分(如下图),有很多绿色的小点点,每个点代表一次 http 请求,横坐标为请求时间,纵坐标为该次请求的耗时(毫秒);红色的点表示该次请求抛异常。

  • 我们可以用鼠标框住耗时最长的一部分“小点”,就可以查看每个请求的调用链(如下图),调用链精确到某个应用的某个类的某个方法,并打印出执行的 SQL 语句;以及每个方法的执行耗时和百分比。

  • 点击“inspector”按钮,可以看到每个应用(jvm 进程)的内存分配情况,比如堆、永久区(java8 为 metaspace)占用空间,GC 执行耗时,CPU 消耗,每秒事务数(TPS),活跃线程数,请求响应时间,堆外直接内存空间等等数据。一目了然,犹如上帝视角。

eclipseMAT 内存分析工具

还记得第一步“常用工具”中的两个命令吧:

`jmap -dump:format=b,file=store-back.hprof 12131`

`-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=${目录}`

eclipseMAT 就是用来分析 dump 下来的内存文件的。


  • 用 MAT 生成内存泄漏分析报告,如下图:

  • 通过直方图(Histogram)和支配树(Dorminator Tree)分析内存泄漏:

决策树

首先,我们通过决策树(Dorminator Tree)来分析,按 package 分组:



  • 找到占用内存最大的类,如下图。


浅堆(shallow heap) : 指某一个对象本身所占内存大小(也包括外部对象的"引用",每个外部对象的"引用"为 4 或 8 个字节,分析时一般都是忽略这部分)。


保留堆(retained heap): 指对象本身和引用的“外部对象”的内存大小,但不包括“共享对象”(共享对象的概念后面会讲到)。


深堆(deep heap): 指对象本身和引用的“外部对象”的内存大小。


MAT 只显示浅堆和保留堆的大小,很明显,浅堆和保留堆所占空间差距过大,就非常有可能是“内存泄漏”了。



  • 在这里,我们怀疑 Activity 对象可能泄漏内存,于是查下引用此对象的是谁(with incoming references)。

  • 我们大概看一眼,就会发现,主要是 WebAppClassLoader 持有大量 Activity 对象的引用。

  • 使用合并最短根路径(GC ROOTS)方式,检查对象的引用路径,如下图。


Merge Shortest Path To GC Roots:快速分析的一个常用功能,它能够从当前内存映像中找到一条指定对象所在的到 GC Root 的最短路径。


需要排除弱引用、软引用及影子引用等,一般来说这三种类型的引用都不会是造成内存泄漏的原因,因为 JVM 迟早是会回收只存在这三种引用的资源的。



  • 再次确认,要是 WebAppClassLoader 持有大量 Activity 对象的引用。我们通过决策树的方式分析,大概可以判断 Activity 对象是内存泄漏的最大嫌疑人。

直方图

下面我们再通过直方图的方式分析。


直方图首页,依然通过浅堆和保留堆来分析,发现 FindShoppingCartByType 对象可能导致内存泄漏。



  • 查看引用人是谁(with incoming references)

  • 咦,又看到熟悉的人了,Activity……

  • 依然使用合并最短根路径(GC ROOTS)方式,检查对象的引用路径,如下图。

  • 根据下图,得出结论:主要是 WebAppClassLoader 持有大量 Activity 对象的引用;Activity 对象持有大量 FindShoppingCartByType 对象的引用。



因为本例的程序是部署在 tomcat 中,这就相当于是 Activity 对象常驻内存,无法在 GC 时释放。通过阅读代码,发现 Activity 对象是使用 static 声明的变量,符合我们的推断。至此,内存泄漏问题定位到了原因,后面就是改代码啦。

发布于: 2021 年 05 月 19 日阅读数: 72
用户头像

try catch

关注

还未添加个人签名 2012.07.23 加入

还未添加个人简介

评论

发布
暂无评论
java性能分析与问题定位 实战