記一次多個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接口進行字節碼增強操作時,我們必要使用的方法便是:

推薦閱讀