性能文章>MProfiler的字节码增强服务>

MProfiler的字节码增强服务原创

https://a.perfma.net/img/2382850
11月前
234014

在引入字节码增强Service之前,研究了一下BTrace和jvm-sandbox的实现,因为这两个开源软件是以程序编写的方式使用的,另外还有Arthas,虽然是以命令执行的方式使用的,但其相关的一些命令,如monitor和watch等仍然使用了字节码增强,有很高的参考价下面我们就这3个知名度比较大的开源软件进行对比,如下表所示:

在使用上,个人觉得BTrace更简洁一些,而jvm-sandbox需要新建module并打包成jar后存储到相应的目录中才可以。举个例子体验一下BTrace和jvm-sandbox,需要被增强的代码如下:

package btrace.script;
 
public class Calculator {
    private int c = 1;
 
    public int add(int a, int b) {
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return a + b;
    }
}
BTrace增强脚本如下:
@BTrace
public class BTraceTest {
    @OnMethod(
            clazz = "btrace.script.Calculator",
            method = "add",
            location = @Location(Kind.RETURN)
    )
    public static void trace(int a, int b, @Return int sum) {
        println("trace1:a=" + a + ",b=" + b + ",sum=" + sum);
    }
}

jvm-sandbox增强脚本如下:

@MetaInfServices(Module.class)
@Information(id = "calculator")
public class JVMSandboxTest implements Module {
 
    @Resource
    private ModuleEventWatcher moduleEventWatcher;
 
    @Command("trace")
    public void trace() {
        new EventWatchBuilder(moduleEventWatcher)
                .onClass("Calculator")
                .onBehavior("add")
                .onWatch(new AdviceListener() {
                    @Override
                    protected void afterReturning(Advice advice) throws Throwable {
                        Object[] parameters = advice.getParameterArray();
                        if (parameters != null) {
                            for (Object obj : parameters) {
                                System.out.println("参数类型:" + obj.getClass().getName());
                                System.out.println("参数值:" + obj);
                            }
                        }
                    }
                });
 
    }
}

两个增强脚本都是在目标应用程序中执行的,所以BTrace类的trace()方法和AdviceListener匿名类中afterReturning()方法都是在目标进程中执行的,我们可以将采集到的数据发送到脚本启动进程中,例如BTrace脚本中的println()就将结果通过socket直接传回去了,而jvm-sandbox由于使用的是http,所以只能主动去拉取数据。

如果要将jvm-sandbox或BTrace集成到MProfiler并以Service的方式提供出来,需要在增强脚本中将相关的数据按一定的格式发送到MProfiler。

假设现在MProfiler要对Calculator类的add()方法进行增强,获取某一次的调用参数,则增强逻辑类似如下:

@Inject
MPorfilerInstrumentService service;
 
MPorfilerInstrument<Result> mp = service.createMProfilerInstrument()
 .onClass("Calculator")
 .onMethod("add")
 .onAgentWatch(new AdviceListener() {
      @Override
      protected Result afterReturning(Advice advice) throws Throwable {
         // Agent附加的目标进程中执行的逻辑
         // 将获取到的信息封装为特定类对象(这里是Result对象)后返回
         Result r = ...
         return r;
      }
 });

我们为了防止执行诊断项的主线程阻塞,需要在MProfiler中异步处理增强返回的结果,如下:

// 在MProfiler进程中异步执行
mp.thenAccept((result) -> {
   Object[] parameters = result.getParameterArray();
   if (parameters != null) {
       for (Object obj : parameters) {
          System.out.println("参数类型:" + obj.getClass().getName());
          System.out.println("参数值:" + obj);
       }
   }
});
 
// 在MProfiler进程中异步执行处理异常
mp.exceptionally((e) -> {
   e.printStackTrace();
   return null;
});

由于MProfiler中已经封装并提供了Attach服务,获取所有类服务(用于Attach后执行retransform操作)及定义了进程间socket通信方式,所以如果要深度集成BTrace或jvm-sandbox还需要大的改动,不如借鉴这些成熟工具自己编写一个字节码增强服务。

 

 

点赞收藏
分类:标签:
鸠摩

著有《深入解析Java编译器:源码剖析与实例详解》、《深入剖析Java虚拟机:源码剖析与实例详解》等书籍。公众号“深入剖析Java虚拟机HotSpot”作者

请先登录,查看1条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步
4
1