BTrace使用

BTrace简介

首先***BTrace就是为了解决线上问题而存在的***

举一个例子,因为设计问题,我们生产环境有一个Map的大小超过了16M,这个Map是要一次写入MongoDB数据库中的,但是MongoDB数据库的一个文档大小最多不超过16M,超过就没有办法写入,这个问题怎么解决?

首先,不能重启,重启数据就丢了。堆dump,然后去解析dump数据?今天我们来介绍一些使用BTrace解决这个问题。

BTrace可以获取程序运行时的数据信息,如方法参数、返回值、全局变量、堆栈信息等

我们先来看一下使用BTrace怎么解决上面的那个问题,了解一下BTrace怎么使用。如果有地方暂时不清楚的也没有关系,可以看后面的一些说明。

BTrace 导出map数据

首先到BTrace下载去下载BTrace的release版。

解压到文件,大概像是下面这样:

根目录

bin目录

测试业务类

现在假设我们的业务类是这样子的:

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这种方式就可以了。

ClassNotFound

如果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目录大概就是想下面这样了:

new_bin

然后

  1. 启动我们的测试业务类BusinessMap

  2. 执行jps命令找到BusinessMap的pid [jps]

  3. 使用下面的命令执行脚本(2个中的任一个)

btrace 5244 BusinessMapTrace.java
btrace -o D:\ptool\btrace\bin\map.txt 5244 BusinessMapTrace.java

-o是参数是输出到指定文件,注意要使用绝对路径,使用相对路径并不会出现在脚本目录下。

exe_btrace

如果你遇到下面的错误,不要着急,把脚本中的中文注释删除了就可以了。

gbk_error

如果出现什么btrace-libs找不到就在build下面新建一个btrace-libs把build下的jar包拷贝到btrace-libs目录下就可以了。

现在再看上面的问题是不是简单多了,这里我们就不多介绍例子了,你可以在BTrace实例这里看到更多的例子,或者在github下载的BTrace的release版本中的sample中有很多使用的例子,在UserGuide中有记录每一个例子测试的是什么。

下面主要介绍BTrace中常用的注解和方法。

@OnMethod注解

@OnMethod注解常用的属性:

  1. "clazz"属性:用来指定目标类名
  2. "method"属性:用来指定被trace的方法
  3. "location"属性:用来指定拦截时机
  4. "type"属性:用来指定方法签名(重载的时候,只有方法名不行)如:type="int (int, int)"

location后面单独讲,先说"clazz"和"method"

  1. 使用全限定名
clazz="cn.freemethod.btra.BtraceMap"
method="sayHello"

注意:静态内部类的写法,是在类与内部类之间加上"$"

  1. 使用正则表达式 方法和类都可以使用正则表达式
clazz="/java\\.lang\\..*/"
method="/.*/"
  1. 使用接口 接口在前面添加"+"表明是一个接口就可了
clazz="+xxx.xxx.Interface"
  1. 使用注解 类和方法都可以使用注解,在前面加"@"就可以了
clazz="@xxx.xxx.Annotation"
method="@xxx.xxx.Annotation"
  1. 构造方法 构造方法指定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

  1. Kind.ENTRY 在进入方法时,调用Btrace脚本

  2. Kind.RETURN 方法执行完时,调用Btrace脚本,只有把拦截位置定义为Kind.RETURN,才能获取方法的返回结果@Return和执行时间@Duration

  3. Kind.CALL 分析方法中调用其它方法的执行情况

  4. Kind.LINE 通过设置line,可以监控代码是否执行到指定的位置

  5. Kind.ERROR 异常未捕获被抛出方法之外

  6. Kind.THROW 异常抛出

  7. 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;
  1. 获取当前线程名称
BTraceUtils.Threads.name(BTraceUtils.currentThread())
  1. 获取Hash code
BTraceUtils.identityHashCode()
  1. 获取对象的类名称
BTraceUtils.Reflective.name(clazz)
  1. 返回类
BTraceUtils.classOf(obj)
  1. 打印信息
BTraceUtils.print()
BTraceUtils.println()
BTraceUtils.printArray()
printVmArguments()
printProperties()
printEnv()
printFields(obj)
  1. 获取值
field("java.lang.Thread", "name")
field(classForName("xxx.xxx.ClassName", contextClassLoader()),"fieldName")
get(field, obj)
getBoolean()
getInt()
  1. 系统相关
exit()//退出BTrace
heapUsage()//堆使用情况
nonHeapUsage()//非堆使用情况
jstack()//堆栈信息
Sys.Memory.dumpHeap("data.bin")//堆dump

限制

  1. 不能创建对象
  2. 不能抛出或者捕获异常
  3. 不能用synchronized关键字
  4. 不能对目标程序中的instace或者static变量
  5. 不能调用目标程序的instance或者static方法
  6. 脚本的field、method都必须是static的
  7. 脚本不能包括outer,inner,nested class
  8. 脚本中不能有循环,不能继承任何类,任何接口与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来规避前面提到的限制,但不推荐

参考

GitHub BTrace

BTrace简介及使用

Btrace入门到熟练小工完全指南

BTrace实例

猜你喜欢

转载自my.oschina.net/u/2474629/blog/1797075