1.主要内容
- Btrace安装入门
- Btrace使用详解
2.Btrace入门
BTrace是一个安全的,动态的Java跟踪工具。BTrace通过运行Java程序的动态(字节码)工具类来工作。BTrace将追踪操作插入到正在运行的Java程序的类中,并将跟踪的程序类热插拔。
2.1 Btrace 简介
- BTrace可以动态地向目标应用程序的字节码注入追踪代码。
- JavaComplierApi、JVMTI、Agent、Instrumentation+ASM
2.2 Btrace安装
- 根据系统下载:https://github.com/btraceio/btrace/releases/tag/v1.3.11
- 然后解压安装包
- 新建环境变量BTRACE_HOME
- 添加Path: %BTRACE_HOME%\bin
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 脚本默认只能本地运行
- 生产环境下可以使用,但是被修改的字节码不会被还原