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


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

文章插图
分析源码可知SkyWalking使用的是Byte Buddy字节码增强工具 , AgentBuilder作为其提供字节码增强的接口,SkyWalking中使用到的是如下的默认的AgentBuilder$Default,其中的RedefinitionStrategy规定了已加载的类如何被构建的JavaAgent修改字节码,RedefinitionStrategy.DiscoveryStrategy则规定了发现哪些类来进行字节码的重定义,该默认策略使用的是RedefinitionStrategy.DiscoveryStrategy.SinglePass
/** * Creates a new agent builder with default settings. By default, Byte Buddy ignores any types loaded by the bootstrap class loader, any * type within a {@code net.bytebuddy} package and any synthetic type. Self-injection and rebasing is enabled. In order to avoid class format * changes, set {@link AgentBuilder#disableClassFormatChanges()}. All types are parsed without their debugging information * ({@link PoolStrategy.Default#FAST}). * * @param byteBuddy The Byte Buddy instance to be used. */public Default(ByteBuddy byteBuddy) { this(byteBuddy, Listener.NoOp.INSTANCE,DEFAULT_LOCK, PoolStrategy.Default.FAST, TypeStrategy.Default.REBASE, LocationStrategy.ForClassLoader.STRONG, NativeMethodStrategy.Disabled.INSTANCE, WarmupStrategy.NoOp.INSTANCE, TransformerDecorator.NoOp.INSTANCE, new InitializationStrategy.SelfInjection.Split(), RedefinitionStrategy.DISABLED, RedefinitionStrategy.DiscoveryStrategy.SinglePass.INSTANCE, RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE, RedefinitionStrategy.Listener.NoOp.INSTANCE, RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE, InjectionStrategy.UsingReflection.INSTANCE, LambdaInstrumentationStrategy.DISABLED, DescriptionStrategy.Default.HYBRID, FallbackStrategy.ByThrowableType.ofOptionalTypes(), ClassFileBufferStrategy.Default.RETAINING, InstallationListener.NoOp.INSTANCE, new RawMatcher.Disjunction( new RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader().or(isExtensionClassLoader())), new RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.") .and(not(ElementMatchers.nameStartsWith(NamingStrategy.BYTE_BUDDY_RENAME_PACKAGE + "."))) .or(nameStartsWith("sun.reflect.").or(nameStartsWith("jdk.internal.reflect."))) .<TypeDescription>or(isSynthetic()))), Collections.<Transformation>emptyList()); }RedefinitionStrategy.DiscoveryStrategy.SinglePass源码中的resolve()方法返回的是instrumentation.getAllLoadedClasses(),也就是说,该方法将返回JVM当前加载的所有类的集合 。由此可以看出 , AgentBuilder$Default将会对所有在JVM中已加载的类进行筛?。ㄒ舶ㄆ淠诓坷啵?。上文提到com.google.common.eventbus.Dispatcher和其内部类都在其中 。RedefinitionStrategy作为字节码redefine的策略将作用于字节码增强的retransform过程 。
/** * 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接口进行字节码增强操作时,我们必要使用的方法便是:

推荐阅读