前言
在项目中,我们常常会面临CPU过高或堆内存溢出的问题,解决这类问题通常需要查看线程的堆栈信息。本文将重点介绍两种线程排查工具:jstack和Arthas。当然,Arthas还有许多其他用途,例如在不修改代码的情况下查看方法调用的输入输出参数、异常信息等。最后,我们将引入一个线上的真实案例进行说明。
线程排查工具jstack
jstack介绍
jstack是java虚拟机自带的一种堆栈跟踪工具。jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源导致的长时间等待等。
命令格式
常用命令: jstack [-l]
-l 打印关于锁的附加信息
比如:
jps -ml 查看到应用进程id是57,
jstack -l 57 可以查看到当前jvm的线程快照。
jstack -l 57 >jstack.log 生成线程快照到指定文件里
定位占用CPU较高的线程
- top查找出哪个进程消耗的cpu高。执行top命令,默认是进程视图,其中PID是进程号
这里我们分析140这个java进程
- top中shift+h 或"H"查找出哪个线程消耗的cpu高
先输入top,然后再按shift+h 或"H",此时打开的是线程视图,pid为线程号
这里我们分析502这个线程,并且注意的是,这个线程是属于140这个进程的。
- jstack查找这个线程的信息
jstack [进程]|grep -A 10 [线程的16进制]
即: jstack 140 | grep -A 10 0x1f6
-A 10表示查找到所在行的后10行。502用计算器转换为16进制是1f6,注意字母是小写。
结果:
说不定可以一下子定位到出问题的代码。
Arthas
Arthas 简介
Arthas是一款Alibaba开源的Java诊断工具,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
常用命令
dashboard
使用dashboard命令可以显示当前系统的实时数据面板,包括线程信息、JVM内存信息及JVM运行时参数。
thread
查看当前线程信息,查看线程的堆栈,可以找出当前最占CPU的线程。
perl
# 打印当前cpu占用率最高的3个线程的堆栈信息
thread -n 3
# 查看ID为1都线程的堆栈信息
thread 1
# 找出当前阻塞其他线程的线程
thread -b
# 查看指定状态的线程
thread -state WAITING
jad
反编译已加载类的源码,觉得线上代码和预期不一致,可以反编译看看。
jad com.yonyou.devcenter.platform.DevCenterBootStrap
使用--source-only参数可以只打印类信息。
jad --source-only com.yonyou.devcenter.platform.DevCenterBootStrap
monitor
实时监控方法执行信息,可以查看方法执行成功此时、失败次数、平均耗时等信息。
monitor -c 5 com.yonyou.devcenter.platform.controller.PluginController search
-c 统计间隔,后面跟数字,代表每多少秒统计一次
watch
方法执行数据观测,可以观察方法执行过程中的参数、返回值和报错信息等。方便在没有日志的情况下快速定位问题。
watch com.yonyou.devcenter.platform.controller.PluginController search '{params,returnObj,throwExp}' -n 5 -x 3
-n 代表捕获结果次数,
-x 代表遍历深度
trace
跟踪某个方法的调用链路,查看耗时情况等
trace com.yonyou.devcenter.platform.controller.PluginController search -n 5 --skipJDKMethod false
-n 代表捕获结果次数
--skipJDKMethod 默认情况下,trace 不会包含 jdk 里的函数调用,如果希望 trace jdk 里的函数,需要显式设置--skipJDKMethod false
真实案例
前一段时间,我们生产上的一个服务出现了cpu达到100%的问题。通过使用Arthas排查分析,最终定位到问题并解决。
现象
运维报该服务的cpu达到了100%
定位
- 查看日志,日志报java.lang.OutOfMemoryError: Java heap space,也就是java堆溢出。除此之外,没有其它明显信息。
- 使用arthas查看线程信息,输入 thread -n 10,列出10个占用cpu最高的线程堆栈信息。如下图所示,最高的线程cpuUsage=10.59%,紧接着其它的线程的堆栈信息跟这个几乎一样,多次出现"java.util.regex.Pattern$Loop.match"字样,Loop是循环的意思,猜测可能是陷入了死循环。
- 网上搜索"java.util.regex.Pattern$Loop.match",发现是正则表达式的一个缺陷,当某些情况下写法不规范并且待匹配的内容很长时,会触发回溯机制,导致了循环的产生。正则表达式有三个模式,默认是贪婪模式,另外两个是独占模式和懒惰模式,在其它两个模式不会有这种问题。
- 查看源码,定位到该代码是在一个三方包里,同时追踪到该正则表达式所在的位置,是在一个配置文件里。
解决
解决方法有两种,一个是优化写法,一个是采用其它模式。
ini
String regular_1 = "/(([a-z0-9]|[A-Z]|\-)+).html"; //原写法
String regular_2 = "/([a-z0-9A-Z-]+).html"; //优化后的写法
String regular_3 = "/([a-z0-9A-Z-]++).html"; //优化并采用独占模式
通过测试发现,优化写法后速度提高很多,而优化后再采用独占模式,速度更快。最终采用两种方式结合,优化写法同时采用独占模式。最后上线更新,问题解决。
参考文档:
jstack 工具:www.cnblogs.com/snake23/arc...
arthas使用:www.macrozheng.com/project/art...
arthas官网:arthas.aliyun.com/doc/
正则表达式优化:www.jianshu.com/p/f31374611...