Hello,大家好我是你们可爱的小花。
前言
你是不是为了生产环境问题,无法定位、无法中断、无法解决
项目无故异常,日志无报错、报错不够明确
测试环境无法复现、生产环境问题偶发
但重启项目后问题消失,无法给领导一个答复而苦恼~
why?
别急别急
小花花在线帮你疑问解答
because:
生产环境情况复杂,程序多一,占用线程无法直观看到、内存无法直观定位、缓存无法直接查看、gc无法确定正常,出参入参无法断点调试,业务问题在不启动远程debug的场景下无法轻松找到
重启项目后,一切回到起点,占用内存、缓存、cpu、线程通通清空,重新计算
这就是为什么生产出现问题,一建重启便可解决绝大多数问题
重启可解决一时问题,没有从根本解决
所谓之:跑得了和尚、跑不了庙
当当当当当,废话太多,废话太多,进入主题
我我我我我我我我我我我发现了一款绝佳的线上诊断产品------arthas
(当然当然当然,大牛就别来笑话我了,初生小牛犊,小见解、小眼光(狗头保命))
Now!!!!!!
---------------------分割线---------------------------分割线-------------------------分割线--------------------------
我要一本正经了,大家耳朵竖起来了啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
Arthas能解决什么问题?
Arthas 是一款线上监控诊断产品,通过全局视角实时查看应用 load、内存、gc、线程的状态信息,并能在不修改应用代码的情况下,对业务问题进行诊断,包括查看方法调用的出入参、异常,监测方法执行耗时,类加载信息等,大大提升线上问题排查效率。
Arthas 支持 JDK 6+,支持 Linux/Mac/Windows,采用命令行交互模式,同时提供丰富的 Tab 自动补全功能,进一步方便进行问题的定位和诊断。
怎样使用呢?
1、下载arthas
找一个你喜欢的文件夹输入以下命令下载arthas
curl -O https://arthas.aliyun.com/arthas-boot.jar
下载成功后会出现一个arthas-boot.jar的包
如果下载太慢,可更换为aliyun的镜像:java -jar arthas-boot.jar --repo-mirror aliyun --use-http
注意注意:执行该程序的用户需要和目标进程具有相同的权限。比如以myy用户来执行:sudo su myy && java -jar arthas-boot.jar 或 sudo -u myy -EH java -jar arthas-boot.jar
这里我使用网上的用例给大家来精确举例
下载一个小项目math-game
curl -O https://arthas.aliyun.com/math-game.jar
启用
java -jar math-game.jar
2、启动arthas进程
找一个舒服的姿势来执行以下命令,跟正常启动jar包命令一致
java -jar arthas-boot.jar
3、选择java程序进行监控
arthas启动成功后,会将所有正在运行的java进程罗列出来,只需要输入你想要监控线程前面的数字
我选择刚刚安装的数字游戏进程4
回车之后,出现arthas的图标之后,说明你的女神与你对接成功,女神为你爆灯
4、来吧来吧,接下来就看用那些骚操作,可更快读懂女神的心灵,为女神排忧解难
注注注,以下所有操作,都需要回车/enter确认之后查看结果,要不然你小声哔哔,女神怎能听的见
(1)dashboard
this is 查看控制面板的操作,改命令可查询到当前进程的信息,了解足够时可按ctrl+c中断该了解
可看到第一个框中显示正在运行的线程id、名称、组。。。。的信息
第二个框显示内存使用情况
第三个框显示运行环境
每5秒刷新一次面板
比如内存泄露:如果内存使用率在不断上升,而且gc后也不下降,后面还发现gc越来越频繁,很可能就是内存泄漏了。
这个时候我们可以直接用heapdump命令:heapdump --live /root/jvm.hprof 把内存快照dump出来,作用和jmap工具一样(jmap -dump:live):
下载下来,再使用内存dump分析工具,比如:MAT( Eclipse Memory Analyzer),分析可能泄露的内存原因
(2)thread 线程id
当发现某个线程cpu过高时,可通过thread 线程id命令开输出该线程的栈信息
通过 thread 线程id命令来获取到math-game进程的名称
thread -n 3 查看CPU使用率top n线程的栈
比如我们输入 thread 1 回车
搜索thread 1 | grep main
可看到类名
thread -b 可查看是否有线程阻塞:找出当前阻塞其他线程的线程。有时候我们发现应用卡住了, 通常是由于某个线程拿住了某个锁, 并且其他线程都在等待这把锁造成的
(3)jad 类名
通过jad 类名来反编译代码
回车后代码就反编译成功,
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/math-game.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
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();
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 + "=");
Iterator<Integer> iterator = primeFactors.iterator();
while (iterator.hasNext()) {
int factor = iterator.next();
sb.append(factor).append('*');
}
if (sb.charAt(sb.length() - 1) == '*') {
sb.deleteCharAt(sb.length() - 1);
}
System.out.println(sb);
}
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;
}
}
Affect(row-cnt:1) cost in 970 ms.
有兴趣的小朋友可以编译好代码看看是否正确
可单独查看方法
jad demo.MathGame print
(4)watch
watch+类名+方法名+returnObj
可看到函数的返回值
watch demo.MathGame primeFactors returnObj
返回数据
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]
watch 类名 方法名 "{params,returnObj}" -x 3 -b -s
可查看入参和返回值
-x 3是指定输出结果的属性遍历深度,默认为 1
-b方法调用前观察,用于返回方法入参
-s方法调用后观察,用于返回方法返回值
(5)monitor监控
monitor -c 10 demo.MathGame primeFactors
每10秒监控方法的执行情况
包括:成功次数、失败次数、平均响应时间、失败率
- 监控primeFactors这个方法的执行情况
- -c 10 指定统计周期为10秒统计一次,默认是120秒统计一次
(6)tt命令: TimeTunnel 记录下方法执行数据的时空隧道
tt -t demo.MathGame primeFactors
INDEX: 时间片段记录编号,每一个编号代表着一次调用,后续tt还有很多命令都是基于此编号指定记录操作,非常重要。
TIMESTAMP: 方法执行的本机时间,记录了这个时间片段所发生的本机时间
COST(ms): 方法执行的耗时
IS-RET: 方法是否以正常返回的形式结束
IS-EXP: 方法是否以抛异常的形式结束
OBJECT: 执行对象的hashCode(),注意,曾经有人误认为是对象在JVM中的内存地址,但很遗憾他不是。但他能帮助你简单的标记当前执行方法的类实体
CLASS: 执行的类名
METHOD: 执行的方法名
如果看到IS-RET中有false,可记住index
用以下快速重新重新调用一次
tt -i 1002 -p
表示重做Index为1002的那次调用
(7)stack命令:监控方法的被执行的路径
很多时候我们都知道一个方法被执行,但不知道被谁在调用
stack 命令, 主要用于监控方法被谁调用了:
stack 类名 方法名
(8)sm命令:能搜索出所有已经加载了 Class 信息的方法信息
sm -d 类名
(9)logger
动态更新日志级别
使用sc命令查看需要改变的类信息,classLoaderHash为类加载的hash值
使用hash值和类名
查看类的日志级别
logger -c hash值 --name 类名
更改日志级别:
logger -c hash值 --name 类名 --level 级别(debug\info\error)
好了好了,到这里就结束了,如果还有啥不理解的可以评论或简单粗暴看官网啊~
总结:
arthas是一个可以不停服进行java项目诊断的产品,操作简单,都是命令,忘记命令的可直接上网搜索
我是小花,一个专注于Java后端研发的小姑娘,觉得文章有帮助到你的,关注、再看、点赞,三连哦~
最后,欢迎大家关注Java程序人生公众号,每日分享互联网行业故事、经验技术