記一次多個Java Agent同時使用的類增強沖突問題及分析

摘要:Java Agent技術常被用于加載class文件之前進行攔截并修改字節碼,以實現對Java應用的無侵入式增強 。
本文分享自華為云社區《記一次多個JavaAgent同時使用的類增強沖突問題及分析》,作者:Vansittart 。
問題背景Java Agent技術常被用于加載class文件之前進行攔截并修改字節碼,以實現對Java應用的無侵入式增強 。Sermant是致力于服務治理領域的開源Java Agent框架項目 。某客戶在集成Sermant之前已集成了兩套Java Agent:用于業務能力增強的自研Java Agent和用于鏈路采集的SkyWalking 。該客戶單獨掛載自研Java Agent插件包時,字節碼增強可以按照預期生效 。后期引入開源SkyWalking并同時將自研Java Agent插件包和SkyWalking通過-javaagent啟動參數掛載至業務應用中 。使用過程中發現 , 兩者的加載順序會對預期的攔截點增強生效與否有直接影響 。為什么會產生這種現象?該客戶求助Sermant社區尋求解決多個JavaAgent的增強沖突問題,以避免類似典型問題再次出現以及順利集成Sermant用于業務的服務治理 。
筆者嘗試從字節碼增強的底層邏輯的角度來分析該問題的癥結 。
掛載多個JavaAgent的增強沖突問題引入SkyWalking的初衷,是希望自研JavaAgent對業務的增強和SkyWalking的鏈路追蹤能力都能正常在業務應用上生效 。-javaagent參數是支持多次執行的,所以因此在啟動應用時在JAVA_TOOL_OPTIONS中加上了-javaagent:/xxx/my-agent.jar和-javaagent:/xxx/skywalking-agent.jar參數 。
先加載自研JavaAgent后加載SkyWalking在測試時首先把自研JavaAgent放在前面 , SkyWalking放在后面, 即-javaagent:/xxx/my-agent.jar -javaagent:/xxx/SkyWalking-agent.jar 。應用啟動前執行的邏輯如下圖所示 。按照參數的配置順序,應該是自研JavaAgent先對業務應用的jar包中字節碼進行增強,然后再由SkyWalking進行增強,最后再執行業務應用的main()方法啟動應用 。
記一次多個Java Agent同時使用的類增強沖突問題及分析

文章插圖
然而啟動后發現日志中SkyWalking拋出java.lang.UnsupportedOperationException異常,該異常對應的目標類是com.google.common.eventbus.Dispatcher$LegacyAsyncDispatcher 。自研JavaAgent無異常拋出 。
ERROR 2022-09-27 15:32:09:546 main SkyWalkingAgent : index=0, batch=[class com.google.common.eventbus.Dispatcher$LegacyAsyncDispatcher], types=[class com.google.common.eventbus.Dispatcher$LegacyAsyncDispatcher]Caused by: java.lang.UnsupportedOperationException: class redefinition failed: attempted to change superclass or interfacesat sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:144)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)at java.lang.reflect.Method.invoke(Method.java:498)at org.apache.SkyWalking.apm.dependencies.net.bytebuddy.agent.builder.AgentBuilder$RedefinitionStrategy$Dispatcher$ForJava6CapableVm.retransformClasses(AgentBuilder.java:6910)... 12 more經過確認自研JavaAgent并沒有對這個類有過攔截和增強,而SkyWalking中的apm-guava-eventbus-plugin插件對該類進行了攔截和增強 。兩個JavaAgent并沒有同時增強同一個類,但是SkyWalking卻增強失敗了,有點令人費解 。初步猜測可能JavaAgent的加載順序有關,筆者調整了順序 , 再次進行了測試 。
先加載SkyWalking后加載自研JavaAgent調整后JAVA_TOOL_OPTIONS配置為-javaagent:/xxx/SkyWalking-agent.jar -javaagent:/xxx/my-agent.jar,應用啟動前執行的邏輯如下圖所示
記一次多個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啟動過程中被加載的 。是不是被加載過后的類再進行增強就會沖突呢?接著往下看 。

推薦閱讀