一句话总结: 从问题现象为入口, 归结为 3 类问题进行定位分析: 内存满, CPU 高, 线程阻塞.
首先先介绍下 jvisualvm 这款 jdk 自带的性能工具. 通常我们要定位哪块代码性能差, 耗时久, 最原始的办法就是在各个方法前后日志打印时间戳并计算耗时, 这种方法很繁琐, 通常要加很多日志多次部署才能定位到, 我一开始也是这么搞的. 而使用 jvisualvm 工具则可以直接查看整个业务代码调用链中各个方法的耗时及占比, 直接就能定界出是哪个方法性能差, 耗时久.
操作步骤: 点击抽样器页签, 点击 CPU 抽样, 前台操作触发代码执行, 操作完点击停止, 再点击快照. 在快照页点击搜索按钮, 输入代码入口方法名搜索, 结果见下图.
我们来分析下下图, RegionNorthbound.createRegion 是北向入口方法, 总耗时见右侧 5.298 秒, 其调用了 RegionServiceImpl.createRegion 方法, 耗时 4.093 秒, 那么相减就是 RegionNorthbound.createRegion 方法本身的耗时.
RegionServiceImpl.createRegion 方法调用了 CommonDao.saveEntity 和 RegionServiceImpl.checkRepeat 方法, 分别耗时 0.998 秒和 0.094 秒. 可以直观查看是调用的哪个方法耗时久, 性能差. 整个方法调用链的耗时均可查看.
这在大型系统中非常有用. 大型系统中通常业务由业务部门开发, 平台由平台部门开发, 只提供 jar 包给业务部门使用. 那么当测试出性能问题时, 首先要定界出是业务的问题还是平台的问题, 再转给对应部门去修改. 业务开发不知道平台的实现, 无法查看修改其代码, 更不用说去加日志. 固通过此工具可以快速定界出问题责任主体.
当然 jvisualvm 除了查看代码调用链耗时, 实时监控 CPU, 内存, 线程数, 线程 dump, 内存 dump 等功能都非常好用.
下面开始按问题分类进行定位分析.
一: 内存问题
问题现象: 后台报错, 前台提示异常或 500 错误.
定位: 后台报错的定位思路很明确了, 查看日志, 这里分两类异常: 一, java.lang.OutOfMemoryError: PermGen space 异常, 此为方法区内存溢出, 方法区用来存放 class 代码, 通常解决办法就是调大 jvm permSize 参数值; 二, java.lang.OutOfMemoryError: heap space 异常, 比较常见的堆内存溢出. 当然也可以调大 jvm 参数来解决, 但若是不恰当代码引起的, 首先检查下自己写的代码, 看哪里创建了大量对象. 若检查不出来, 则使用 jmap 或 jvisualvm 导出内存快照.
分析下图 heap dump, 这里又分两种情况, 若左侧占内存大的类名为 com.**.User 这种自己定义的业务对象, 那在代码中搜索下此类名就能定位到哪里创建了大量此对象. 若为 String,Integer 这种基本类型的对象, 则可以使用 Eclipse Memory Analyer 此类工具进一步分析, 可以查看内存大对象的线程堆栈, 即可查看代码调用方法.
二: CPU 问题
问题现象: 前台显示卡顿, 响应时间长, Linux top 查看 CPU 使用率非常高
定位步骤:
1, 使用 top 查看进程 pid,java 系统通常进程名就叫 java
2, 使用 top -H -p pid 查看进程中各线程的 CPU 使用情况, 找出最占 CPU 的线程 pid, 注意这里其实就是 tid
3, 通过步骤 2 我们知道是哪个线程占 CPU 了, 使用 jstack pid 打印线程堆栈, 查看该线程的代码调用方法
4, 将步骤 2 线程 tid 转换为 16 进制, 因为 jstack 打印出来的 threaddump 中 tid 为 16 进制, 然后在 threaddump 中搜索, 即可找到线程堆栈, 这里 30725 转换为 16 进制就是 7805
这里有个情况要特别说明, 如果步骤 4 查看的占用 CPU 的线程为 java gc 线程, 通常是由于内存快满才导致 jvm 频繁 gc, 进而导致 CPU 高. 固此种情况得按上面提到的内存问题去定位解决.
三: 线程问题
问题现象 1: 请求响应时间长, 性能测试 TPS/QPS 上不去, 查看 CPU 占用又不高
问题现象 2: 请求响应直接超时, 后台线程相互死锁
定位: 线程问题通常打印一次 threaddump 是看不出问题的, 要多打印几次对比才能看出问题. 建议使用 jvisualvm 线程页签实时查看线程状态. 查看指定业务线程状态, 若长时间处于 wait 或 block 状态, 则可确认该问题是由于线程阻塞引起的. 查看线程堆栈可查看是调用哪个方法时阻塞的. 死锁问题也是类似定位, 线程堆栈里会提示在等待哪个线程释放 lock, 而有两个线程互相等待即会死锁.
备忘:
jvisualvm 连接远程 jvm, 远程 jvm 添加启动参数:
-Djava.rmi.server.hostname=10.6.188.43 -Dcom.sun.management.jmxremote.port=18888 -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.managementote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false
来源: https://www.cnblogs.com/yuzhengzhong/p/9845704.html