写点什么

利用 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:004483

评论

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

深入浅出带你掌握线程、多线程和线程池

华为云开发者联盟

Java 线程 多线程 线程池 操作系统

5分钟教你学会GaussDB数据分布策略设计

华为云开发者联盟

数据库 分布式数据库 GaussDB GaussDB(for openGauss) 数据分布

Python基础之:struct和格式化字符

程序那些事

Python 数据分析 程序那些事

在有道 | L同学:一位十五年有道人的成长故事

有道技术团队

分享 访谈录 阅读 网易有道

征服耶鲁教授的算法大神程序媛,是如何践行“以人为本”开发智慧社区大脑的?

华为云开发者联盟

算法 音视频 智慧社区 华为智慧园区数字平台 数字平台

合约量化交易机器人系统开发|合约量化交易机器人APP软件开发

合约量化交易APP开发|合约量化交易系统软件开发

系统开发

磁盘快照服务USnap:公有云连续数据保护(CDP)系统升级改造实践

UCloud技术

我们真的可以使世界成为无密码的地方吗?

龙归科技

网络 安全性

量化合约机器人APP开发|量化合约机器人软件系统开发

系统开发

【有奖征文】WEB前端大作战,走在技术最前端!

华为云开发者联盟

node.js Vue 大前端 Web Web框架

维度数据模型建模过程(Kimball)

大数据技术指南

数据仓库 维度建模 4月日更

13年Java开发经验精华总结!29大核心知识模块,带你直达架构师!

Java架构追梦

Java 阿里巴巴 架构 全栈知识点

合约跟单系统开发|合约跟单APP软件开发

MySQL性能监控与调优

Sakura

4月日更

HTTPS双向认证

上海派拉基础研发

https HTTP ssl SSL 连接

如何利用ipad随时随地开发代码

程序员石磊

ipad 编程 远程

中国SaaS的终局:神仙打架,小鬼遭殃

ToB行业头条

合约量化机器人系统开发|合约量化机器人软件APP开发

系统开发

使用transform制作书本翻页效果

空城机

JavaScript 大前端 4月日更 书本翻页

web简易视频聊天室+媒体流插入

anyRTC开发者

大前端 音视频 WebRTC RTC

构建智慧金融新引擎|DataPipeline与巨杉数据库完成产品兼容互认证

DataPipeline数见科技

一周信创舆情观察(4.5~4.11)

统小信uos

借助 Serverless 容器服务Cube,筷子科技轻松打造 10 万+ 爆款短视频

UCloud技术

vue2的$refs在vue3组合式API中的替代方法

devpoint

Vue3 $refs vue2 this.$refs

拍乐云入选 2021 爱分析·产业数字化厂商全景报告

拍乐云Pano

RTC

很坑的Could not transfer artifact报错

01Running

maven Mac IDEA

聪明人的训练(十六)

Changing Lin

4月日更

Linux df 命令

一个大红包

linux命令 4月日更

Java 常见 bean mapper 的性能及原理分析

Java小咖秀

Java bean Copier

使用Python映射,过滤和缩减函数:所有您需要知道的

华为云开发者联盟

Python 函数 映射 内置函数

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