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

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

文章插图
经过调整后,发现两个JavaAgent都没有错误日志,而且各拦截点的增强也能正常生效,没有遇到类增强的冲突问题 。
问题表象给人的直觉是JavaAgent的加载顺序确实对字节码增强有关系 。但是为什么会出现这种现象呢?
冲突根因分析增强失败的类在两个JavaAgent中的角色上面提到,先加载自研JavaAgent后加载SkyWalking的场景中遇到SkyWalking对com.google.common.eventbus.Dispatcher$LegacyAsyncDispatcher增强失败 。Dispatcher$LegacyAsyncDispatcher这个类在SkyWalking的插件中定义为被拦截增强的类 。
经过排查发现Dispatcher$LegacyAsyncDispatcher也被自研JavaAgent中在增强过程中作为第三方依赖引入,但并未对其增强 。
Debug分析鉴于自研JavaAgent没有报错,但SkyWalking出现异常,所以对SkyWalking进行debug分析 。
在premain方法中,可以看到进入到SkyWalkingAgent时``com.google.common.eventbus.Dispatcher`已经被加载了 。观察它的类加载器,可以知道该类是在自研JavaAgent启动过程中被加载的 。是不是被加载过后的类再进行增强就会冲突呢?接着往下看 。
记一次多个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过程 。

经验总结扩展阅读