利用 Arthas 精准定位 Java 应用 CPU 负载过高问题
云栖号资讯:【点击查看更多行业资讯】
在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来!
最近我们线上有个应用服务器有点上头,CPU 总能跑到 99%,我寻思着它流量也不大啊,为啥能把自己整这么累?于是我登上这台服务器,看看它到底在干啥!
以前碰到类似问题,可能会考虑使用top -Hp 加 jstack命令去排查,虽然能大致定位到问题范围,但有效信息还是太少了,多数时候还是要靠猜。
今天向大家推荐一款更高效更精准的工具:Arthas!
Arthas 是 Alibaba 开源的 Java 诊断工具,能够帮助我们快速定位线上问题。基本的安装使用可以参考官方文档: https://alibaba.github.io/arthas 这次我们利用它来排查 CPU 负载高的问题。
CPU 负载过高一般是某个或某几个线程有问题,所以我们尝试使用第一个命令:thread,这个命令会显示所有线程的信息,并且把 CPU 使用率高的线程排在前面。
[arthas@384]$ thread Threads Total: 112, NEW: 0, RUNNABLE: 26, BLOCKED: 0, WAITING: 31, TIMED_WAITING: 55, TERMINATED: 0 ID NAME STATE %CPU TIME 108 h..ec-0 RUNNABLE 51 4011:48 100 h..ec-2 RUNNABLE 48 4011:51
...
为了方便阅读,删掉了一些不重要的信息
可以看到,CPU 资源几乎被前两个线程占满,并且已经执行了 4000 多分钟,我们服务器也就启动了两天,可见这两天它们是一刻也没闲着!
那它们究竟在干什么呢?我们可以使用命令:thread id,查看线程堆栈
[arthas@384]$ thread 108 "http-nio-7001-exec-10" Id=108 cpuUsage=51% RUNNABLE at c.g.c.c.HashBiMap.seekByKey(HashBiMap.java) at c.g.c.c.HashBiMap.put(HashBiMap.java:270) at c.g.c.c.HashBiMap.forcePut(HashBiMap.java:263) at c.y.r.j.o.OaInfoManager.syncUserCache(OaInfoManager.java:159)
也可以使用 thread -n 3 命令打印出 CPU 占比最高的前三个线程,这差不多是top -Hp & printf & jstack 三令合一的效果了
可以看到,这个线程一直在执行HashBiMap.seekByKey方法(可以重复执行几次thread id确保该线程执行的方法没有时刻在变化),造成这个问题一般有两个原因:
- seekByKey方法被循环调用
- seekByKey内部有死循环
先看一下是不是第一种,我们使用 tt 命令监听一下这个方法的调用情况
tt -t com.google.common.collect.HashBiMap seekByKey -n 100
注意:在线上执行这个命令的时候,一定要记得加上 -n 参数,否则线上巨大的流量可能会瞬间撑爆你的 JVM 内存
执行结果显示,seekByKey方法并没有被一直调用,那大概率是seekByKey方法内部有死循环。看下这个方法内部的逻辑,我们可以使用jad com.google.common.collect.HashBiMap seekByKey命令反编译这个方法,这样做的好处是显得比较高端,不过我还是打算直接找到源码,说不定还有注释。
源码如下:
private BiEntry<K, V> seekByKey(@Nullable Object key, int keyHash) { for (BiEntry<K, V> entry = hashTableKToV[keyHash & mask]; entry != null; entry = entry.nextInKToVBucket) { if (keyHash == entry.keyHash && Objects.equal(key, entry.key)) { return entry; } } return null; }
然后并没有注释,还好这个方法逻辑比较简单,也很容易看懂。
- 通过 hash 找到 bucket,每个 bucket 是一个链表
- 遍历链表,找到这个 key 对应的 entry。这里要留意下 entry 的下一个节点是 nextInKToVBucket,后文中会用到
发生了死循环,我们猜想可能是因为这个链表有环路。那么有没有办法验证这个猜想呢?
答案是 有!那么如何验证呢?
首先我们要获得这个HashBiMap对象,以便于查询对象里的数据。获得这个对象有很多办法,比如监听这个对象的某个方法,然后主动触发这个方法。这里向大家介绍一种更为通用的方法,这个方法在 SpringMVC 程序里非常好用。
因为我们是 SpringMVC 应用,所有请求都会被RequestMappingHandlerAdapter拦截,我们通过 tt 命令,监听invokeHandlerMethod的执行,然后在页面随便点点,就会得到以下内容:
[arthas@384]$ tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod -n 10 Press Q or Ctrl+C to abort. Affect(class-cnt:1 , method-cnt:1) cost in 622 ms. {1} INDEX COST(ms) OBJECT CLASS METHOD ------------------------------------------------------------------------------------ 1000 481.203383 0x481eb705 RequestMappingHandlerAdapter invokeHandlerMethod 1001 3.432024 0x481eb705 RequestMappingHandlerAdapter invokeHandlerMethod ... {1}
tt 命令会记录方法调用时的所有入参和返回值、抛出的异常、对象本身等数据。INDEX 字段代表着一次调用,后续 tt 还有很多命令都是基于此编号指定记录操作。
我们可以通过 -i 参数后边跟着对应的 INDEX 编号查看这条记录的详细信息。再通过 -w 参数,指定一个 OGNL 表达式,查找相关对象
[arthas@384]$ tt -i 1000 -w 'target.getApplicationContext()' @AnnotationConfigServletWebServerApplicationContext[ reader=@AnnotatedBeanDefinitionReader[org.springframework.context.annotation.AnnotatedBeanDefinitionReader@50294e97], scanner=@ClassPathBeanDefinitionScanner[org.springframework.context.annotation.ClassPathBeanDefinitionScanner@5eeeaae2], annotatedClasses=@LinkedHashSet[isEmpty=true;size=0], basePackages=null,
Arthas 会把当前执行的对象放到 target 变量中,通过 target.getApplicationContext() 就得到了 SpringContext 对象,然后,我们就可以为所欲为了!
接下来我们需要用 OGNL 写一个函数,来实现链表的环路检测,在 OGNL 里写一段环路检测代码里是不太容易的,这里我用了一个取巧的伪实现(有更好思路的欢迎在评论区留言)
#loopCnt=0, #foundCycle=:[ #this == null ? false : #loopCnt > 50 ? true : ( #loopCnt = #loopCnt + 1, #foundCycle(#this.nextInKToVBucket) )]
因为我知道一个 bucket 不太可能有 50 个以上的节点,所以就通过遍历次数是否大于 50 来判断是否有环路。
完整的命令:
tt -i 1000 -w ‘target.getApplicationContext().getBean(“oaInfoManager”).userCache.entrySet().{delegate}.{^ #loopCnt = 0,#foundCycle = :[ #this == null ? false : #loopCnt > 50 ? true : (#loopCnt = #loopCnt + 1, #foundCycle(#this.nextInKToVBucket))], #foundCycle(#this)}.get(0)’ -x 2
命令解析:
- 获取HashBiMap对象:target.getApplicationContext().getBean("oaInfoManager").userCache
- 遍历所有 entry,取出第一个有环路的 entry
- -x 参数指定展开层级,我们需要将这个参数设置的比环要大一些,才能确保可以发现环路。这里我们的环路非常小,所以设置成了 2
执行结果如下:
@BiEntry[ key=@String[张三], value=@Long[1111], nextInKToVBucket=@BiEntry[ key=@String[李四], value=@Long[2222], nextInKToVBucket=@BiEntry[张三 =1111] ] ]
可以看到是有 张三 -> 李四 -> 张三 这样一个环路。至此,造成死循环的原因确定了下来。结合两个线程几乎同时启动,又同时在执行HashBiMap.forcePut方法,容易想到是因为并发导致了数据的不一致,这一点也可以验证,不过由于篇幅有限,这里就不再赘述。
找到了问题,就成功了 99%,解决这个问题的方法非常简单,就是对syncUserCache方法加一个 synchronized 关键字!
结语
这次遇到的问题并不复杂,用jstack命令也可以解决的了。但我们希望通过这样一个案例,向大家展示 Arthas 一些强大的功能,帮助大家打开思路,未来在遇到更复杂场景时,可以多一些趁手的工具!
【云栖号在线课堂】每天都有产品技术专家分享!
课程地址:https://yqh.aliyun.com/live立即加入社群,与专家面对面,及时了解课程最新动态!
【云栖号在线课堂 社群】https://c.tb.cn/F3.Z8gvnK
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
vivo 悟空活动中台-基于行为预设的动态布局方案
本文首发于 vivo互联网技术 微信公众号 链接: https://mp.weixin.qq.com/s/CwLAV2j7Uxam01m1p7cXxg 作者:悟空中台研发团队 【悟空活动中台】系列往期精彩文章: 《揭秘 vivo 如何打造千万级 DAU 活动中台 - 启航篇》 主要为大家讲述 vivo 活动中台的能力与创新。 《悟空活动中台 - 微组件状态管理(上)》介绍了活动页内 RSC 组件之间的状态管理和背后的设计思路。 《悟空活动中台 - 微组件状态管理(下)》探索平台和跨沙箱环境下的微组件状态管理。 一、写在前面 作为前端工程师,页面布局是基本功。面对悟空中台的海量的活动需求,仅仅有几招常规的布局套路显然是难以招架的,悟空开发者团队从个性化需求中提炼特定场景下的共性特点,设计了多个“创意布局”方案。 本文以“满屏”场景下的页面布局思考为切入点,以微组件为元素单元,提供了一种新的布局方案设计思路——基于行为预设的动态布局方案,并详细的分享了设计目的及具体实现方案,对技术基础要求不高,是一篇男女老少皆宜的“技术甜点”。 二、灵感缘起 灵感往往并不是凭空产生的,而是与问题的出现形成...
- 下一篇
HBuilderX 2.7.x 发布,插件系统开放,C++ 版的 vscode 来了
HBuilderX简介 HBuilderX是一款免费的前端开发工具(IDE),C++内核架构:轻巧、极速,是轻如编辑器、强如IDE的合体版本。查看详细介绍。 重大升级:插件系统开放 HBuilderX自发布以来,目前已累计500万+的开发者在使用,不少开发者在呼吁HBuilderX开放插件系统,想共同参与打造更好的开发工具。 所以从2.7+起,我们正式开放HBuilderX插件系统。考虑到HBuilderX主要是为前端工程师打造的工具,我们将插件的开发语言定为了JavaScript,插件API及规范则参考了vscode,实现对vscode插件的兼容,这样vscode的插件也可以快速的移植到HBuilderX中。 下图为HBuilderX的插件架构: 相对于vscode的优势: HBuilderX的UI进程渲染采用的是C++/Qt实现,拥有: 更优秀的性能 更快的启动速度 更小的标准包体积 并且同时具备了vscode的扩展性。 针对已兼容的vscode插件API,我们移植了几个前端优秀开源的vscode插件,比如eslint、ftp等。 完整的的更新日志查看http://update....
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群