前言

当你兴冲冲地开始运行自己的Java项目时,你是否遇到过如下问题:

  • 程序在稳定运行了,可是实现的功能点了没反应。
  • 为了修复Bug而上线的新版本,上线后发现Bug依然在,却想不通哪里有问题?
  • 想到可能出现问题的地方,却发现那里没打日志,没法在运行中看到问题,只能加了日志输出重新打包——部署——上线
  • 程序功能正常了,可是为啥响应时间这么慢,在哪里出现了问题?
  • 程序不但稳定运行,而且功能完美,但跑了几天或者几周过后,发现响应速度变慢了,是不是内存泄漏了?

以前,你碰到这些问题,解决的办法大多是,修改代码,重新上线。但是在大公司里,上线的流程是非常繁琐的,如果为了多加一行日志而重新发布版本,无疑是非常折腾人的。

现在,我们有了更为优雅的线上调试方法,来自阿里巴巴开源的Arthas

下图是Arthas文档中对于为什么要使用它的描述,我进行了精简:

好了,前言已经超过字数了,哈哈,在本篇文章里,你能够了解:

  • Arthas使用实例:帮助你快速让你上手,拯救你的低效率Debug
  • 使用Arthas解决具体问题:看一下Arthas帮我拯救了多少时间
  • 相似工具:看看线上Debug还有没有别的工具可以使用
  • 原理浅谈:莫在浮沙筑高阁!你需要大概了解下Arthas的原理

相信我,Arhas觉得是你提升效率的利器,适合各种阶段的开发者,尤其适合我这种刚入门的新人(天天上班写Bug的人)。你不应该有这种东西是高阶程序员才应该去使用的思想,放心大胆的去用吧

(打广告时间,更多精彩文章,请关注公众号:后端技术漫谈)

线上Debug神器Arthas

Arthas使用实例

命令的详细文档请参考:

https://alibaba.github.io/arthas/commands.html

快速启动

快速启动它,你只需要两行命令:

wget https://alibaba.github.io/arthas/arthas-boot.jar
java -jar arthas-boot.jar

随后,在界面出现的进程中,选择你的程序序号,比如1

这样你就进入了arthas的控制台

基本使用

Arthas有如下功能:

1. 首先是我认为的“上帝视角”指令:Dashboard

当前系统的实时数据面板,按 ctrl+c 退出。

当运行在Ali-tomcat时,会显示当前tomcat的实时信息,如HTTP请求的qps, rt, 错误数, 线程池信息等等。

通过这些,你可以对于整个程序进程有个直观的数据监控。

2. 类加载问题相关指令

SC:查看JVM已加载的类信息

通过SC我们可以看到我们这个类的详细信息,包括是从哪个jar包读取的,他是不是接口/枚举类等,甚至包括他是从哪个类加载器加载的。

上图中代码:

[arthas@37]$ sc -d *MathGame
class-info demo.MathGame
code-source /home/scrapbook/tutorial/arthas-demo.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@70dea4e
+-sun.misc.Launcher$ExtClassLoader@69260973
classLoaderHash 70dea4e

SC也可以查看已加载的类,帮助你看是否有没有纳入进来的类,尤其是在Spring中,可以判断的你的依赖有没有正确的进来。

上图中代码:

# 查看JVM已加载的类信息
[arthas@37]$ sc javax.servlet.Filter
com.example.demo.arthas.AdminFilterConfig$AdminFilter
javax.servlet.Filter
org.apache.tomcat.websocket.server.WsFilter
org.springframework.boot.web.filter.OrderedCharacterEncodingFilter
org.springframework.boot.web.filter.OrderedHiddenHttpMethodFilter
org.springframework.boot.web.filter.OrderedHttpPutFormContentFilter
org.springframework.boot.web.filter.OrderedRequestContextFilter
org.springframework.web.filter.CharacterEncodingFilter
org.springframework.web.filter.GenericFilterBean
org.springframework.web.filter.HiddenHttpMethodFilter
org.springframework.web.filter.HttpPutFormContentFilter
org.springframework.web.filter.OncePerRequestFilter
org.springframework.web.filter.RequestContextFilter
org.springframework.web.servlet.resource.ResourceUrlEncodingFilter
Affect(row-cnt:14) cost in 11 ms. # 查看已加载类的方法信息
[arthas@37]$ sm java.math.RoundingMode
java.math.RoundingMode <init>(Ljava/lang/String;II)V
java.math.RoundingMode values()[Ljava/math/RoundingMode;
java.math.RoundingMode valueOf(I)Ljava/math/RoundingMode;
java.math.RoundingMode valueOf(Ljava/lang/String;)Ljava/math/RoundingMode;
Affect(row-cnt:4) cost in 6 ms.

jad:反编译某个类,或者反编译某个类的某个方法

上图中代码:

# 反编译只显示源码
jad --source-only com.Arthas
# 反编译某个类的某个方法
jad --source-only com.Arthas mysql [arthas@37]$ jad demo.MathGame ClassLoader:
+-sun.misc.Launcher$AppClassLoader@70dea4e
+-sun.misc.Launcher$ExtClassLoader@69260973 Location:
/home/scrapbook/tutorial/arthas-demo.jar /*
* Decompiled with CFR.
*/
package demo; import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit; public class MathGame {
private static Random random = new Random();
public int illegalArgumentCount = 0; public List<Integer> primeFactors(int number) {
if (number < 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
} public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
} public void run() throws InterruptedException {
try {
int number = random.nextInt() / 10000;
List<Integer> primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
catch (Exception e) {
System.out.println(String.format("illegalArgumentCount:%3d, ", this.illegalArgumentCount) + e.getMessage());
}
} public static void print(int number, List<Integer> primeFactors) {
StringBuffer sb = new StringBuffer(number + "=");
for (int factor : primeFactors) {
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
} Affect(row-cnt:1) cost in 760 ms.

3. 方法运行相关指令

watch:方法执行的数据观测

你可以通过watch指令,来监控某个类,监控后,运行下你的功能,复现下场景,arthas会提供给你具体的出参和入参,帮助你排查故障

trace:输出方法调用路径,并输出耗时

这个指令对于优化代码非常的有用,可以看出具体每个方法执行的时间,如果是for循环等重复语句,还能看出n次循环中的最大耗时,最小耗时,和平均耗时,完美!

tt:官方名为时空隧道

这是我调试用的最多的指令,在你对某方法开启tt后,会记录下每一次的调用(你需要设置最大监控次数),然后你可以在任何时候会看这里面的调用,包括出参,入参,运行耗时,是否异常等。非常强大。

4. 线程调试相关指令

thread相关命令:

thread -n:排列出 CPU 使用率 Top N 的线程。

thread -b:排查阻塞的线程

我们代码有时候设计的不好,会引发死锁的问题,卡住整个线程执行,使用这个指令可以轻松的找到问题线程,以及问题的执行语句。

  1. 强大的ognl表达式

众所周知,一般来说,表达式都是调试工具里最强的指令,哈哈。

在Arthas中你可以利用ognl表达式语言做很多事,比如执行某个方法,获取某个信息,甚至进行修改。

[arthas@19856]$ ognl '@com.Arthas@hashSet'
@HashSet[
@String[count1],
@String[count2],
@String[count29],
@String[count28],
@String[count0],
@String[count27],
@String[count5],
@String[count26],
@String[count6],
@String[count25],
@String[count3],
@String[count24], [arthas@19856]$ ognl '@com.Arthas@hashSet.add("test")'
@Boolean[true]
[arthas@19856]$
# 查看添加的字符
[arthas@19856]$ ognl '@com.Arthas@hashSet' | grep test
@String[test],
[arthas@19856]$

甚至你可以动态更换日志输出级别

$ ognl '@com.lz.test@LOGGER.logger.privateConfig'
@PrivateConfig[
loggerConfig=@LoggerConfig[root],
loggerConfigLevel=@Level[INFO],
intLevel=@Integer[400],
]
$ ognl '@com.lz.test@LOGGER.logger.setLevel(@org.apache.logging.log4j.Level@ERROR)'
null
$ ognl '@com.lz.test@LOGGER.logger.privateConfig'
@PrivateConfig[
loggerConfig=@LoggerConfig[root],
loggerConfigLevel=@Level[ERROR],
intLevel=@Integer[200], ]

使用Arthas解决具体问题

1. 响应时间异常问题

工作中遇到一个优化问题,系统中一个导出表格的功能,响应时间长达2分钟,虽然给内部使用,但也不能这么夸张,用trace跟踪下方法,发现是其中的手机号加解密函数占用了非常大的时间,几千个手机号,进行了解密后加密的精彩操作,最终导致了两分钟的返回时间。

2. 某功能Bug导致服务器返回500

首先通过trace看异常报错的方法,之后通过tt排查方法,发现入参进来后,居然走错了方法(因为多态),走到了返回null的方法中,所以导致了NPE空指针错误。

3. tt添加表达式条件过滤请求

在一次使用tt -t时,由于线上请求太多,需要抓取一次导致错误的请求,必须开启表达式过滤,这时候可以直接在tt后面添加表达式:

tt -t com.xxxxx.domain.xx.xxx.impl.LoupanAdvShowService compositeClickUrl -n 2 'params[1]==377142'

上面的句子可以将第二个参数值位377142的请求,过滤出来,注意:请使用==号,而非赋值的=号,我傻乎乎的蒙了好久才发现少写了等于号。

4. watch添加表达式条件过滤请求

同理,watch也可以实现同样的效果,watch com.xinfang.apps.chat.controller.ChatMsgController uploadImg "{params[1].request.getParameterMap()}" -x 3 -b -n 1。

5. 补充

Arthas还支持Web Console,详见:

https://alibaba.github.io/arthas/web-console.html

相似工具

BTrace一是个历史比较久的工具,观察下来Arthas其实和他的理念蛮相似的,相信Arthas也参考过Btrace,作为一个学习样例来开发Arthas。详细的优劣势看图:

其他的相似工具,还有jvm-sandbox,有兴趣的朋友可以去看看。

Arthas原理浅谈

分为三个部分:

  • 启动
  • arthas服务端代码分析
  • arthas客户端代码分析

启动

使用了阿里开源的组件cli,对参数进行了解析

com.taobao.arthas.boot.Bootstrap

在传入参数中没有pid,则会调用本地jps命令,列出java进程

进入主逻辑,会在用户目录下建立.arthas目录,同时下载arthas-core和arthas-agent等lib文件,最后启动客户端和服务端

通过反射的方式来启动字符客户端

服务端——前置准备

看服务端启动命令可以知道 从 arthas-core.jar开始启动,arthas-core的pom.xml文件里面指定了mainClass为com.taobao.arthas.core.Arthas,使得程序启动的时候从该类的main方法开始运行。

  • 首先解析入参,生成com.taobao.arthas.core.config.Configure类,包含了相关配置信息
  • 使用jdk-tools里面的VirtualMachine.loadAgent,其中第一个参数为agent路径, 第二个参数向jar包中的agentmain()方法传递参数(此处为agent-core.jar包路径和config序列化之后的字符串),加载arthas-agent.jar包
  • 运行arthas-agent.jar包,指定了Agent-Class为com.taobao.arthas.agent.AgentBootstrap

上图中代码:

public class Arthas {

    private Arthas(String[] args) throws Exception {
attachAgent(parse(args));
} private Configure parse(String[] args) {
// 省略非关键代码,解析启动参数作为配置,并填充到configure对象里面
return configure;
} private void attachAgent(Configure configure) throws Exception {
// 省略非关键代码,attach到目标进程
virtualMachine = VirtualMachine.attach("" + configure.getJavaPid());
virtualMachine.loadAgent(configure.getArthasAgent(),
configure.getArthasCore() + ";" + configure.toString());
} public static void main(String[] args) {
new Arthas(args);
}
}

服务端——监听客户端请求

  • 如果是exit,logout,quit,jobs,fg,bg,kill等直接执行。
  • 如果是其他的命令,则创建Job,并运行。
  • 创建Job时,会根据具体客户端传递的命令,找到对应的Command,并包装成Process, Process再被包装成Job。
  • 运行Job时,反向先调用Process,再找到对应的Command,最终调用Command的process处理请求。

服务端——Command处理流程

  1. 不需要使用字节码增强的命令

其中JVM相关的使用 java.lang.management 提供的管理接口,来查看具体的运行时数据。比较简单,就不介绍了。

  1. 需要使用字节码增强的命令

字节码增加的命令统一继承EnhancerCommand类,process方法里面调用enhance方法进行增强。调用Enhancer类enhance方法,该方法内部调用inst.addTransformer方法添加自定义的ClassFileTransformer,这边是Enhancer类。

Enhancer类使用AdviceWeaver(继承ClassVisitor),用来修改类的字节码。重写了visitMethod方法,在该方法里面修改类指定的方法。visitMethod方法里面使用了AdviceAdapter(继承了MethodVisitor类),在onMethodEnter方法, onMethodExit方法中,把Spy类对应的方法(ON_BEFORE_METHOD, ON_RETURN_METHOD, ON_THROWS_METHOD等)编织到目标类的方法对应的位置。

在前面Spy初始化的时候可以看到,这几个方法其实指向的是AdviceWeaver类的methodOnBegin, methodOnReturnEnd等。在这些方法里面都会根据adviceId查找对应的AdviceListener,并调用AdviceListener的对应的方法,比如before,afterReturning, afterThrowing。

客户端

客户端代码在arthas-client模块里面,入口类是com.taobao.arthas.client.TelnetConsole。

主要使用apache commons-net jar进行telnet连接,关键的代码有下面几步:

  1. 构造TelnetClient对象,并初始化
  2. 构造ConsoleReader对象,并初始化
  3. 调用IOUtil.readWrite(telnet.getInputStream(), telnet.getOutputStream(), System.in, consoleReader.getOutput())处理各个流,一共有四个流:
    • telnet.getInputStream()
    • telnet.getOutputStream()
    • System.in
    • consoleReader.getOutput()

请求时:从本地System.in读取,发送到 telnet.getOutputStream(),即发送给远程服务端。

响应时:从telnet.getInputStream()读取远程服务端发送过来的响应,并传递给 consoleReader.getOutput(),即在本地控制台输出。

关于源码,深入下去还有很多东西需要生啃,我也没有消化得很好,大家可以继续阅读详细资料。

总结

Arthas是一个线上Debug神器,小白也可以轻松上手。

码字不易,希望大家捧个人场,谢谢诸位。

参考文献

开源地址:

https://github.com/alibaba/arthas

官方文档:

https://alibaba.github.io/arthas

其他参考:

  1. Hollis:Arthas - Java 线上问题定位处理的终极利器
  2. https://www.cnblogs.com/LittleHann/p/4783581.html
  3. https://juejin.im/post/6844903765145813006#heading-29
  4. http://tech.dianwoda.com/2018/12/20/arthasyuan-ma-fen-xi/
  5. https://www.jianshu.com/p/0771646f3f25
  6. https://github.com/alibaba/arthas/blob/master/README_CN.md
  7. https://blog.csdn.net/qq_27376871/article/details/51613066
  8. https://github.com/alibaba/arthas/issues/222

关注我

我是一名后端开发工程师。主要关注后端开发,数据安全,网络爬虫,物联网,边缘计算等方向,欢迎交流。

各大平台都可以找到我

原创博客主要内容

  • Java知识点复习全手册
  • Leetcode算法题解析
  • 剑指offer算法题解析
  • SpringBoot菜鸟入门实战系列
  • SpringCloud菜鸟入门实战系列
  • 爬虫相关技术文章
  • 后端开发相关技术文章
  • 逸闻趣事/好书分享/个人兴趣

个人公众号:后端技术漫谈

如果文章对你有帮助,不妨收藏起来并转发给您的朋友们~

Java线上问题排查神器Arthas快速上手与原理浅谈的更多相关文章

  1. Java线上问题排查神器Arthas实战分析

    概述 背景 是不是在实际开发工作当中经常碰到自己写的代码在开发.测试环境行云流水稳得一笔,可一到线上就经常不是缺这个就是少那个反正就是一顿报错抽风似的,线上调试代码又很麻烦,让人头疼得抓狂:而且deb ...

  2. 线上问题排查神器 Arthas

    线上问题排查神器 Arthas 之前介绍过 BTrace,线上问题排查神器 BTrace 的使用,也说它是线上问题排查神器.都是神器,但今天这个也很厉害,是不是更厉害不好说,但是使用起来非常简单.如果 ...

  3. BTrace : Java 线上问题排查神器

    BTrace 是什么 BTrace 是检查和解决线上的问题的杀器,BTrace 可以通过编写脚本的方式,获取程序执行过程中的一切信息,并且,注意了,不用重启服务,是的,不用重启服务.写好脚本,直接用命 ...

  4. Java线上问题排查思路及Linux常用问题分析命令学习

    前言 之前线上有过一两次OOM的问题,但是每次定位问题都有点手足无措的感觉,刚好利用星期天,以测试环境为模版来学习一下Linux常用的几个排查问题的命令. 也可以帮助自己在以后的工作中快速的排查线上问 ...

  5. JAVA 线上故障排查套路,从 CPU、磁盘、内存、网络到GC 一条龙!

    线上故障主要会包括cpu.磁盘.内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍. 同时例如jstack.jmap等工具也是不囿于一个方面的问题的, ...

  6. JAVA线上故障排查手册-(推荐)

    参考:https://fredal.xin/java-error-check?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=tout ...

  7. JAVA 线上问题排查方法

    CPU 磁盘 内存 GC问题 网络 线上故障主要会包括cpu.磁盘.内存以及网络问题,而大多数故障可能会包含不止一个层面的问题,所以进行排查时候尽量四个方面依次排查一遍. 同时例如jstack.jma ...

  8. JAVA线上常见问题排查手段(小结)

    在平时开发过程中,对于线上问题的排查以及系统的优化,免不了和Linux进行打交道.每逢大促和双十一,对系统的各种压测性能测试,优化都是非常大的一次考验.抽空整理了一下自己在线上问题排查以及系统优化的一 ...

  9. 如何把Java代码玩出花?JVM Sandbox入门教程与原理浅谈

    在日常业务代码开发中,我们经常接触到AOP,比如熟知的Spring AOP.我们用它来做业务切面,比如登录校验,日志记录,性能监控,全局过滤器等.但Spring AOP有一个局限性,并不是所有的类都托 ...

随机推荐

  1. IDEA去掉Autowired的黄色背景和对象的红线

    Intellij idea @AutoWired注入bean 出现红色波浪线,@autowird下面显示黄色波浪线或者标黄,如下图,解决方法总结一下供大家使用  首先选择File--Settings- ...

  2. Python Tuple(元组) cmp()方法

    描述 Python 元组 cmp() 函数用于比较两个元组元素.高佣联盟 www.cgewang.com 语法 cmp()方法语法: cmp(tuple1, tuple2) 参数 tuple1 -- ...

  3. PHP tmpfile() 函数

    定义和用法 tmpfile() 函数以读写(w+)模式创建一个具有唯一文件名的临时文件. 语法 tmpfile() 提示和注释 注释:临时文件会在文件关闭后(用 fclose())或当脚本结束后自动被 ...

  4. luogu P4929 【模板】舞蹈链 DLX

    LINK:舞蹈链 具体复杂度我也不知道 但是 搜索速度极快. 原因大概是因为 每次检索的时间少 有一定的剪枝. 花了2h大概了解了这个东西 吐槽一下题解根本看不懂 只能理解大概的想法 核心的链表不太懂 ...

  5. 5.22 noip模拟赛

    本来我是不想写的,无奈不会写.蒟蒻 考场就是想不出来 今天得到了100分额外水过了100分我是真的失败.还有一个根本不会check 感觉自己非常之菜. 这道题是这样的 还行吧比较有意思 首先确立一个真 ...

  6. 4.23 子集 分数规划 二分 贪心 set 单峰函数 三分

    思维题. 显然考虑爆搜.然后考虑n^2能做不能. 容易想到枚举中间的数字mid 然后往mid两边加数字 使其整个集合权值最大. 这里有一个比较显然的贪心就不再赘述了. 可以发现这样做对于集合是奇数的时 ...

  7. bzoj 4448 [Scoi2015]情报传递 主席树

    比较套路的题目. 可以发现难点在于某个点的权值动态修改 且我们要维护树上一条路径上的点权>x的个数. 每个点都在动态修改 这意味着我们的只能暴力的去查每个点. 考虑将所有可以动态修改的点变成静态 ...

  8. Jenkins总结1-部署jenkins

    1. 介绍 jenkins是一个广泛用于持续构建的可视化web工具,持续构建说得更直白点,就是各种项目的"自动化"编译.打包.分发部署.jenkins可以很好的支持各种语言(比如: ...

  9. jetbrain的plugin repository地址

    jetbrain的plugin repository地址:https://plugins.jetbrains.com/plugins/alpha/5047 有的时候 plugins内搜不到东西 把这个 ...

  10. opencv图片缩放与镜像

    一个练习的代码,先对图片进行缩放,然后再做镜像: import cv2 import numpy as np img = cv2.imread("src.jpg", 1) cv2. ...