Java生产环境下性能监控与调优详解(四)基于Btrace的监控调试

深入理解Btrace

1.主要内容

  • Btrace安装入门
  • Btrace使用详解

2.Btrace入门

BTrace是一个安全的,动态的Java跟踪工具。BTrace通过运行Java程序的动态(字节码)工具类来工作。BTrace将追踪操作插入到正在运行的Java程序的类中,并将跟踪的程序类热插拔。

2.1 Btrace 简介

  • BTrace可以动态地向目标应用程序的字节码注入追踪代码。
  • JavaComplierApi、JVMTI、Agent、Instrumentation+ASM

2.2 Btrace安装

2.3 两种运行脚本方式

  • 在JVisualVM中添加Btrace插件,添加classpath
  • 使用命令行btrace <pid> <trace_script>

2.4 Btrace 案例(一)命令行+代码形式

(1)测试接口

        @RequestMapping("/arg1")
	public String arg1(@RequestParam("name")String name) {
		return "hello,"+name;
	}

(2)BTrace追踪脚本(Java代码格式)

package com.imooc.monitor_tuning.chapter4;//package com.imooc.monitor_tuning.chapter4;
import com.sun.btrace.AnyType;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.ProbeClassName;
import com.sun.btrace.annotations.ProbeMethodName;

@BTrace
public class PrintArgSimple {

	@OnMethod(
	        clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
	        method="arg1",
	        location=@Location(Kind.ENTRY)
	)
	/**
	 * @Param pcn  拦截的方法的类类名
	 * @Param pmn  要拦截的方法名
	 * @Param args	要拦截的参数
	 */
	public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
		BTraceUtils.printArray(args);
		BTraceUtils.println(pcn+","+pmn);
		BTraceUtils.println();
    }
}

当前代码为BTrace的脚本,用于在指定时间对指定的类进行拦截。

①拦截参数:

  • @BTrace: 标识当前代码为BTrace脚本
  • clazz:  要拦截的方法所在的类
  • method:   要拦截的方法
  • location:  什么时候进行拦截 。 Kind.ENTRY,代表在进入方法的时候就拦截。

②anyRead(……)方法

该方法用于打印出BTrace拦截到的数据,如:类、方法、参数等。

(3)BTraceUtils

为了保证跟踪动作是“只读的”(即跟踪动作不会改变被跟踪的程序的状态)和有界的(即跟踪动作在有限的时间内终止),一个BTrace程序被允许只做一套有限的行动。特别是一个BTrace类:

  • 不能new新的对象。
  • 不能new新的数组。
  • 不能抛异常。
  • 不能捕获异常。
  • 不能随意调用方法——只能调用com.sun.btrace.BTraceUtils的静态方法。
  • 不能为追踪的目标对象赋值成员变量,不能为追踪的目标类赋值静态变量(不能改变追踪的应用程序的状态)。但是,你可以给自己这个类设置静态变量。
  • 不能有实例方法和字段,只允许有static public void的方法。所有的字段必须是静态的。
  • 不能有外部类,内部类,嵌套的或局部类。
  • 不能有同步块或同步方法。
  • 不能有控制语句(for, while, do..while)。
  • 不能继承其他类(父类只能是java.lang.Object)。
  • 不能实现接口。
  • 不能有断言。
  • 不能字面值。

在当前的案例中,通过成名静态方法 anyRead() 使用BTraceUtils来打印出追踪的信息。

(4)开启拦截追踪

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>jps -l
11824
15040
4336 org.jetbrains.idea.maven.server.RemoteMavenServer
11748 org.jetbrains.jps.cmdline.Launcher
17700
16440 sun.tools.jps.Jps
19544 com.imooc.monitor_tuning.MonitorTuningApplication

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>btrace 19544 PrintArgSimple.java

在被拦截方法所在类的目录下打开 “cmd” 命令行,获取到程序 PID ,执行 btrace [pid] 脚本文件名.java 命令将追踪代码加入到程序中。

(5)访问被追踪的接口方法,触发BTrace拦截追踪机制,打印相关信息。

①访问接口:

访问后接口返回 hello 拼接 imooc2 的结果。正确。

②命令行界面打印出BTrace追踪的方法信息

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>btrace 19544 PrintArgSimple.java
[imooc2, ]
com.imooc.monitor_tuning.chapter4.Ch4Controller,arg1

以上信息中, imooc2是 BTrace 所拦截到的方法参数,并且还打印了所拦截的方法全路径名 :

  • 拦截到的方法参数:  [imooc2, ]
  • 拦截到的方法路径:com.imooc.monitor_tuning.chapter4.Ch4Controller,arg1

2.5 Btrace 案例(二)JVisualVM插件+脚本形式

(1)JVisualVM启动BTrace脚本

(2)在指定区域编写BTrace脚本

3.拦截构造函数、同名函数

3.1 BTrace的使用详情

  • 拦截方法
  • 拦截时机
  • 拦截this、参数、返回值
  • 其他

3.2 拦截方法

  • 普通方法 @OnMethod(clazz="",method="")
  • 构造函数 @OnMethod(clazz="",method="<init>")
  • 拦截同名函数,用参数区分

3.3 演示

拦截构造方法

(1)脚本

@BTrace
public class PrintConstructor {

	@OnMethod(
	        clazz="com.imooc.monitor_tuning.chapter2.User",
	        method="<init>"
	)
	public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
		BTraceUtils.println(pcn+","+pmn);
		BTraceUtils.printArray(args);
		BTraceUtils.println();
    }
}

上述脚本用于拦截User的构造器。

对应的访问接口为:http://localhost:8080/ch4/constructor?name=imooc&id=1

	@RequestMapping("/constructor")
	public User constructor(User user) {
		return user;
	}

User构造器为:

	public User(int id, String name) {
		super();
		this.id = id;
		this.name = name;
	}

更多的拦截脚本参考:源码

4.拦截返回值、异常、行号

4.1 拦截时机

拦截参数参数作用
Kind.ENTRY入口,默认值
Kind.RETURN返回
Kind.THROW异常
Kind.Line

4.2 案例——拦截返回值

(1)拦截脚本

@BTrace
public class PrintReturn {

	@OnMethod(
	        clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
	        method="arg1",
	        location=@Location(Kind.RETURN)
	)
	public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Return AnyType result) {
		BTraceUtils.println(pcn+","+pmn + "," + result);
		BTraceUtils.println();
    }
}

上述脚本中,针对拦截返回值主要改变的是:

  • OnMethod()中的@Location(Kind.RETURN) 配置,这里使用了 RETURN 表示拦截的方法返回值
  • anyRead() 的形参 AnyType 添加 @Return 表示该参数为返回值

(2)重新程序并访问接口触发脚本

访问url: http://localhost:8080/ch4/arg1?name=imooc&id=1

BTrace 脚本拦截的结果:

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>btrace 1380 PrintReturn.java
com.imooc.monitor_tuning.chapter4.Ch4Controller,arg1,hello,imooc

4.3 案例——拦截异常

(1)拦截脚本

package com.imooc.monitor_tuning.chapter4;
import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Self;
import com.sun.btrace.annotations.TLS;
/*
 * Copyright (c) 2008, 2015, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the Classpath exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

@BTrace
public class PrintOnThrow {
    // store current exception in a thread local
    // variable (@TLS annotation). Note that we can't
    // store it in a global variable!
    @TLS
    static Throwable currentException;

    // introduce probe into every constructor of java.lang.Throwable
    // class and store "this" in the thread local variable.
    @OnMethod(
        clazz="java.lang.Throwable",
        method="<init>"
    )
    public static void onthrow(@Self Throwable self) {//new Throwable()
        currentException = self;
    }

    // 拦截带有String参数的异常
    @OnMethod(
        clazz="java.lang.Throwable",
        method="<init>"
    )
    public static void onthrow1(@Self Throwable self, String s) {//new Throwable(String msg)
        currentException = self;
    }

    // 拦截带有String,cause的异常
    @OnMethod(
        clazz="java.lang.Throwable",
        method="<init>"
    )
    public static void onthrow1(@Self Throwable self, String s, Throwable cause) {//new Throwable(String msg, Throwable cause)
        currentException = self;
    }

    @OnMethod(
        clazz="java.lang.Throwable",
        method="<init>"
    )
    public static void onthrow2(@Self Throwable self, Throwable cause) {//new Throwable(Throwable cause)
        currentException = self;
    }

    // 拦截返回值,如果拦截到异常,会用jstack打印异常信息
    // when any constructor of java.lang.Throwable returns
    // print the currentException's stack trace.
    @OnMethod(
        clazz="java.lang.Throwable",
        method="<init>",
        location=@Location(Kind.RETURN)
    )
    public static void onthrowreturn() {
        if (currentException != null) {
        	BTraceUtils.Threads.jstack(currentException);
        	BTraceUtils.println("=====================");
            currentException = null;
        }
    }
}

脚本参数说明:

  • @BTrace —— 说明是BTrace脚本
  • clazz —— 要拦截的类
  • method —— 要拦截的方法。 如果为“<init>"说明是构造函数
  • (@Self Throwable)—— 只有一个参数说明拦截空参的异常,否则就是拦截带有对应参数的异常

(2)定义接口

        @RequestMapping("/exception")
	public String exception() {
		try {
			System.out.println("start...");
			System.out.println(1/0);
			System.out.println("end...");
		}catch(Exception e) {
			//
		}
		return "success";
	}

该接口中,没有将异常抛出,也没有在 catch() 中进行处理。相当于就是说该异常被吃掉了,即使真的发生异常,那也是无法将异常信息抛给后台的。

而正因为可能发生异常被吃掉的问题,所以 BTrace的意义就体现出来了。

(3)访问接口,触发BTrace拦截异常机制

①访问url:http://localhost:8080/ch4/exception

②控制台截图

从控制台可以看出,虽然程序发生了异常,但是由于没有将异常进行处理或抛出,导致我们不能及时的知道异常的发生。这将会对定位问题造成很大的困扰。

③ BTrace 拦截后 jstack 打印异常返回值结果

从截图可知,exception() 中的异常被BTrace拦截到并使用jstack 打印了出来,虽然程序本身没有做 throws 等抛出异常的处理,但是BTrace能避免异常被吃掉的问题。

4.4 案例 —— 拦截行

(1)脚本

@BTrace
public class PrintLine {

	@OnMethod(
	        clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
	        method="exception",
	        location=@Location(value=Kind.LINE, line=-1)
	)
	public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
		BTraceUtils.println(pcn+","+pmn + "," +line);
		BTraceUtils.println();
    }
}

参数:

  • @Location(value=Kind.LINE,line=-1)  —— 拦截行
  • anyRead() 中的 int line 为行号

(2)拦截的接口

(3)访问接口后触发的BTrace拦截行

访问url: http://localhost:8080/ch4/exception

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>btrace 3424 PrintLine.java
com.imooc.monitor_tuning.chapter4.Ch4Controller,exception,40

com.imooc.monitor_tuning.chapter4.Ch4Controller,exception,41

com.imooc.monitor_tuning.chapter4.Ch4Controller,exception,43

com.imooc.monitor_tuning.chapter4.Ch4Controller,exception,46

从上述的拦截结果中可以看出, 42 行并没有被打印出来。其原因经过分析:因为在41行时就抛出了异常,后面的代码自然不再执行了。

43、46行的打印: 抛出异常后必然进入到catch()语句块中,所以有43行;而 return 是返回值,必然会执行,也同样被打印。

5.拦截复杂参数、环境变量、正则匹配

5.1 拦截this、入参、返回

  • this: @Self
  • 入参:可以用AnyType,也可以用真实类型,同名的用真实的
  • 返回:@Return

5.2 获取对象的值

有两种类型:

  • 简单类型:直接获取
  • 复杂类型:反射,类名+属性名

5.3 案例 —— 拦截复杂参数

(1)脚本

@BTrace
public class PrintArgComplex {


	@OnMethod(
	        clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
	        method="arg2",
	        location=@Location(Kind.ENTRY)
	)
	public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user) {
		//print all fields
		BTraceUtils.printFields(user);
		//print one field
		Field filed2 = BTraceUtils.field("com.imooc.monitor_tuning.chapter2.User", "name");
		BTraceUtils.println(BTraceUtils.get(filed2, user));
		BTraceUtils.println(pcn+","+pmn);
		BTraceUtils.println();
    }
}

注意: 如果打印的类对象不属于JDK内置的类,那么就需要配置为全路径名的方式,如果是jdk内置类,可以直接用类名,比如 String等。

(2)接口

        @RequestMapping("/arg2")
	public User arg2(User user) {
		return user;
	}

(3)访问接口并触发BTrace拦截

访问url: http://localhost:8080/ch4/arg2?name=imooc&id=2

触发结果:

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>btrace -cp "J:\idea_space\monitor_tuning\target\classes" 17880 PrintArgComplex.java
{id=2, name=imooc, }
imooc
com.imooc.monitor_tuning.chapter4.Ch4Controller,arg2

注意:从上述的结果中,可以得知,类的属性确实被拦截到了。并且可以使用  BTraceUtils.printFields(user); 拦截所有的属性,或者用 get() 方法拦截指定的属性。

报错处理:

J:\idea_space\monitor_tuning\src\main\java\com\imooc\monitor_tuning\chapter4>btrace -cp "J:\idea_space\monitor_tuning\target\classess" 17880 PrintArgComplex.java
PrintArgComplex.java:4: 错误: 程序包com.imooc.monitor_tuning.chapter2不存在
import com.imooc.monitor_tuning.chapter2.User;
                                        ^
PrintArgComplex.java:22: 错误: 找不到符号
        public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, User user) {
                                                                                            ^
  符号:   类 User
  位置: 类 com.imooc.monitor_tuning.chapter4.PrintArgComplex
BTrace compilation failed

这里打印出找不到 User 的提示,可以通过指定 User .clazz 文件所在文件的方式来解决该问题。

btrace -cp "J:\idea_space\monitor_tuning\target\classes" 17880 PrintArgComplex.java

5.4 案例——拦截正则

脚本:

@BTrace
public class PrintRegex {

	@OnMethod(
	        clazz="com.imooc.monitor_tuning.chapter4.Ch4Controller",
	        method="/.*/"
	)
	public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn) {
		BTraceUtils.println(pcn+","+pmn);
		BTraceUtils.println();
    }
}

不做详细测试了,具体参考源码

5.5 此外,还支持以下功能

  • 打印行号:Kind.LINE
  • 打印堆栈:Threads.jstack()
  • 打印环境变量

6.注意事项

  • BTrace 脚本默认只能本地运行
  • 生产环境下可以使用,但是被修改的字节码不会被还原
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值