BTrace使用

BTrace简介

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

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

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

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

咱们先来看一下使用BTrace怎么解决上面的那个问题,了解一下BTrace怎么使用。若是有地方暂时不清楚的也没有关系,能够看后面的一些说明。正则表达式

BTrace 导出map数据

首先到BTrace下载去下载BTrace的release版。数据库

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

根目录

bin目录

测试业务类

如今假设咱们的业务类是这样子的:jvm

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文件均可以),像下面这样子:ide

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实例

相关文章
相关标签/搜索