文章目录

  • 一、引言
  • 二、BTrace是什么?
  • 三、BTrace原理
  • 四、安装配置
  • 五、注意事项
  • 六、使用示例
    • 1、拦截一个普通方法
    • 2、拦截构造函数
    • 3、拦截同名函数,以参数区分
    • 4、拦截方法返回值
    • 5、异常分析
    • 6、定位某个超过阈值的函数
    • 7、追踪方法执行时间
    • 8、性能分析
    • 9、死锁排查
  • 七、小结

一、引言

在我们对Java应用做性能分析的时候,往往采用log进行问题定位和分析,但是如果我们的log缺乏相关的信息呢?远程调试会影响应用的正常工作,修改代码重新部署应用,实时性和灵活性难以保证,有没有不影响正常应用运行,又灵活并无侵入性的方法呢?

答案是有,它就是Java中的神器-BTrace

二、BTrace是什么?

BTrace使用Java的Attach技术,可以让我们无缝的将我们BTrace脚本挂到JVM上,通过脚本你可以获取到任何你想拿到的数据,在侵入性和安全性都非常可靠,特别是定位线上问题的神器。

三、BTrace原理

BTrace是基于动态字节码修改技术(Hotswap)向目标程序的字节码注入追踪代码。

四、安装配置

关于BTrace的安装配置使用,此处就不再重复造轮子,网上有太多的教程。

官网地址:https://github.com/btraceio/btrace

五、注意事项

生产环境可以使用,但修改的字节码不会被还原,使用Btrace时,需要确保追踪的动作是只读的(即:追踪行为不能修改目标程序的状态)和有限的行为(即:追踪行为需要在有限的时间内终止),一个追踪行为需要满足以下的限制:

  • 不能创建新的对象 不能创建新的数组
  • 不能抛出异常
  • 不能捕获异常
  • 不能对实例或静态方法调用-只有从BTraceUtils中的public static方法中或在当前脚本中声明的方法,可以被BTrace调用
  • 不能有外部,内部,嵌套或本地类
  • 不能有同步块或同步方法
  • 不能有循环(for,while,do…while)
  • 不能继承抽象类(父类必须是java.lang.Object)
  • 不能实现接口
  • 不能有断言语句
  • 不能有class保留字

以上的限制可以通过通过unsafe模式绕过。追踪脚本和引擎都必须设置为unsafe模式。脚本需要使用注解为@BTrace(unsafe = true),需要修改BTrace安装目录下bin中btrace脚本将-Dcom.sun.btrace.unsafe=false改为-Dcom.sun.btrace.unsafe=true

注:关于unsafe的使用,如果你的程序一旦被btrace追踪过,那么unsafe的设置会一直伴随该进程的整个生命周期。如果你修改了unsafe的设置,只有通过重启目标进程,才能获得想要的结果。所以该用法不是很好使用,如果你的应用不能随便重启,那么你在第一次使用btrace最终目标进程之前,先想好到底使用那种模式来启动引擎。

六、使用示例

1、拦截一个普通方法

control方法

@GetMapping(value = "/arg1")public String arg1(@RequestParam("name") String name) throws InterruptedException {Thread.sleep(2000);return "7DGroup," + name;}

BTrace脚本

/*** 拦截示例*/
@BTrace
public class PrintArgSimple {@OnMethod(//类名clazz = "com.techstar.monitordemo.controller.UserController",//方法名method = "arg1",//拦截时刻:入口location = @Location(Kind.ENTRY))/*** 拦截类名和方法名*/ public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {BTraceUtils.printArray(args);BTraceUtils.println(pcn + "," + pmn);BTraceUtils.println();}
}

拦截结果:

192:Btrace apple$ jps -l
369
5889 /Users/apple/Downloads/performance/apache-jmeter-4.0/bin/ApacheJMeter.jar
25922 sun.tools.jps.Jps
23011 org.jetbrains.idea.maven.server.RemoteMavenServer
25914 org.jetbrains.jps.cmdline.Launcher
25915 com.techstar.monitordemo.MonitordemoApplication
192:Btrace apple$ btrace 25915 PrintArgSimple.java
[zuozewei, ]
com.techstar.monitordemo.controller.UserController,arg1[zee, ]
com.techstar.monitordemo.controller.UserController,arg1

2、拦截构造函数

构造函数

@Data
public class User {private int id;private String name;}

control方法

@GetMapping(value = "/arg2")public User arg2(User user) {return user;}

BTrace脚本

/*** 拦截构造函数*/
@BTrace
public class PrintConstructor {@OnMethod(clazz = "com.techstar.monitordemo.domain.User", method = "<init>")public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {BTraceUtils.println(pcn + "," + pmn);BTraceUtils.printArray(args);BTraceUtils.println();}
}

拦截结果

192:Btrace apple$ btrace 34119 PrintConstructor.java
com.techstar.monitordemo.domain.User,<init>
[1, zuozewei, ]

3、拦截同名函数,以参数区分

control方法

@GetMapping(value = "/same1")public String same(@RequestParam("name") String name) {return "7DGroup," + name;}@GetMapping(value = "/same2")public String same(@RequestParam("id") int id, @RequestParam("name") String name) {return "7DGroup," + name + "," + id;}

BTrace脚本

/*** 拦截同名函数,通过输入的参数区分*/@BTrace
public class PrintSame {@OnMethod(clazz = "com.techstar.monitordemo.controller.UserController", method = "same")public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, String name) {BTraceUtils.println(pcn + "," + pmn + "," + name);BTraceUtils.println();}
}

拦截结果

192:Btrace apple$ jps -l
369
5889 /Users/apple/Downloads/performance/apache-jmeter-4.0/bin/ApacheJMeter.jar
34281 sun.tools.jps.Jps
34220 org.jetbrains.jps.cmdline.Launcher
34221 com.techstar.monitordemo.MonitordemoApplication
192:Btrace apple$ btrace 34221 PrintSame.java
com.techstar.monitordemo.controller.UserController,same,zuozeweicom.techstar.monitordemo.controller.UserController,same,zuozeweicom.techstar.monitordemo.controller.UserController,same,zuozewei

4、拦截方法返回值

BTrace脚本

/*** 拦截返回值*/
@BTrace
public class PrintReturn {@OnMethod(clazz = "com.techstar.monitordemo.controller.UserController", 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();}
}

拦截结果

192:Btrace apple$ jps -l
34528 org.jetbrains.jps.cmdline.Launcher
34529 com.techstar.monitordemo.MonitordemoApplication
369
5889 /Users/apple/Downloads/performance/apache-jmeter-4.0/bin/ApacheJMeter.jar
34533 sun.tools.jps.Jps
192:Btrace apple$ btrace 34529 PrintReturn.java
com.techstar.monitordemo.controller.UserController,arg1,7DGroup,zuozewei

5、异常分析

有时候开发人员对异常处理不合理,导致某些重要异常人为被吃掉,并且没有日志或者日志不详细,导致性能分析定位问题困难,我们可以使用BTrace来处理

control方法

@GetMapping(value = "/exception")public String exception() {try {System.out.println("start...");System.out.println(1 / 0); //模拟异常System.out.println("end...");} catch (Exception e) {}return "successful...";}

BTrace脚本

/*** 有时候,有些异常被人为吃掉,日志又没有打印,这个时候可以用该类定位问题* This example demonstrates printing stack trace* of an exception and thread local variables. This* trace script prints exception stack trace whenever* java.lang.Throwable's constructor returns. This way* you can trace all exceptions that may be caught and* "eaten" silently by the traced program. Note that the* assumption is that the exceptions are thrown soon after* creation [like in "throw new FooException();"] rather* that be stored and thrown later.*/
@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!@TLSstatic 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) {currentException = self;}@OnMethod(clazz = "java.lang.Throwable", method = "<init>")public static void onthrow1(@Self Throwable self, String s) {currentException = self;}@OnMethod(clazz = "java.lang.Throwable", method = "<init>")public static void onthrow1(@Self Throwable self, String s, Throwable cause) {currentException = self;}@OnMethod(clazz = "java.lang.Throwable", method = "<init>")public static void onthrow2(@Self Throwable self, Throwable cause) {currentException = self;}// 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) {Threads.jstack(currentException);BTraceUtils.println("=====================");currentException = null;}}
}

拦截结果

192:Btrace apple$ jps -l
369
5889 /Users/apple/Downloads/performance/apache-jmeter-4.0/bin/ApacheJMeter.jar
34727 sun.tools.jps.Jps
34666 org.jetbrains.jps.cmdline.Launcher
34667 com.techstar.monitordemo.MonitordemoApplication
192:Btrace apple$ btrace 34667 PrintOnThrow.java
java.lang.ClassNotFoundException: org.apache.catalina.webresources.WarResourceSetjava.net.URLClassLoader.findClass(URLClassLoader.java:381)java.lang.ClassLoader.loadClass(ClassLoader.java:424)java.lang.ClassLoader.loadClass(ClassLoader.java:411)sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)java.lang.ClassLoader.loadClass(ClassLoader.java:357)org.apache.catalina.webresources.StandardRoot.isPackedWarFile(StandardRoot.java:656)org.apache.catalina.webresources.CachedResource.validateResource(CachedResource.java:109)org.apache.catalina.webresources.Cache.getResource(Cache.java:69)org.apache.catalina.webresources.StandardRoot.getResource(StandardRoot.java:216)org.apache.catalina.webresources.StandardRoot.getResource(StandardRoot.java:206)org.apache.catalina.mapper.Mapper.internalMapWrapper(Mapper.java:1027)org.apache.catalina.mapper.Mapper.internalMap(Mapper.java:842)org.apache.catalina.mapper.Mapper.map(Mapper.java:698)org.apache.catalina.connector.CoyoteAdapter.postParseRequest(CoyoteAdapter.java:679)org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:336)org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:800)org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:800)org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1471)org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)java.lang.Thread.run(Thread.java:748)
=====================
...

6、定位某个超过阈值的函数

BTrace脚本

*** 探测某个包路径下的方法执行时间是否超过某个阈值的程序,如果超过了该阀值,则打印当前线程的栈信息。*/import com.sun.btrace.BTraceUtils;
import com.sun.btrace.annotations.*;import static com.sun.btrace.BTraceUtils.*;@BTrace
public class PrintDurationTracer {@OnMethod(clazz = "/com\\.techstar\\.monitordemo\\..*/", method = "/.*/", location = @Location(Kind.RETURN))public static void trace(@ProbeClassName String pcn, @ProbeMethodName String pmn, @Duration long duration) {//duration的单位是纳秒if (duration > 1000 * 1000 * 2) {BTraceUtils.println(Strings.strcat(Strings.strcat(pcn, "."), pmn));BTraceUtils.print(" 耗时:");BTraceUtils.print(duration);BTraceUtils.println("纳秒,堆栈信息如下");jstack();}}
}

拦截结果

192:Btrace apple$ btrace 39644 PrintDurationTracer.java
com.techstar.monitordemo.controller.Adder.execute 耗时:1715294657纳秒,堆栈信息如下
com.techstar.monitordemo.controller.Adder.execute(Adder.java:13)
com.techstar.monitordemo.controller.Main.main(Main.java:10)
com.techstar.monitordemo.controller.Adder.execute 耗时:893795666纳秒,堆栈信息如下
com.techstar.monitordemo.controller.Adder.execute(Adder.java:13)
com.techstar.monitordemo.controller.Main.main(Main.java:10)
com.techstar.monitordemo.controller.Adder.execute 耗时:1331363658纳秒,堆栈信息如下
com.techstar.monitordemo.controller.Adder.execute(Adder.java:13)

7、追踪方法执行时间

BTrace脚本

/*** 追踪某个方法的执行时间,实现原理同AOP一样。*/
@BTrace
public class PrintExecuteTimeTracer {@TLSstatic long beginTime;@OnMethod(clazz = "com.techstar.monitordemo.controller.Adder", method = "execute")public static void traceExecuteBegin() {beginTime = timeMillis();}@OnMethod(clazz = "com.techstar.monitordemo.controller.Adder", method = "execute", location = @Location(Kind.RETURN))public static void traceExecute(int arg1, int arg2, @Return int result) {BTraceUtils.println(strcat(strcat("Adder.execute 耗时:", str(timeMillis() - beginTime)), "ms"));BTraceUtils.println(strcat("返回结果为:", str(result)));}
}

拦截结果

192:Btrace apple$ btrace 40863 PrintExecuteTimeTracer.java
Adder.execute 耗时:803ms
返回结果为:797
Adder.execute 耗时:1266ms
返回结果为:1261
Adder.execute 耗时:788ms
返回结果为:784
Adder.execute 耗时:1524ms
返回结果为:1521
Adder.execute 耗时:1775ms

8、性能分析

压测的时候经常发现某一个服务变慢了,但是由于这个服务有很多的业务逻辑和方法构成,这个时候就不好定位到底慢在哪个地方。BTrace可以解决这个问题,只需要大概定位问题可能存在的地方,通过包路径模糊匹配,就可以找到问题。

BTrace脚本

/**** Description:* This script demonstrates new capabilities built into BTrace 1.2* Shortened syntax - when omitting "public" identifier in the class* definition one can safely omit all other modifiers when declaring methods* and variables* Extended syntax for @ProbeMethodName annotation - you can use* parameter to request a fully qualified method name instead of* the short one* Profiling support - you can use {@linkplain Profiler} instance to gather* performance data with the smallest overhead possible*/
@BTrace
class Profiling {@PropertyProfiler profiler = BTraceUtils.Profiling.newProfiler();@OnMethod(clazz = "/com\\.techstar\\..*/", method = "/.*/")void entry(@ProbeMethodName(fqn = true) String probeMethod) {BTraceUtils.Profiling.recordEntry(profiler, probeMethod);}@OnMethod(clazz = "/com\\.techstar\\..*/", method = "/.*/", location = @Location(value = Kind.RETURN))void exit(@ProbeMethodName(fqn = true) String probeMethod, @Duration long duration) {BTraceUtils.Profiling.recordExit(profiler, probeMethod, duration);}@OnTimer(5000)void timer() {BTraceUtils.Profiling.printSnapshot("Performance profile", profiler);}

9、死锁排查

我们怀疑程序是否有死锁,可以通过以下的脚步扫描追踪,非常简单方便。

/*** This BTrace program demonstrates deadlocks* built-in function. This example prints* deadlocks (if any) once every 4 seconds.*/
@BTrace
public class PrintDeadlock {@OnTimer(4000)public static void print() {deadlocks();}
}

七、小结

BTrace是一个事后工具,所谓的事后工具就是在服务已经上线或者压测后,但是发现有问题的时候,可以使用BTrace动态跟踪分析。

  1. 比如哪些方法执行太慢,例如监控方法执行时间超过1秒的方法;
  2. 查看哪些方法调用了system.gc(),调用栈是怎样的;
  3. 查看方法的参数和属性
  4. 哪些方法发生了异常

总之,这里只是将部分经常用的列举了下抛砖引玉,还有很多没有列举,大家可以参考官方的其他Sample去玩下。

本文源码:
https://github.com/zuozewei/blog-example/tree/master/Performance-testing/03-performance-monitoring/btrace

性能工具之Java分析工具BTrace入门相关推荐

  1. iOS开发工具——网络封包分析工具Charles

    iOS开发工具--网络封包分析工具Charles 简介 Charles是在Mac下常用的截取网络封包的工具,在做iOS开发时,我们为了调试与服务器端的网络通讯协议,常常需要截取网络封包来分析.Char ...

  2. java堆栈分析工具_JVM内存分析工具使用

    Java 内存堆栈分析.我们在分析现网问题时候,经常会遇到一些问题从日志上无法分析的疑难问题.在我们举足无措的时候,我们可以分析一些JVM内存,来看看问题出在哪里了. 我们经常用到的一工具: 分析栈内 ...

  3. JDK的命令行工具、故障处理分析工具

    目录 •写在前面 •jps虚拟机进程状况工具 •jstat虚拟机统计信息监视工具 •jinfo配置信息工具 •jmap内存映像工具 •jhat虚拟机堆转储快照分析工具 •jstack堆栈跟踪器 •JC ...

  4. weblogic工具_WebLogic Classloader分析工具

    weblogic工具 WebLogic Server具有一个名为Classloader Analysis Tool的内置Web应用程序,您可以通过http:// localhost:7001 / wl ...

  5. 程序崩溃 分析工具_程序分析工具| 软件工程

    程序崩溃 分析工具 A program analysis tool implies an automatic tool that takes the source code or the execut ...

  6. insert转update工具_mysql binlog 分析工具

    用于回滚工具: binlog2sql 分析问题工具: analysis_binlog(https://gitee.com/mo-shan/analysis_binlog) 介绍 分析binlog工具, ...

  7. 建站必备SEO工具和网站分析工具

    Seo是一种廉价的推广手法,做的好能在短时间内给你带来不错的流量.正如我在上篇文章中说的只有通过网站分析才能够了解自己的一些不足,分析方法很多.从中我也介绍了谷歌的Google Analytics流量 ...

  8. mysql 查询分析工具下载_SQL分析工具下载-SQL查询工具(DB Solo)下载v5.2.5官方版-西西软件下载...

    DB Solo是一款完美的数据库查询分析工具.软件优秀跨平台SQL查询功能,支持所有主要DBMS产品:主要用于POJO的J2EE代码生成器,EJB 3.0批注,使用DAO  模式的JDBC持久层,JU ...

  9. python足球数据可视化_NBA数据分析_python可视化数据分析_可视化数据分析工具_可视化分析工具-帆软...

    夺冠没含金量!python和BI可视化分析,湖人赢在这点上. 在经历了很多很多之后,湖人队终于获得了总冠军,众望所归. 如果科比还在的话,一定也很自豪吧,毕竟上一次夺冠还是10年前. 那问题来了,为什 ...

最新文章

  1. Android乐动力的开始启动页面开源代码
  2. ROS学习笔记九:ROS工具
  3. rust 案例_理解Rust的引用与借用
  4. linux打开图形化命令,在Linux命令行中以图形化窗口打开文件夹
  5. kernel devel 安装与卸载
  6. spark 应用场景2-身高统计
  7. React使用的扩展
  8. magento网站建设_跨境自建站Magento麦进斗代打包代贴单代发货
  9. WORD中插入的公式与文字对不齐——公式比文字高——文字比公式低
  10. 用户角色权限设计思路
  11. [CTO札记]社区领域模型-SRC抽象模型
  12. 计算机使用快捷键大全
  13. Python2.7利用xpath爬取韩寒博客(多线程版)
  14. opencv 双目测距
  15. MES制造执行系统的四层架构体系
  16. 美国恐怖故事第一季/全集American Horror Story 1全迅雷下载
  17. 计算机usb接口无法充电,电脑可充电USB接口不能使用怎么办
  18. latex图片及其标题居右
  19. hdu4311 Meeting point-1 求最小的曼哈顿距离和
  20. 最新AI产品经理求职动态:卡年龄、卡学历,这么卷,怎么办?

热门文章

  1. 蔡颖-《APS走向实践》书解读之四:供应链、系统模型、APS软件
  2. redis的setex key seconds value命令的bug
  3. 华为ensp的路由器怎么和本地电脑通信
  4. [转]电子产品将标环保使用期限
  5. 亚马逊出单技巧 掌握财富密码
  6. ubuntu下安装gfortran
  7. 两种依赖注入的类型是什么?
  8. 《肖申克的救赎》- 阅后小记
  9. Android/安卓 文本添加中划线、下划线的方法
  10. R语言——ggplot2的绘图逻辑