首先***BTrace就是为了解决线上问题而存在的***html
举一个例子,由于设计问题,咱们生产环境有一个Map的大小超过了16M,这个Map是要一次写入MongoDB数据库中的,可是MongoDB数据库的一个文档大小最多不超过16M,超过就没有办法写入,这个问题怎么解决?java
首先,不能重启,重启数据就丢了。堆dump,而后去解析dump数据?今天咱们来介绍一些使用BTrace解决这个问题。git
BTrace能够获取程序运行时的数据信息,如方法参数、返回值、全局变量、堆栈信息等github
咱们先来看一下使用BTrace怎么解决上面的那个问题,了解一下BTrace怎么使用。若是有地方暂时不清楚的也没有关系,能够看后面的一些说明。正则表达式
首先到BTrace下载去下载BTrace的release版。数据库
解压到文件,大概像是下面这样:缓存
如今假设咱们的业务类是这样子的: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"); } }
咱们想要获取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这种方式就能够了。
若是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注解经常使用的属性:
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"
注意正则表达式须要写在两个 "/",正则表达式的范围要尽量的小,否则会很是慢
注意:@OnMethod都是注解的public static void方法
定时触发Trace,时间能够指定,单位为毫秒
当trace代码抛异常或者错误时,该注解的方法会被执行.若是同一个trace脚本中其余方法抛异常,该注解方法也会被执行
当trace方法调用内置exit(int)方法(用来结束整个trace程序),该注解的方法会被执行
用来截获"外部"btrace client触发的事件
当内存超过某个设定值将触发该注解的方法
使用外部文件XML来定义trace方法以及具体的位置
定义ThreadLocal的共享变量
该注解的静态属性主要用来与jvmstat计数器作关联 例若有:
@Export private static long count;
其余脚本能够经过
Counters.perfLong("btrace.com.sun.btrace.samples.ThreadCounter.count")
这种方式引用
定义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方法内部被调用的方法名
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())
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
btrace -cp .:xxx.jar $pid HelloWorld.java
指定classpath,若是脚本文件HelloWorld.java有用到xxx.jar包
btrace -o data.txt $pid HelloWorld.java
输出到文件,好比咱们须要获取缓存数据,这个缓存数据比较大,而且想使用这些,就可使用-o参数
能够用-u 运行在unsafe mode来规避前面提到的限制,但不推荐