BTrace简介
首先***BTrace就是为了解决线上问题而存在的***
举一个例子,因为设计问题,我们生产环境有一个Map的大小超过了16M,这个Map是要一次写入MongoDB数据库中的,但是MongoDB数据库的一个文档大小最多不超过16M,超过就没有办法写入,这个问题怎么解决?
首先,不能重启,重启数据就丢了。堆dump,然后去解析dump数据?今天我们来介绍一些使用BTrace解决这个问题。
BTrace可以获取程序运行时的数据信息,如方法参数、返回值、全局变量、堆栈信息等
我们先来看一下使用BTrace怎么解决上面的那个问题,了解一下BTrace怎么使用。如果有地方暂时不清楚的也没有关系,可以看后面的一些说明。
BTrace 导出map数据
首先到BTrace下载去下载BTrace的release版。
解压到文件,大概像是下面这样:
测试业务类
现在假设我们的业务类是这样子的:
package cn.freemethod.business;
import java.util.HashMap;
import java.util.Scanner;
public class BusinessMap {
private static HashMap<Integer,Integer> map = new HashMap<>();
static {
map.put(1,1);
map.put(2,2);
map.put(3,3);
map.put(4,4);
}
public static void main(String[] args) throws InterruptedException {
final Scanner scanner = new Scanner(System.in);
scanner.nextLine();
// final BusinessMap bm = new BusinessMap();
// for (int i = 0; i < 1000; i++) {
// TimeUnit.SECONDS.sleep(5);
// bm.business();
// }
}
public void business(){
System.out.println("business");
}
}
BTrace脚本
我们想要获取map中的数据,怎么办呢?我们就可以写一个BTrace脚本(java和class文件都可以),像下面这样子:
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.OnTimer;
import com.sun.btrace.annotations.Self;
import java.lang.reflect.Field;
import static com.sun.btrace.BTraceUtils.Reflective;
import static com.sun.btrace.BTraceUtils.Reflective.classForName;
import static com.sun.btrace.BTraceUtils.Reflective.contextClassLoader;
import static com.sun.btrace.BTraceUtils.println;
@BTrace
public class BusinessMapTrace {
@OnMethod(
clazz="cn.freemethod.business.BusinessMap",
method="business"
)
public static void getMap(@Self Object bm) {
//如果map是实例变量
Field mapField = Reflective.field("cn.freemethod.business.BusinessMap", "map");
println(Reflective.get(mapField,bm));
}
//一分钟执行一次
@OnTimer(1000*60)
public static void timeGetMap(){
//如果map是静态变量
Class clazz = classForName("cn.freemethod.business.BusinessMap", contextClassLoader());
Field mapField = Reflective.field(clazz, "map");
println(Reflective.get(mapField));
}
}
因为map是静态变量,所有可以直接使用@OnTimer注解的这个方法。@OnTimer注解是固定多少时间执行一次,单位是毫秒@OnTimer(1000*60)就是一分钟执行一次。
注意这里我们是使用:
Class clazz = classForName("cn.freemethod.business.BusinessMap", contextClassLoader());
Field mapField = Reflective.field(clazz, "map");
这种方式获取到Field的,和上面的@OnMethodz中的直接使用类全限定名是不一样的。有些资料说是JDK自带的就可以使用像:
Field mapField = Reflective.field("cn.freemethod.business.BusinessMap", "map");
这种使用全限定名的方式,其他的类就使用classForName这种方式,感觉是有局限性的啊,上面一个我没有使用classForName这种方式也没有问题的。
就是尽量使用classForName这种方式吧,如果使用全限定名字符串这种方式出现下面的错误就改成classForName这种方式就可以了。
如果map是实例变量那怎么办呢,就使用上一个方法嘛。
@OnMethod(clazz="cn.freemethod.business.BusinessMap",
method="business")
我们知道Java的AOP中拦截的一般是方法,BTrace也一样。上面的OnMethod就表示当cn.freemethod.business.BusinessMap这个类执行business方法的时候执行。但是是什么时候执行呢,进入方法的时候?方法返回的时候?其实还有一个location属性来控制,默认是进入方法的时候执行。后面有介绍location内容的,这里不详细说了。
@Self注解是把调用business方法的instance注入,就是获取this。因为是实例变量所有我们要通过实例来获取属性。所有通过@Self把实例注入进来。
然后就可以通过BTrace提供的field方法获取Field,通过get方法获取Field实例了。通过println方法来打印map对象了。
脚本使用
脚本我们有了,但是怎么使用呢?下面我们就简单的说一下脚本怎么使用。首先我们把脚本拷贝到bin目录下(主要是我懒,不想加脚本路径),bin目录大概就是想下面这样了:
然后
-
启动我们的测试业务类BusinessMap
-
执行jps命令找到BusinessMap的pid [jps]
-
使用下面的命令执行脚本(2个中的任一个)
btrace 5244 BusinessMapTrace.java
btrace -o D:\ptool\btrace\bin\map.txt 5244 BusinessMapTrace.java
-o是参数是输出到指定文件,注意要使用绝对路径,使用相对路径并不会出现在脚本目录下。
如果你遇到下面的错误,不要着急,把脚本中的中文注释删除了就可以了。
如果出现什么btrace-libs找不到就在build下面新建一个btrace-libs把build下的jar包拷贝到btrace-libs目录下就可以了。
现在再看上面的问题是不是简单多了,这里我们就不多介绍例子了,你可以在BTrace实例这里看到更多的例子,或者在github下载的BTrace的release版本中的sample中有很多使用的例子,在UserGuide中有记录每一个例子测试的是什么。
下面主要介绍BTrace中常用的注解和方法。
@OnMethod注解
@OnMethod注解常用的属性:
- "clazz"属性:用来指定目标类名
- "method"属性:用来指定被trace的方法
- "location"属性:用来指定拦截时机
- "type"属性:用来指定方法签名(重载的时候,只有方法名不行)如:type="int (int, int)"
location后面单独讲,先说"clazz"和"method"
- 使用全限定名
clazz="cn.freemethod.btra.BtraceMap"
method="sayHello"
注意:静态内部类的写法,是在类与内部类之间加上"$"
- 使用正则表达式 方法和类都可以使用正则表达式
clazz="/java\\.lang\\..*/"
method="/.*/"
- 使用接口 接口在前面添加"+"表明是一个接口就可了
clazz="+xxx.xxx.Interface"
- 使用注解 类和方法都可以使用注解,在前面加"@"就可以了
clazz="@xxx.xxx.Annotation"
method="@xxx.xxx.Annotation"
- 构造方法 构造方法指定method="<init>"就可以了
注意正则表达式需要写在两个 "/",正则表达式的范围要尽可能的小,不然会非常慢
注意:@OnMethod都是注解的public static void方法
@OnTimer注解
定时触发Trace,时间可以指定,单位为毫秒
@OnError注解
当trace代码抛异常或者错误时,该注解的方法会被执行.如果同一个trace脚本中其他方法抛异常,该注解方法也会被执行
@OnExit
当trace方法调用内置exit(int)方法(用来结束整个trace程序),该注解的方法会被执行
@OnEvent
用来截获"外部"btrace client触发的事件
@OnLowMemory
当内存超过某个设定值将触发该注解的方法
@OnProbe
使用外部文件XML来定义trace方法以及具体的位置
@TLS
定义ThreadLocal的共享变量
@Export
该注解的静态属性主要用来与jvmstat计数器做关联 例如有:
@Export private static long count;
其他脚本可以通过
Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count")
这种方式引用
@Location注解
定义Btrace对方法的拦截位置 通过@Location注解指定 默认为Kind.ENTRY
-
Kind.ENTRY 在进入方法时,调用Btrace脚本
-
Kind.RETURN 方法执行完时,调用Btrace脚本,只有把拦截位置定义为Kind.RETURN,才能获取方法的返回结果@Return和执行时间@Duration
-
Kind.CALL 分析方法中调用其它方法的执行情况
-
Kind.LINE 通过设置line,可以监控代码是否执行到指定的位置
-
Kind.ERROR 异常未捕获被抛出方法之外
-
Kind.THROW 异常抛出
-
Kind.CATCH 异常被捕获
参数注解
@Self用来指定被trace方法的this
@Return用来指定被trace方法的返回值
@ProbeClassName用来指定被trace的类名
@ProbeMethodName用来指定被trace的方法名
@TargetInstance用来指定被trace方法内部被调用到的实例
@TargetMethodOrField用来指定被trace方法内部被调用的方法名
BTrace方法
import static com.sun.btrace.BTraceUtils.exit;
import static com.sun.btrace.BTraceUtils.field;
import static com.sun.btrace.BTraceUtils.get;
import static com.sun.btrace.BTraceUtils.jstack;
import static com.sun.btrace.BTraceUtils.printEnv;
import static com.sun.btrace.BTraceUtils.printFields;
import static com.sun.btrace.BTraceUtils.printProperties;
import static com.sun.btrace.BTraceUtils.printVmArguments;
import static com.sun.btrace.BTraceUtils.println;
import com.sun.btrace.BTraceUtils.Strings;
import com.sun.btrace.BTraceUtils.Sys;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.annotations.Duration;
import com.sun.btrace.annotations.Kind;
import com.sun.btrace.annotations.Location;
import com.sun.btrace.annotations.OnMethod;
import com.sun.btrace.annotations.Self;
- 获取当前线程名称
BTraceUtils.Threads.name(BTraceUtils.currentThread())
- 获取Hash code
BTraceUtils.identityHashCode()
- 获取对象的类名称
BTraceUtils.Reflective.name(clazz)
- 返回类
BTraceUtils.classOf(obj)
- 打印信息
BTraceUtils.print()
BTraceUtils.println()
BTraceUtils.printArray()
printVmArguments()
printProperties()
printEnv()
printFields(obj)
- 获取值
field("java.lang.Thread", "name")
field(classForName("xxx.xxx.ClassName", contextClassLoader()),"fieldName")
get(field, obj)
getBoolean()
getInt()
- 系统相关
exit()//退出BTrace
heapUsage()//堆使用情况
nonHeapUsage()//非堆使用情况
jstack()//堆栈信息
Sys.Memory.dumpHeap("data.bin")//堆dump
限制
- 不能创建对象
- 不能抛出或者捕获异常
- 不能用synchronized关键字
- 不能对目标程序中的instace或者static变量
- 不能调用目标程序的instance或者static方法
- 脚本的field、method都必须是static的
- 脚本不能包括outer,inner,nested class
- 脚本中不能有循环,不能继承任何类,任何接口与assert语句
BTrace参数
-cp
btrace -cp .:xxx.jar $pid HelloWorld.java
指定classpath,如果脚本文件HelloWorld.java有用到xxx.jar包
-o
btrace -o data.txt $pid HelloWorld.java
输出到文件,比如我们需要获取缓存数据,这个缓存数据比较大,并且想使用这些,就可以使用-o参数
-u
可以用-u 运行在unsafe mode来规避前面提到的限制,但不推荐