如何在OpenJ9場景下使用Arthas

騎牛上青山發表於2023-05-20

Alibaba開源的Arthas是一個非常有名的Java診斷工具,他可以解析JVM的執行資源佔用,執行狀況,可以檢視類的載入過程,使用的類載入器等等。但是比較可惜的是,他沒有對於OpenJ9做出額外的支援,因此當你的JVM選擇OpenJ9後,使用arthas可能會存在一定問題。本文將從我的親身使用出發,看看OpenJ9在使用Arthas時會遇到哪些問題?


dashboard

dashboard是arthas的指令之一,該指令用於展示當前系統的實時皮膚。但是在實際使用中會發現,如果OpenJ9中啟用-Xgcpolicy:balanced的gc策略,會報如下的錯誤:

[arthas@73192]$ dashboard
process dashboard failed: init argument cannot be less than -1

檢視Arthas的日誌檔案,檔案中有更加詳細的日誌:

java.lang.IllegalArgumentException: init argument cannot be less than -1
        at java.lang.management.MemoryUsage.<init>(MemoryUsage.java:94)
        at com.ibm.java.lang.management.internal.MemoryPoolMXBeanImpl.getUsageImpl(Native Method)
        at com.ibm.java.lang.management.internal.MemoryPoolMXBeanImpl.getUsage(MemoryPoolMXBeanImpl.java:235)
        at com.taobao.arthas.core.command.monitor200.MemoryCommand.getUsage(MemoryCommand.java:82)
        at com.taobao.arthas.core.command.monitor200.MemoryCommand.memoryInfo(MemoryCommand.java:52)
        at com.taobao.arthas.core.command.monitor200.DashboardCommand$DashboardTimerTask.run(DashboardCommand.java:245)
        at java.util.TimerThread.mainLoop(Timer.java:555)
        at java.util.TimerThread.run(Timer.java:505)

本來以為是Arthas對於OpenJ9不相容導致的,但是經過一番研究發現事情並不是那麼簡單。

寫一個最簡單的程式來進行驗證:

public static void main(String[] args) {
    List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();

    for (MemoryPoolMXBean memoryPoolMXBean : memoryPoolMXBeans) {
        memoryPoolMXBean.getUsage();
    }
}

這麼簡單一個demo居然在OpenJ9 balanced gc策略下丟擲了異常:

Exception in thread "main" java.lang.IllegalArgumentException: init argument cannot be less than -1
    at java.management/java.lang.management.MemoryUsage.<init>(MemoryUsage.java:94)
    at java.management/com.ibm.java.lang.management.internal.MemoryPoolMXBeanImpl.getUsageImpl(Native Method)
    at java.management/com.ibm.java.lang.management.internal.MemoryPoolMXBeanImpl.getUsage(MemoryPoolMXBeanImpl.java:235)
    at org.example.openj9.Test.main(Test.java:12)

經過測試驗證,確認了這個是OpenJ9的bug,錯怪Arthas了!

經過和OpenJ9社群的溝通,發現這個bug會在空閒的region數小於Eden region數時產生,此時的"reserved size"會小於0,並導致報錯。遺憾的是,此bug目前還未修復,預計將在後續的7月份的版本中進行修復併發布。

trace

使用trace可以排查具體的類的載入時長,排查執行慢的原因。不過在OpenJ9場景下使用trace會出現如下報錯:

[arthas@22508]$ trace org.springframework* * '#cost > 1000'
Affect(class count: 4180 , method count: 33388) cost in 28438 ms, listenerId: 1
Enhance error! exception: java.lang.VerifyError
error happens when enhancing class: null, check arthas log: /Users/logs/arthas/arthas.log

檢視日誌發現如下報錯:

java.lang.VerifyError: null
        at sun.instrument.InstrumentationImpl.retransformClasses0(Native Method)
        at sun.instrument.InstrumentationImpl.retransformClasses(InstrumentationImpl.java:156)
        at com.taobao.arthas.core.advisor.Enhancer.enhance(Enhancer.java:446)
        at com.taobao.arthas.core.command.monitor200.EnhancerCommand.enhance(EnhancerCommand.java:162)
        at com.taobao.arthas.core.command.monitor200.EnhancerCommand.process(EnhancerCommand.java:109)
        at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.process(AnnotatedCommandImpl.java:82)
        at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl.access$100(AnnotatedCommandImpl.java:18)
        at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:111)
        at com.taobao.arthas.core.shell.command.impl.AnnotatedCommandImpl$ProcessHandler.handle(AnnotatedCommandImpl.java:108)
        at com.taobao.arthas.core.shell.system.impl.ProcessImpl$CommandProcessTask.run(ProcessImpl.java:385)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:826)

這讓我百思不得其解,沒辦法只好求助Arthas社群,可是得到的結果就是Arthas不支援OpenJ9。沒辦法,既然不支援,那麼我們就換種辦法來排查問題。

火焰圖

Arthas火焰圖是基於開源專案async-profiler實現的,async-profiler使用C++實現。支援生成應用熱點的火焰圖。其也可以用於排查應用執行慢的問題。他本質上是透過不斷的取樣,然後把收集到的取樣結果生成火焰圖。

然而在實際的使用中,會發現火焰圖也不支援OpenJ9。

沒辦法,又又又只能去社群尋找答案。最終經過排查,發現async-profiler是做了OpenJ9的支援的,不過,這些支援在2.7+版本後才陸續合併入主分支,而Arthas的最新版本引用的是2.6.x的async-profiler,因此不支援OpenJ9也是情理之中了。

於是我們只好自己動手豐衣足食。好在Arthas整合async-profiler的方法非常粗暴,是直接使用的so檔案,於是我們直接替換Arthas的async-profiler目錄下的幾個so檔案,將之替換為最新版本的,果然火焰圖能力就能夠正常使用了。

後續在提了Issue之後,Arthas社群近期反應已經將async-profiler升級至了高版本,因此只要下載最新版本就能讓OpenJ9也可以使用Arthas的火焰圖能力了。

總結

OpenJ9雖然有其優勢,但是在實際中的使用者遠遠不如HotSpot,因此在各個開源專案中的支援還遠遠不夠。在折騰Arthas的過程中遇到了無數的坑,而且很多還是無法簡單解決的,本文只是簡單選取幾個遇到的典型問題,希望能夠有相同需求的朋友們能夠一起來 踩坑 探索。