写点什么

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

评论

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

WordPress 是什么?

InfoQ IT百科

现在常用的在线协作文档软件有哪些?

InfoQ IT百科

一个WPS账号可以在多个设备同时登陆吗?

InfoQ IT百科

使用WPS需要注册/登录账号吗?

InfoQ IT百科

有哪些适合程序员用的笔记应用?

InfoQ IT百科

App怎么做灰度发布?

InfoQ IT百科

阿里云 云效一站式研发平台

阿里云云效

阿里云 DevOps 云原生 研发 一站式研发平台

现在常用的视频会议软件有哪些?

InfoQ IT百科

Java篇-序列化与反序列化

是老郭啊

Java 对象 序列化 反序列化

如何优雅高效地管理公司文档?

小炮

文档 文档管理

怎么让网站在搜索结果中排更前?

InfoQ IT百科

如何修改电脑文件格式?

InfoQ IT百科

如何部署自己的网站?

InfoQ IT百科

如何隐藏电脑上的文件?

InfoQ IT百科

2022鲲鹏开发者创享日即将扬帆起航 与开发者共创未来共享非凡成就

科技热闻

主流的网站服务器架构有哪些?

InfoQ IT百科

WPS是什么软件?

InfoQ IT百科

做 App 还是小程序好?

InfoQ IT百科

安卓App和鸿蒙App有什么不同?

InfoQ IT百科

注册域名后,怎么创建个人网站?

InfoQ IT百科

有哪些好用的代码编辑器?

InfoQ IT百科

WPS有哪些隐藏的使用小技巧?

InfoQ IT百科

目前WPS支持在哪些设备上使用?

InfoQ IT百科

有哪些比较靠谱的低代码开发平台?

InfoQ IT百科

移动App的设计流程是怎样的?

InfoQ IT百科

如何添加字体?

InfoQ IT百科

低代码开发平台能开发哪些应用?

InfoQ IT百科

软件激活码是什么?

InfoQ IT百科

怎么做SEO网站优化?

InfoQ IT百科

zip格式的文件怎么打开?

InfoQ IT百科

从杀慢查询入手来预防 MySQL 雪崩的办法

Qunar技术沙龙

dba

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