写点什么

利用 Arthas 精准定位 Java 应用 CPU 负载过高问题

  • 2020-06-07
  • 本文字数:3311 字

    阅读完需:约 11 分钟

利用 Arthas 精准定位 Java 应用 CPU 负载过高问题

最近我们线上有个应用服务器有点上头,CPU 总能跑到 99%,我寻思着它流量也不大啊,为啥能把自己整这么累?于是我登上这台服务器,看看它到底在干啥!


以前碰到类似问题,可能会考虑使用top -Hpjstack命令去排查,虽然能大致定位到问题范围,但有效信息还是太少了,多数时候还是要靠猜。


今天向大家推荐一款更高效更精准的工具:Arthas


Arthas 是 Alibaba 开源的 Java 诊断工具,能够帮助我们快速定位线上问题。基本的安装使用可以参考官方文档:https://alibaba.github.io/arthas 这次我们利用它来排查 CPU 负载高的问题。


CPU 负载过高一般是某个或某几个线程有问题,所以我们尝试使用第一个命令:thread,这个命令会显示所有线程的信息,并且把 CPU 使用率高的线程排在前面。


[arthas@384]$ threadThreads Total: 112, NEW: 0, RUNNABLE: 26, BLOCKED: 0, WAITING: 31, TIMED_WAITING: 55, TERMINATED: 0ID  NAME    STATE    %CPU  TIME108 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确保该线程执行的方法没有时刻在变化),造成这个问题一般有两个原因:


  1. seekByKey方法被循环调用

  2. 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;  }
复制代码


然后并没有注释,还好这个方法逻辑比较简单,也很容易看懂。


  1. 通过 hash 找到 bucket,每个 bucket 是一个链表

  2. 遍历链表,找到这个 key 对应的 entry。这里要留意下 entry 的下一个节点是 nextInKToVBucket,后文中会用到


发生了死循环,我们猜想可能是因为这个链表有环路。那么有没有办法验证这个猜想呢?


答案是 !那么如何验证呢?


首先我们要获得这个HashBiMap对象,以便于查询对象里的数据。获得这个对象有很多办法,比如监听这个对象的某个方法,然后主动触发这个方法。这里向大家介绍一种更为通用的方法,这个方法在 SpringMVC 程序里非常好用。


因为我们是 SpringMVC 应用,所有请求都会被RequestMappingHandlerAdapter拦截,我们通过 tt 命令,监听invokeHandlerMethod的执行,然后在页面随便点点,就会得到以下内容:


[arthas@384]$ tt -t org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter invokeHandlerMethod -n 10Press Q or Ctrl+C to abort.Affect(class-cnt:1 , method-cnt:1) cost in 622 ms.
INDEX COST(ms) OBJECT CLASS METHOD------------------------------------------------------------------------------------ 1000 481.203383 0x481eb705 RequestMappingHandlerAdapter invokeHandlerMethod 1001 3.432024 0x481eb705 RequestMappingHandlerAdapter invokeHandlerMethod...
复制代码


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,
复制代码


OGNL 使用文档:https://commons.apache.org/proper/commons-ognl/language-guide.html


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


命令解析:


  1. 获取HashBiMap对象:target.getApplicationContext().getBean("oaInfoManager").userCache

  2. 遍历所有 entry,取出第一个有环路的 entry

  3. -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 一些强大的功能,帮助大家打开思路,未来在遇到更复杂场景时,可以多一些趁手的工具!


有任何问题欢迎在评论区留言或者联系我本人:zhangyunxiang@youzan.com


本文转载自公众号有赞 coder(ID:youzan_coder)。


原文链接


https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455760839&idx=1&sn=fb414082e0a11d0b07cfae29f12b6437&chksm=8c6869e2bb1fe0f4195ea0e1aba9465e995ef6c900431e9178eeab2df8ae2852bd0013e3a3f8&scene=27#wechat_redirect


2020-06-07 10:004371

评论

发布
暂无评论
发现更多内容

技术盘点:消息中间件的过去、现在和未来

Apache RocketMQ

阿里云 云原生 消息队列 EventBridge

烟草行政处罚案卷制作与评查平台被中国信通院认定为2025年商业产品及企业典型案例

中烟创新

全链路灰度之 RocketMQ 灰度

Apache RocketMQ

阿里云 云原生 消息队列

MySQL 自增主键满了咋办?

秃头小帅oi

消息队列 RocketMQ 遇上可观测:业务核心链路可视化

Apache RocketMQ

阿里云 RocketMQ 云原生 消息队列

在AI技术快速落地的时代,挖掘用户真实需求成为关键——某知名Windows优化工具需求洞察

qife122

需求分析 产品优化

投票活动:高效投票

微擎应用市场

二手车小程序系统:汽车交易

微擎应用市场

印度软件外包业的AI大逃杀

脑极体

AI

融云十周年纪录片《十字路口》之《妳很了不起》,看到焦虑的本质

融云 RongCloud

Dify基础应用篇 (5) | 创建第一个对话型应用:智能客服机器人(保姆级教程)

测吧(北京)科技有限公司

软件测试 人工智能、 dify

RocketMQ-Streams 首个版本发布,轻量级计算的新选择

Apache RocketMQ

在线律师咨询小程序系统:法律服务

微擎应用市场

【跨国数仓迁移最佳实践6】MaxCompute SQL语法及函数功能增强,10万条SQL转写顺利迁移

阿里云大数据AI技术

sql 阿里云 MaxCompute ODPS BigQuery

轻量化赋能,小程序容器激活 SuperApp 生态潜能

Byte_Me

小程序 App 小程序容器 轻量化 superapp

鸿蒙生态7月技术月报 | HarmonyOS 5.1 开发特性详解

优测云服务平台

鸿蒙 HarmonyOS

金标联盟发布隐私权限体系,共建安卓生态安全体验

科技热闻

Apache RocketMQ + Hudi 快速构建 Lakehouse

Apache RocketMQ

阿里云 RocketMQ 云原生 消息队列

LevelDB 辅助工具类

数新网络官方账号

利用Cron定时任务管理OpenVPN用户访问权限

qife122

OpenVPN Bash脚本

API 接口驱动 1688 采购自动化:从商品获取到下单支付的全流程贯通

Noah

平安保险基于 SPI 机制的 RocketMQ 定制化应用

Apache RocketMQ

阿里云 RocketMQ 云原生 中间件 消息队列

互联网 + 共享农业小程序系统:智慧农业

微擎应用市场

从Swatch“辱华”风波看品牌危机应对:舆情监测为何是出海品牌的必修课?

沃观Wovision

舆情监测系统 海外舆情监测 Swatch

稳中求进 以进促稳 | 光大永明资产召开2025年年中工作会议

北京中暄互动广告传媒有限公司

Playwright教程基础篇(2)元素定位策略大全

测吧(北京)科技有限公司

软件测试 #人工智能

新闻速递丨Altair 重磅发布:全球100 个 AI 应用案例,助力企业加速 AI 技术应用

Altair RapidMiner

人工智能 AI 仿真 CAE RapidMiner

EventBridge消息路由|高效构建消息路由能力

Apache RocketMQ

云原生 消息队列 EventBridge 异地双活 事件总线Eventbridge

低代码 + 智能体能有啥新花样?直接上实操!

行云创新

低代码 智能体 AI Agent

医科+AI 人才培养丨首都医科大学AI通识教育新路径探索与实践分享

ModelWhale

人工智能 AI通识教育 首都医科大学

信创技术架构:自主可控体系的构建与实践路径

Byte_Me

利用 Arthas 精准定位 Java 应用 CPU 负载过高问题_开源_张云翔_InfoQ精选文章