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


/** * A strategy for discovering types to redefine. */public interface DiscoveryStrategy { /*** Resolves an iterable of types to retransform. Types might be loaded during a previous retransformation which might require* multiple passes for a retransformation.** @param instrumentation The instrumentation instance used for the redefinition.* @return An iterable of types to consider for retransformation.*/ Iterable<Iterable<Class<?>>> resolve(Instrumentation instrumentation); /*** A discovery strategy that considers all loaded types supplied by {@link Instrumentation#getAllLoadedClasses()}.*/ enum SinglePass implements DiscoveryStrategy { /*** The singleton instance.*/INSTANCE; /*** {@inheritDoc}*/ public Iterable<Iterable<Class<?>>> resolve(Instrumentation instrumentation) { return Collections.<Iterable<Class<?>>>singleton(Arrays.<Class<?>>asList(instrumentation.getAllLoadedClasses())); } }在AgentBuilder中,retransform过程如下图进行 。首先AgentBuilder在构建过程中会根据重定义策略来对JVM中当前已加载的所有类来进行筛选处理,执行到Dispatcher#retransformClasses()时已经筛选出JVM已加载的类和SkyWalking声明要增强的类的交集,最终将通过反射调用到字节码增强的底层实现逻辑Instrumentation#retransformClasses(),通过native方法retransformClasses0()来完成最后的处理 。

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

文章插图
上文所述产生冲突的类com.google.common.eventbus.Dispatcher$LegacyAsyncDispatcher就在Instrumentation#retransformClasses()要处理的类的集合中 。
记一次多个Java Agent同时使用的类增强冲突问题及分析

文章插图
根因探究分析到这一步,可以初步看出应该是retransformClasses()方法的某些限制造成冲突的类遇到前面的的java.lang.UnsupportedOperationException异常的抛出 。因此接下来分析下Instrumentation的实现逻辑 。
transform在使用java.lang.instrument.Instrumentation接口进行字节码增强操作时,我们必要使用的方法便是:
void addTransformer(ClassFileTransformer transformer, boolean canRetransform)通过此方法,我们可以为我们想要操作的类添加一个ClassFileTransFormer,顾名思义其为类文件转换器,其官方描述如下:
All future class definitions will be seen by the transformer, except definitions of classes upon which any registered transformer is dependent. The transformer is called when classes are loaded, when they are redefined. and if canRetransform is true, when they are retransformed.
简单来讲,在对一个类注册了该转换器后,未来该类的每一次redefine以及retransform,都会被该转换器检查到,并且执行该转换器的操作 。
由上述描述可以知道,我们想要做的字节码增强操作就是通过向JVM中添加转换器并且通过转换器将JVM中的类转换为我们想要的结果(Transform a class by transfomer.)流程如下:
记一次多个Java Agent同时使用的类增强冲突问题及分析

文章插图
首先通过premain方法运行JavaAgent,此时在premain参数中我们可以获取到Instrumentation,第二步通过Instrumentation接口将实现的ClassFileTransfomer注册到JVM上,当JVM去加载类的时候,ClassFileTransfomer会获得类的字节数组,并对其进行transform后再返回给JVM,此后该类在Java程序中的表现就是转换之后的结果 。
retransform上述为类加载时Instrumentation在其中所做的工作,但是如果类以及被加载完成后,想要再次对其做转换(适用于多个JavaAgent场景及通过agentmain方式运行JavaAgent),就需要使用到Instrumentation接口为我们提供的如下方法:
void retransformClasses(Class<?>... classes) throws UnmodifiableClassException其官方描述如下:

经验总结扩展阅读