Java动态追踪技术--BTrace

Java动态追踪技术
  • 需求翻译官的日常工作不是在的改bug,发布代码,就是在加日志查找bug的路上。查BUG的过程是痛苦的,我们总是在不停的看代码,修改代码,添加日志,从而帮助我们发现问题,这种形式是比较繁琐的,不断的在发布项目,开发效率也不高,而且线上的问题排查不可能让你经常添加调试代码,能不能找到一种和JSP一样快捷的方式呢。
JSP模式
  • 对应大多数程序员来说,早期的时候都接触过JSP(java server pages)。虽然之后在前后端分离的主流思想下主键淘汰不用了,但是JSP还是有一些比较值得我们去思考的点,例如我们在使用JSP写页面效果的时候,修改代码只需要刷新页面,而不需重启服务,就可以看到页面展示效果。并不需要重启JVM。

  • 按照我们的常识,java程序一般都需要启动时候加载类文件,如果像JSP这样修改完代码,不用重启就生效,那么我们就不用这么痛苦的查bug,直接加代码又不用发,加日志看下就解决了。其实是JSP的运行机制和java不一样,当我们打开浏览器,请求一个JSP文件有如下流程:
    在这里插入图片描述

  • JSP文件修改后,之所以能及时生效,是因为tomcate会检查请求的JSP文件是否被更改过。如果发送过更改,呢么就会将JSP文件重新解析,翻译成一个新的java,从而产生新的servlet类,加载到jvm中。

  • 但是此处有一个问题,根据java类加载机制,同一个classLoader中,类是不允许重复的。为绕开这个限制,Tomcate每次会创建一个新的ClassLoader实例,来加载新编译的servlet类。之后的请求都会有这个新的Servlet来处理,这样就实现了新旧JSP的切换。

  • HTTP服务是无状态的,所以JSP的场景基本上是一次性消费,请求后给当前解析后的jsp内容,这种通过ClassLoader来替换class的方法在JSP上可行,因为有一个翻译过程,但是在Spring应用中,对象大多是单例,存在内存中,就算创建新ClassLoader也要全部在处理一次,不太现实,所有这种方式在Spring项目中不可行。

  • BTrace 插桩

java 对象行为
  • java对象使用两种东西来描述事物: 方法,属性
    • java对象的属性跟着对象走,每个对象存储一份
    • java对象的方法,函数存储在方法区
      在这里插入图片描述
  • 如上图:
    • 方法区中数据是类加载时候从class文件中取出
    • class文件是从java或者其他符号jvm规范的源码中编译来的
    • 源码我们我们可以自己控制
  • 通过上面几个步骤我们能否找到办法去修改需要加载的类,从而达到我们的目的,并且需要满足几个条件:
    • 需要修改字节码中目标方法所在区域,然后重新加载这个类
    • 只修改调用方法,不修改对象的属性,也不印象已经存在的对象状态
    • 不违背jvm类加载原理,也就是这个类还是这个类,还是同一个ClassLoader
  • 还真有:java.lang.instrument.Instrumentation
Instrumentation
  • 我们来查一下Java API中对这个类的描述信息:
该类提供了用于设计Java编程语言代码所需的服务。 仪器是向方法添加字节码,用于收集工具要使用的数据。 由于这些更改纯粹是加法的,因此这些工具不会修改应用程序的状态或行为。 这种良性工具的示例包括监视代理,剖析器,覆盖分析器和事件记录器。
  • 文档描述中有两个我们能用得到的方法:redefineClasses和retransformClasses。一个重新定义class,一个修改class。这两个差不多功能,
//   使用提供的类文件重新定义提供的一组类。
void redefineClasses(ClassDefinition... definitions)
              throws ClassNotFoundException,
                     UnmodifiableClassException
//重新转换提供的一组类。
void retransformClasses(<?>... classes)
                 throws UnmodifiableClassException
  • 两个方法都是替换已经存在的class文件,redefineClasses是自己提供字节码文件替换已经存在的class,retransformClasses是在已经存在的字节码文件上修改后在替换。
  • 既然JDK提供了这种API,那么我们可以在编译得到class文件后,在通过redefineClass替换,就能加日志,修改class文件,从而达到上文中不重启修改的目的。
直接操作字节码
  • 我们通过JDK的api来修改了本要加载的一个Class字节码文件,字节码文件也是程序语言,只不过人类不好理解,可读性远没有java代码高。
  • 一般人都不会去直接修改字节码文件,但是,有一部分杰出的程序员,创造出来可以直接编辑字节码的框架,提供接口可以让我们方便的去操作字节码文件,进行注入修改类的方法,动态创造一个新的类等等操作。其中最著名的就是ASM,现在我们接触的cglib,Spring等框架中对字节码的操作就是基于ASM上的,
实现方式
  • 截止目前都是针对开始遇到的问题的理论层面的可行性研究,那么我们怎么实现,而且修改线上的字节码文件简直就是在作死,而且实施起来会有意想不到的困难:

    • 寻找工程中的这个字节码
    • 修改这个字节码,然后reTransform这个字节码
    • 我们无法预知某个程序出错需要去修改字节码,也不可能每个工程都开发一段专门的程序去修改字节码,并且重新加载改代码,这样成本太高
    • 即使我们能解决上面的问题,我们也不一定会用ASM,需要更通用的办法
    • JVM不在本地,在远程,我们也是需要解决的问题
    • 修改线上JVM中字节码文件简直在作死,这存在巨大的安全性问题
  • 幸运的是,已经有人吧这么都搞定了,因为有一个开源工具BTrace的存在,描述非常精炼:

A safe, dynamic tracing tool for the Java platform
  • BTrace 是基于Java预约的一个安全,可提供动态追踪服务的工具。BTrace基于ASM,Java Attach API, Instruments开发,为用户提供很多注解,依靠这些注解,可以编写BTrace脚本(简单的Java脚本)达到我们的目的,而不必对ASM有深刻理解。

  • 我们看下Attach 的api

  • 看BTrace官网的一个简单例子:拦截所有java.io包中所有类以read开头的方法,打印类名,方法名,参数名。当程序IO负载比较高的时候,可以从输出的信息看到是哪些类引起的,如此的方便:

package com.sun.btrace.samples;

import com.sun.btrace.annotations.*;
import com.sun.btrace.AnyType;
import static com.sun.btrace.BTraceUtils.*;

/**
 * This sample demonstrates regular expression
 * probe matching and getting input arguments
 * as an array - so that any overload variant
 * can be traced in "one place". This example
 * traces any "readXX" method on any class in
 * java.io package. Probed class, method and arg
 * array is printed in the action.
 */
@BTrace public class ArgArray {
    @OnMethod(
        clazz="/java\\.io\\..*/",
        method="/read.*/"
    )
    public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
        println(pcn);
        println(pmn);
        printArray(args);
    }
}
先理解在应用
  • 既然BTrace能解决以上的问题,那么他是怎么做到的,他的架构是怎么样的,我们可以从管网的信息中找到答案:

  • BTrace主要有一下几个模块

    • BTrace脚本:利用BTrace定义的注解,我们可以很方便的更具需要进行脚本开发
    • Compiler(编译):将BTrace脚本编译成BTrace class文件
    • Client:将Class文件发送到Agent
    • Agent:基于Java的Attach Api,Agent可以动态附着一个运行的JVM上,然后开启一个BTrace Server,接受Client发过来的BTrace脚本。解析脚本然后根据脚本中的规则找到要修改的类;然后调用Java Instruments的reTransform接口完成对象行为的修改并使之生效。

在这里插入图片描述

在这里插入图片描述

安全性
  • 如上流程最终借助Java instruments 和Java Attach api实现class替换,出于安全考虑,instruments在使用上会有诸多的限制:
    • 不允许创建对象
    • 不允许创建数组
    • 不允许抛异常
    • 不允许catch异常
    • 不允许随意调用其他对象或者类的方法,只允许调用com.sun.btrace.BTraceUtils中提供的静态方法(一些数据处理和信息输出工具)
    • 不允许改变类的属性
    • 不允许有成员变量和方法,只允许存在static public void方法
    • 不允许有内部类、嵌套类
    • 不允许有同步方法和同步块
    • 不允许有循环
    • 不允许随意继承其他类(当然,java.lang.Object除外)
    • 不允许实现接口
    • 不允许使用assert
    • 不允许使用Class对象
  • 如此多的限制,其实可以理解。BTrace要做的是,虽然修改了字节码,但是除了输出需要的信息外,对整个程序的正常运行并没有影响。
应用
Arthas
  • BTrace脚本使用上也有一定学习成本,如果吧常用功能封装好,提供简单命令,那就编程了Archas
最后
  • 总结我们之前的知识点,Java的Instruments给运行时的动态追踪留下了一个初始APi,Attach API则给运行时动态追踪提供了“出入口”,ASM则大大方便了“人类”操作Java字节码的操作。
  • 所以基于以上JDK api,前辈们创造出来诸如JProfiler, JVisualvm,BTrace,Archas这样的工具。
  • 以上大大提高了软件开发人民定位问题的效率。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值