一、热更新代码
jad,mc,retransform\redefine命令结合场景
发现线上紧急问题,排查到某个线程或定位到具体类代码bug,使用jad反编译代码,对具体bug修复,使用mc重新编译,使用retransform\redefine热更新修复问题。
# 反编译
$ jad --source-only com.example.demo.TreadTest > C:\\Users\\it06230\\Desktop\\ThreadTest.java
# 修改文件
$ vim /tmp/UserController.java
# 查找加载的 ClassLoader
$ $ sc -d *UserController | grep classLoaderHash
classLoaderHash 6bc28484
# 编译 某个路径的文件到某个路径
$ mc -c 6bc28484 /tmp/UserController.java -d /tmp
# 热更新
$ redefine /tmp/com/example/demo/arthas/user/UserController.class
redefine success, size: 1
注意:jad反编译在99%的情况下都是准确的,但也有可能失败
二、利用多次trace排查耗时模块
利用定时任务,调用两个不定时出现延时的方法来做trace案例
timer()
->getStr(date)
->sleep1()
->sleep1自身可能出现耗时问题,调用的sleep2()出现耗时问题
@Scheduled(cron = "0/10 * * * * ?")
public void timer(){
System.out.println( "测试========>" + getStr(new Date()));
}
private String getStr(Date date){
sleep1();
return date.toString();
}
/**
* 模拟两个随机耗时方法
*/
private void sleep1(){
int max=1000, min=1;
int r = (int) (Math.random()*(max-min)+min);
if (r <= 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
sleep2();
}
private void sleep2(){
int max=1000, min=1;
int r = (int) (Math.random()*(max-min)+min);
if (r >= 950){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
使用trace排查思路,首先对定时任务主方法进行监控,获取到getStr方法出现耗时大于10ms的情况,并定位到sleep1方法上,通过多次定时任务的监控,发现sleep1中出现两种耗时情况,一种自身耗时,一种是调用sleep2时出现耗时大于10ms
三、动态修改日志级别
a.下面的方式在本地测试正常,部署服务器使用不正常(具体问题待研究)
1.查看当前日志级别logger 或 logger -n com.example.demo.ThreadTest
2.修改级别logger --name com.example.demo.ThreadTest --level info
b.使用web console测试也是正常的
1.logger -n com.example.demo.ThreadTest获取classLoaderhash和级别信息
2.使用logger -c 步骤1的hashcode --name 类全名 --lever 级别
有时候会提示使用sc -d查找classloader hashcode,尝试以下方式
1.使用sc -d 类全名 获取classloader hashcode信息
2.使用logger -c 步骤1获取的hashcode 查到当前日志信息
3.使用logger -c 步骤1的hashcode --name 类全名 --lever 级别
四、watch场景下方法参数及返回值监控
样例代码1:
@Scheduled(cron = "0/10 * * * * ?")
public void timer1() {
int max = 3, min = 0;
int r = (int) (Math.random() * (max - min) + min);
int result = getResult(r);
System.out.println(result);
}
private int getResult(int r){
return 10 / r;
}
观察入参
watch com.example.demo.ThreadTest getResult “{params, returnObj}” -x 2 -b
监控异常
watch com.example.demo.ThreadTest timer1 “{params, throwExp}” -e -x 2
观察出参及返回值
watch com.example.demo.ThreadTest getResult “{params, returnObj}” -x 2
样例代码2:
@Scheduled(cron = "0/10 * * * * ?")
public void timer3() {
int max = 3, min = 0;
int r = (int) (Math.random() * (max - min) + min);
Map<String, Integer> map = new HashMap<>();
map.put("a", r);
System.out.println(getNewMap(r, map).toString());
}
private Map<String, Integer> getNewMap(int r, Map<String, Integer> map){
for (Map.Entry<String, Integer> e : map.entrySet()){
e.setValue(e.getValue() + r);
}
return map;
}
调整-x的值,观察具体方法参数值
watch com.example.demo.ThreadTest getNewMap “{params, returnObj}” -x 3
观察当前对象的属性
watch com.example.demo.ThreadTest timer3 ‘targer’
watch com.example.demo.ThreadTest timer3 ‘targer.i’
五、利用tt命令对偶发异常进行监控
模拟偶发异常,当随机数为0时出现异常
@Scheduled(cron = "0/10 * * * * ?")
public void timer1(){
int max=3, min=0;
int r = (int) (Math.random()*(max-min)+min);
System.out.println( 10/r);
}
使用tt -t com.example.demo.ThreadTest timer1监控
当监控到异常执行时,我们可以通过tt -i index命令,重现该异常执行问题
六、当一个方法被多个方法调用,或调用链很长时使用stack
stack com.example.demo.ThreadTest sleep2
按耗时过滤
stack com.example.demo.ThreadTest sleep2 ‘#cost>5’
七、profiler火焰图性能分析(不支持windows)
基本操作
profiler start / profiler start --duration 300(采集300s) 启动
profiler status 状态
profiler stop / profiler stop --format html / profiler stop --file ./temp/s.svg 停止
如果应用比较复杂,生成的内容很多,想只关注部分数据,可以通过 include/exclude 来过滤。比如
profiler start --include 'java/*' --include 'demo/*' --exclude '*Unsafe.park*'