分布式追踪 SkyWalking 源码分析七 agent和byteBuddy 原理

JVM 源码分析之 javaagent 原理完全解读

javaagent 的主要功能如下:

  • 可以在加载 class 文件之前做拦截,对字节码做修改
  • 可以在运行期对已加载类的字节码做变更,但是这种情况下会有很多的限制,后面会详细说
  • 还有其他一些小众的功能
    • 获取所有已经加载过的类
    • 获取所有已经初始化过的类(执行过 clinit 方法,是上面的一个子集)
    • 获取某个对象的大小
    • 将某个 jar 加入到 bootstrap classpath 里作为高优先级被 bootstrapClassloader 加载
    • 将某个 jar 加入到 classpath 里供 AppClassloard 去加载
    • 设置某些 native 方法的前缀,主要在查找 native 方法的时候做规则匹配

JVMTI 全称 JVM Tool Interface,是 JVM 暴露出来的一些供用户扩展的接口集合。JVMTI 是基于事件驱动的,JVM 每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者扩展自己的逻辑。

    比如最常见的,我们想在某个类的字节码文件读取之后、类定义之前修改相关的字节码,从而使创建的 class 对象是我们修改之后的字节码内容,那就可以实现一个回调函数赋给 jvmtiEnv(JVMTI 的运行时,通常一个 JVMTIAgent 对应一个 jvmtiEnv,但是也可以对应多个)的回调方法集合里的 ClassFileLoadHook,这样在接下来的类文件加载过程中都会调用到这个函数中

JVMTIAgent 其实就是一个动态库,利用 JVMTI 暴露出来的一些接口来干一些我们想做、但是正常情况下又做不到的事情,不过为了和普通的动态库进行区分

在启动时加载 instrument agent

正如前面“概述”里提到的方式,就是启动时加载 instrument agent,具体过程都在`InvocationAdapter.c`的`Agent_OnLoad`方法里

    类重新定义,这是 Instrumentation 提供的基础功能之一,主要用在已经被加载过的类上,想对其进行修改,要做这件事,我们必须要知道两个东西,一个是要修改哪个类,另外一个是想将那个类修改成怎样的结构,有了这两个信息之后就可以通过 InstrumentationImpl 下面的 redefineClasses 方法操作了。

在 JVM 里对应的实现是创建一个VM_RedefineClasses的VM_Operation,注意执行它的时候会 stop-the-world

  retransform class 可以简单理解为回滚操作,具体回滚到哪个版本,这个需要看情况而定,下面不管那种情况都有一个前提,那就是 javaagent 已经要求要有 retransform 的能力了

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

ByteBuddy深入学习

https://notes.diguage.com/byte-buddy-tutorial/

一、概述

在前面两节中,我们实现了Agent,但是其无论在使用方式和功能上面都有一定的局限性。本文我们借助字节码工具ByteBuddy,写出高级的Agent。

ByteBuddy不仅仅是为了生成Java-Agent,它提供的API甚至可以改变重写一个Java类

  与静态编译器类似,代码生成库在生成快速代码和快速生成代码之间面临着折衷。当在这些冲突的目标之间进行选择时,Byte Buddy 的主要侧重点在于以最少的运行时生成代码。通常,类型创建或操作不是任何程序中的常见步骤,并不会对任何长期运行的应用程序产生重大影响;特别是因为类加载或类构建(class instrumentation)是运行此类代码时最耗时且不可避免的步骤。

从上面的代码中,我们可以看到Byte Buddy要实现一个方法分为两步。首先,编程人员需要指定一个ElementMatcher,它负责识别一个或多个需要实现的方法。Byte Buddy提供了功能丰富的预定义拦截器(interceptor),它们暴露在ElementMatchers类中。在上述的例子中,toString方法完全精确匹配了名称,但是,我们也可以匹配更为复杂的代码结构,如类型或注解。

当Byte Buddy生成类的时候,它会分析所生成类型的类层级结构。在上述的例子中,Byte Buddy能够确定所生成的类要继承其超类Object的名为toString的方法,指定的匹配器会要求Byte Buddy重写该方法,这是通过随后的 Implementation 实例实现的,在我们的样例中,这个实例也就是FixedValue

当创建子类的时候,Byte Buddy始终会拦截(intercept)一个匹配的方法,在生成的类中重写该方法。但是,我们在本文稍后将会看到Byte Buddy还能够重新定义已有的类,而不必通过子类的方式来实现。在这种情况下,Byte Buddy会将已有的代码替换为生成的代码,而将原有的代码复制到另外一个合成的(synthetic)方法中。

在我们上面的代码样例中,匹配的方法进行了重写,在实现里面,返回了固定的值“Hello World!”。intercept方法接受Implementation类型的参数,Byte Buddy自带了多个预先定义的实现,如上文所使用的FixedValue类。但是,如果需要的话,可以使用前文所述的ASM API将某个方法实现为自定义的字节码,Byte Buddy本身也是基于ASM API实现的。

定义完类的属性之后,就能通过make方法来进行生成。在样例应用中,因为用户没有指定类名,所以生成的类会给定一个任意的名称。最终,生成的类将会使用ClassLoadingStrategy来进行加载。通过使用上述的默认 WRAPPER策略,类将会使用一个新的类加载器进行加载,这个类加载器会使用环境类加载器作为父加载器。

类加载之后,使用Java反射API就可以访问它了。如果没有指定其他构造器的话,Byte Buddy将会生成类似于父类的构造器,因此生成的类可以使用默认的构造器。

除了使用method()方法拦截方法之外,Byte Buddy 还提供了defineMethod() 方法来定义一个新方法

发布了365 篇原创文章 · 获赞 2 · 访问量 7432

猜你喜欢

转载自blog.csdn.net/kuaipao19950507/article/details/104223630