作者 | 何波

【Arthas 官方社区正在举行征文活动,参加即有奖品拿~点击投稿

1.前言

背景

线上代码经常会出现 CPU 占用过高的情况,按以往经验我会使用 top 指令,进一步借助于 jstack 去查看具体信息从而进行问题排查,但基本上都逃不过需要重新发包的局面,及时是一个增量包,应用也需要短暂停启。后来运维大兄弟让我试一下 Arthas,说是可以进行代码的热更新操作,正好来试一下。

关于 Arthas 的安装与基础使用可以参考下面两篇文章:

  • Arthas 安装与监听 SpringBoot 应用
  • Arthas 基础指令使用说明

环境

JDK1.8
SPringBoot 2.2.2
Arthas
Linux

测试代码:

@RequestMapping(value = "/bigThread")
@ResponseBody
public String bigThread(int id) {ArthasService.test();while (true) {Thread t2 = new Thread();t2.start();id ++;if(100000 == id) {return String.valueOf(id);}}
}

思路

2.thread -b 查看是否有阻塞线程

thread -b, 找出当前阻塞其他线程的线程,执行完之后并未发现,说明该线程并非一直阻塞、一直执行的。

3.thread 查看占用最高的线程

当 thread 之后不跟参数时,显示当前全部线程信息,我觉得 thread -n 10,展示前 10 应该就够用,可根据实际需要自己决定。

下图可以很直观的看出,我们的应用瞬间占用了 77% 的 CPU(这里我是发起请求瞬间,通过 thread 查看的,所以比较直观,生产环境应该只有阻塞,死锁这种状态才会比较直观)。

4.thread id 查看具体信息

在上一步基础上,我们进一步查看,thread 15(因为上面的 ID=15)。

他的大致意思就是:线程在等待一个条件从而继续执行,可以看到方法是在执行 LinkedBlockingQueue.take 方法时候,查看这个方法的 API 提示如下:

public E take() throws InterruptedException {E x;int c = -1;final AtomicInteger count = this.count;final ReentrantLock takeLock = this.takeLock;takeLock.lockInterruptibly();try {while (count.get() == 0) {notEmpty.await();}x = dequeue();c = count.getAndDecrement();if (c > 1)notEmpty.signal();} finally {takeLock.unlock();}if (c == capacity)signalNotFull();return x;
}

其中:AtomicInteger 是保证高并发情况下的原子性,ReentrantLock 标识可重入锁,都是 JUC 包下需要了解的这里不赘述,需要的百度了解下。

这段代码关键点就在于:notEmpty.await(),从队列中消费数据,当队列为空是,线程阻塞,所以我们大致知道现在出现的问题是线程阻塞,但是还是不知道具体哪行代码的问题。

如果能够明确知道这次更改了哪些代码,可以直接执行步骤 6,不知道的话可以通过步骤 5 来定位问题。

5.watch 查看哪个 Controller 执行了代码

watch org.springframework.web.servlet.DispatcherServlet getHandler returnObj

这个脚本可以检测一切通过 DispatcherServlet 匹配 Handler 的方法,也就是进入 Controller 的请求,如下:

找到了对应的代码之后,我们来进一步观察异常信息,这里可能会有一个问题:就是我明明能通过日志去查看错误信息,为什么还需要这么繁琐的去操作。我的业务场景是:日志还是非常大的,刚捞到就被刷过去了,这时候定位日志不是很好操作,当然想捞下来日志肯定也是可以的,也很直观,我一般也都是去查看日志进行问题定位,这里也是提供一个思路。

6.watch 该方法异常信息

watch 类全路径 方法名 "{params[0],throwExp}" -e -x 2

如上,错误很直观的提示了出来,下面就可以修复解决了,这里我们也可以通过 trace 指令,查看执行时长:

trace 类全路径 方法名 "{params[0],throwExp}" -e -x 2

返回信息如下,也可以看到错误信息,和每个方法执行的时长。

[arthas@10999]$ trace com.arthas.controller.OrderController bigThread
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 53 ms, listenerId: 10
`---ts=2020-08-19 14:45:57;thread_name=http-nio-0.0.0.0-8080-exec-10;id=16;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[1452.684406ms] com.arthas.controller.OrderController:bigThread() [throws Exception]+---[0.168814ms] com.arthas.service.ArthasService:test() #20`---throw:java.lang.OutOfMemoryError #-2 [unable to create new native thread]

7.jad 反编译热更新

在上面知道问题之后,我们就来定位问题就好了。

命令:jad 类全路径 方法名

[arthas@13190]$ jad com.arthas.controller.OrderControllerClassLoader:
+-org.springframework.boot.loader.LaunchedURLClassLoader@17f052a3                                                                                                                           +-sun.misc.Launcher$AppClassLoader@3d4eac69                                                                                                                                               +-sun.misc.Launcher$ExtClassLoader@45f45fa1                                                                                                                                             Location:
file:/opt/software/arthas/Arthas.jar!/BOOT-INF/classes!/                                                                                                                                    /** Decompiled with CFR.* * Could not load the following classes:*  com.arthas.service.ArthasService*  org.springframework.stereotype.Controller*  org.springframework.web.bind.annotation.RequestMapping*  org.springframework.web.bind.annotation.ResponseBody*/
package com.arthas.controller;import com.arthas.service.ArthasService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;@Controller
public class OrderController {@RequestMapping(value={"/bigThread"})@ResponseBodypublic String bigThread(int id) {ArthasService.test();do {Thread t2 = new Thread();t2.start();} while (100000 != ++id);return String.valueOf(id);}
}Affect(row-cnt:1) cost in 1513 ms.

此时代码就被反编译了,为了能够更改,所以我们需要输出为 java 文件。

指令:jad com.arthas.controller.OrderController > /tmp/OrderController.java

即:jad 类全路径 方法名 > 存储路径/存储名称

然后到 tmp 路径下 vi 修改 java 文件即可,修改完成之后,查看对应的 classloader 为编译做准备。

sc -d *OrderController | grep classLoaderHash
mc -c 17f052a3 /tmp/OrderController.java -d /tmp

但是这里编译出错了,官方提示:

所以我们本地编译好 class 文件,上传上去是一样的。

编译前调用

[arthas@13190]$ trace com.arthas.controller.OrderController bigThread
Press Q or Ctrl+C to abort.
Affect(class count: 1 , method count: 1) cost in 77 ms, listenerId: 2
`---ts=2020-08-19 15:51:46;thread_name=http-nio-0.0.0.0-8080-exec-1;id=d;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[6734.666529ms] com.arthas.controller.OrderController:bigThread() [throws Exception]+---[0.786517ms] com.arthas.service.ArthasService:test() #20`---throw:java.lang.OutOfMemoryError #-2 [unable to create new native thread]

更新前代码

@RequestMapping(value = "/bigThread")
@ResponseBody
public String bigThread(int id) {ArthasService.test();while (true) {Thread t2 = new Thread();t2.start();id ++;if(100000 == id) {return String.valueOf(id);}}
}

更新后代码

@RequestMapping(value = "/bigThread")
@ResponseBody
public String bigThread(int id) {ArthasService.test();Thread t2 = new Thread();t2.start();return "success";
}

编译指令

[arthas@13190]$ redefine /tmp/OrderController.class
redefine success, size: 1, classes:
com.arthas.controller.OrderController

编译后调用三次

`---ts=2020-08-19 15:52:02;thread_name=http-nio-0.0.0.0-8080-exec-3;id=f;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[5.609405ms] com.arthas.controller.OrderController:bigThread()`---[0.204675ms] com.arthas.service.ArthasService:test() #20`---ts=2020-08-19 15:52:04;thread_name=http-nio-0.0.0.0-8080-exec-4;id=10;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[3.900149ms] com.arthas.controller.OrderController:bigThread()`---[0.14636ms] com.arthas.service.ArthasService:test() #20`---ts=2020-08-19 15:52:04;thread_name=http-nio-0.0.0.0-8080-exec-5;id=11;is_daemon=true;priority=5;TCCL=org.springframework.boot.web.embedded.tomcat.TomcatEmbeddedWebappClassLoader@1f1c7bf6`---[1.90945ms] com.arthas.controller.OrderController:bigThread()`---[0.147353ms] com.arthas.service.ArthasService:test() #20

可以发现时间从 6734.666529ms 变成 3ms 左右,说明热更新的代码生效了。

8.profile 绘制火焰图做后续分析

如下图所示:

Arthas 征文活动火热进行中

Arthas 官方正在举行征文活动,如果你有:

  • 使用 Arthas 排查过的问题
  • 对 Arthas 进行源码解读
  • 对 Arthas 提出建议
  • 不限,其它与 Arthas 有关的内容

欢迎参加征文活动,还有奖品拿哦~点击投稿

“阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的公众号。”

记 Arthas 实现一次 CPU 排查与代码热更新相关推荐

  1. ms查约束具体代码_记 Arthas 实现一次 CPU 排查与代码热更新

    简介:线上代码经常会出现 CPU 占用过高的情况,按以往经验我会使用 top 指令,进一步借助于 jstack 去查看具体信息从而进行问题排查,但基本上都逃不过需要重新发包的局面,及时是一个增量包,应 ...

  2. 热更新机制的实现_记 Arthas 实现一次 CPU 排查与代码热更新

    简介:线上代码经常会出现 CPU 占用过高的情况,按以往经验我会使用 top 指令,进一步借助于 jstack 去查看具体信息从而进行问题排查,但基本上都逃不过需要重新发包的局面,及时是一个增量包,应 ...

  3. class在线反编译_Java在线诊断-Arthas生产代码热更新

    生产代码热更新 背景:在某次生产环境上出现了Bug,但不能重启服务,只能对生产代码进行热更新. 准备工作 首先保证开发环境代码与生产一致 提前安装阿里巴巴开源的Java诊断工具Arthas,如果生产环 ...

  4. arthas classclassloader相关命令之二:jad、mc、redifine实现代码热更新(热替换)

    jad mc mc就类似于javac redefine redefine只能修改现有方法,不能增加属性和方法 redefine的限制 案例:redefine结合jad.mc一起使用实现代码热替换 总结

  5. 使用Arthas热更新时,lombok注解导致mc命令编译失败

    看Arthas文档,其中代码热更新vb.net教程功能很吸引人,用文档的demo项目热更新,没有c#教程任何问题. 但是,用公司项目的代码一试,mc编译时报错. 网上查了很多博客,有python基础教 ...

  6. arthas 热更新

    注意:如下几点情况无法进行arthas热更新: 增加.删除,修改了 field(字段) : attempted to add Or remove field; 增加.删除了 method (方法): ...

  7. 阿里云centos环境之被dos,syn攻击策略和排查方法,持续更新九

    阿里云centos环境之被dos,syn攻击策略和排查方法,持续更新<九> 阿里云centos环境之被dossyn攻击策略和排查方法持续更新九 查看cpu使用情况 查看网络连接情况 检查端 ...

  8. arthas命令redefine实现Java热更新

    Arthas非常重要的命令redefine,主要作用是加载外部的.class文件,用来替换JVM已经加载的类,总结起来就是实现了Java的热更新. redefine在一下几种情况中会失败:1.增加了f ...

  9. 飞腾CPU虚拟化相关代码分析(一)

    飞腾CPU虚拟化相关代码分析(一)-- 函数el2_setup 函数el2_setup是ARM64体系结构下Linux内核运行的第一个和虚拟化相关的函数. 相关概念 ARM64支持两种虚拟方式:Hyp ...

最新文章

  1. Windows轻松实现linux shell环境:gitforwindows
  2. bleve搜索引擎源码分析之索引——mapping真复杂啊
  3. Spring - Java/J2EE Application Framework 应用框架 第 11 章 使用ORM工具进行数据访问
  4. 一键实现变老变年轻——Windows 10 下部署PaddleGAN,带你感受从年少到白头的浪漫
  5. Linux 多线程(二)线程安全:线程安全、互斥与互斥锁、死锁、同步与条件变量
  6. 在MFC中添加用户自定义消息
  7. BM:我离开EOS的报道被夸大了 期待与社区合作
  8. 深度学习:batch_size和学习率 及如何调整
  9. CodeforcesRound#498 (Div.3)E题Military Problem
  10. 旅行商问题(TSP)建模方法
  11. vsomeip源码梳理 -- OfferService流程
  12. ipad浏览器安装java_在桌面浏览器中调试iphone、ipad等设备上的网页
  13. Grid Control 和 Database Control
  14. pos系统开发步骤1-创建数据库以及拼音缩写的实现
  15. 无U盘 安装纯净win7系统
  16. scrum立会报告+燃尽图(第二周第六次)
  17. java 服务器 http请求_Java网络编程——发送HTTP请求到服务器
  18. SaltStack常用的模块-1
  19. MTK 按键驱动流程总结
  20. 四年上册级计算机教学计划,2021年四年级信息技术教学计划集锦5篇

热门文章

  1. C/C++使用socket实现server和client
  2. Maven配置JDK编译版本
  3. Rational rose的安装
  4. 【IDEA】怎么把idea的目录结构,以文本形式输出?
  5. 推荐 19 个 github 超牛逼项目!
  6. 笔记本8G+256G固态免费送,吃鸡不吃力,包邮!
  7. 这 12 款 IDEA 插件你用过几款?
  8. Android中ContentProvider组件数据共享
  9. 浅谈sass与less区别优缺点
  10. 外键为主键可以重复原因