记一次多个Java Agent同时使用的类增强冲突问题及分析( 四 )


This function facilitates the instrumentation of already loaded classes. When classes are initially loaded or when they are redefined, the initial class file bytes can be transformed with the ClassFileTransformer. This function reruns the transformation process (whether or not a transformation has previously occurred)
这个方法将用于对已经加载的类进行插桩,并且是从最初类加载的字节码开始重新应用转换器,并且每一个被注册到JVM的转换器都将会被执行 。
通过这个方法,我们就可以对已经被加载的类进行transform,执行该方法后的流程如下,其实就是重新触发ClassFileTransformer中的transform方法:
记一次多个Java Agent同时使用的类增强冲突问题及分析

文章插图
值得注意的是,reTransformClasses 功能很强大,但是其也有一系列的限制,在官方文档描述中,其限制如下:
The retransformation must not add, remove or rename fields or methods, change the signatures of methods, or change inheritance.
重转换过程中,我们不能新增、删除或者重命名字段和方法,不能更改方法的签名,不能更改类的继承 。
字节码分析上述reTransformClasses方法的限制是否是问题产生的根因呢?
在反编译经过SkyWalking增强后的字节码文件后,原因水落石出 。类经过Skywalking增强之后的继承关系上多了implements EnhancedInstance 。这显然改变了类的继承关系,而这一点恰好是官网接口文档中明确描述的限制行为 。正是因为这个接口的实现导致了本文开头描述的多个JavaAgent的类冲突增强失败的问题 。
该问题在SkyWalking的社区中也有一个相关issue,社区解释为了减少链路追踪过程中的反射调用确实打破了reTransformClasses()的限制,类增强后新增实现了一个接口 。
final class Dispatcher$LegacyAsyncDispatcher extends Dispatcher implements EnhancedInstance { private final ConcurrentLinkedQueue<com.google.common.eventbus.Dispatcher.LegacyAsyncDispatcher.EventWithSubscriber> queue; private volatile Object _$EnhancedClassField_ws; private Dispatcher$LegacyAsyncDispatcher() { this.queue = Queues.newConcurrentLinkedQueue(); } void dispatch(Object var1, Iterator<Subscriber> var2) {delegate$51c0bj0.intercept(this, new Object[]{var1, var2}, cachedValue$P524FzM0$7gcbrk1, new JKwtdbN5(this)); } public void setSkyWalkingDynamicField(Object var1) { this._$EnhancedClassField_ws = var1; } public Object getSkyWalkingDynamicField() { return this._$EnhancedClassField_ws; } static {ClassLoader.getSystemClassLoader().loadClass("net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke((Object)null, Dispatcher$LegacyAsyncDispatcher.class, -1207479570);cachedValue$P524FzM0$7gcbrk1 = Dispatcher$LegacyAsyncDispatcher.class.getDeclaredMethod("dispatch", Object.class, Iterator.class); }}总结避免多个JavaAgent增强冲突的建议现在JavaAgent技术越来越受到各大厂商和开源社区的青睐,涌现出不少优秀的JavaAgent框架 。开发者或厂商在使用JavaAgent的时候难免会遇到同时挂载多个JavaAgent的场景,如果JavaAgent开发方能够对其他同类框架做到良好的兼容性,将会给使用者带来更少的麻烦,毕竟使用者未必能透彻的了解字节增强的底层原理 。
上文经过分析已经找到多个JavaAgent类增强冲突的根因,那么该如何避免此类问题出现呢?这里给出两点较为通用的建议 。
谨慎安排JavaAgent的挂载顺序前面我们提到SkyWalking和自研JavaAgent加载顺序会有不同的结果 。SkyWalking增强时对类的继承关系有修改,而自研JavaAgent则没有,那么该场景将兼容性相对较低的SkyWalking放在前面,兼容性相对较高的自研JavaAgent放在后面,可以暂时规避类增强的冲突问题 。
严格遵守字节码增强的使用要求和限制但是如果我们需要使用3个甚至更多的JavaAgent,上面的方法是治标不治本的 。

经验总结扩展阅读