写点什么

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

评论

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

阿里云云原生应用平台总经理丁宇:“连接、合作、赋能”,携手加速器伙伴助力企业云上创新

阿里巴巴云原生

EventBridge 事件总线及 EDA 架构解析

阿里巴巴云原生

java培训SpringBoot性能优化

@零度

Java springboot

关于React项目本地开发设置Https的过程

隔壁的猫

前端 React 3月月更

大数据培训Flink中常见问题定位

@零度

大数据 flink

数字经济“双碳”目标下,“东数西算”数据中心为何依靠液冷散热技术节能减排?

GPU算力

东数西算 GPU服务器

谷歌云对象存储攻防

火线安全

云原生 云安全 云存储

云原生安全实践

火线安全

云原生 安全 云安全

碰到运维难题怎么快速解决?有工具推荐吗?

行云管家

运维 网络运维 IT运维 服务器运维

超级app+轻应用=未来?

發財KK

移动应用 轻应用 快应用 App生态

尚硅谷监控告警系统(Zabbix)视频教程发布

@零度

大数据 zabbix

OpenHarmony 标准系统 HDF 框架之 I2C 驱动开发

Anna

开源 后端 直播 OpenHarmony I2C

负载均衡,你想了解的全在这里!

博文视点Broadview

Android技术分享|【自定义View】实现Material Design的Loading效果

anyRTC开发者

android 音视频 移动开发 自定义view loading

4种典型限流实践保障应用高可用|云效工程师指北

阿里云云效

云计算 阿里云 云原生 系统安全 研发

DevSecOps: 让大家都 Happy 的安全软件构建模式

火线安全

云原生 软件架构 DevSecOps 云安全

平衡树:为什么Redis内部实现用跳跃表

华为云开发者联盟

redis 数据结构 集合 跳跃表 平衡树

产品经理必看的高效产品文档撰写指南

小炮

产品文档

云计算时代,好用的IT运维软件我给推荐行云管家!

行云管家

云计算 运维 网络运维 IT运维

华为云企业级Redis揭秘第17期:集群搭载多DB,多租隔离更降本

华为云开发者联盟

数据库 redis 集群 GaussDB(for Redis) 多DB

TypeScript里string和String,真不是仅仅是大小写的区别

华为云开发者联盟

JavaScript typescript string ts

有奖互动|中国信通院联合 OceanBase 邀您参加《数据库发展研究报告(2022)》调研问卷

OceanBase 数据库

关于 Nvm

隔壁的猫

node.js 前端 NVM 3月月更

DevSecOps邂逅云原生:云原生时代下的持续安全

火线安全

DevOps 云原生 云安全 云原生安全

阿里云 VPC 内网性能测试最佳实践

阿里巴巴云原生

Flink 在米哈游的落地实践

Apache Flink

大数据 flink 编程 流计算 实时计算

性能指标、响应时间、并发量…聊聊性能优化的衡量指标

华为云开发者联盟

性能优化 高并发 响应时间 性能指标 并发量

数盾科技加入,携手龙蜥社区提升网络安全整体防护能力

OpenAnolis小助手

开源 网络安全 数盾科技 密码算法

web前端培训JS解构赋值知识点分享

@零度

JavaScript web前端

浅谈SaaS多租户数据隔离和共享

数商云

SASS

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