1. 程式人生 > >6到飛起的Java診斷工具Arthas,你用過嗎?

6到飛起的Java診斷工具Arthas,你用過嗎?

記得前段時間遇到了一個頁面載入過長的問題,當時就想排查下在哪一步消耗的時間比較長,由於是線上問題,第一反應就是有沒有什麼辦法可以無侵入式的查詢呼叫鏈路耗時呢?

這時 Arthas 走進了我的眼簾,併成功幫我定位到了問題,就是這樣引起了我對 Arthas 的興趣,於是花了點時間對 Arthas 作了一個瞭解。

什麼是 Arthas

摘錄一段官方 Github 上的簡介


Arthas 基於哪些工具開發而來

greys-anatomy: Arthas程式碼基於Greys二次開發而來,非常感謝Greys之前所有的工作,以及Greys原作者對Arthas提出的意見和建議!

termd: Arthas的命令列實現基於termd開發,是一款優秀的命令列程式開發框架,感謝termd提供了優秀的框架。

crash: Arthas的文字渲染功能基於crash中的文字渲染功能開發,可以從這裡看到原始碼,感謝crash在這方面所做的優秀工作。

cli: Arthas的命令列介面基於vert.x提供的cli庫進行開發,感謝vert.x在這方面做的優秀工作。

compiler: Arthas裡的記憶體編繹器程式碼來源

Apache Commons Net: Arthas裡的Telnet Client程式碼來源

JavaAgent:執行在 main方法之前的攔截器,它內定的方法名叫 premain ,也就是說先執行 premain 方法然後再執行 main 方法

ASM:一個通用的Java位元組碼操作和分析框架。它可以用於修改現有的類或直接以二進位制形式動態生成類。ASM提供了一些常見的位元組碼轉換和分析演算法,可以從它們構建定製的複雜轉換和程式碼分析工具。ASM提供了與其他Java位元組碼框架類似的功能,但是主要關注效能。因為它被設計和實現得儘可能小和快,所以非常適合在動態系統中使用(當然也可以以靜態方式使用,例如在編譯器中)

工程目錄

arthas-agent:基於JavaAgent技術的代理

bin:一些啟動指令碼

arthas-boot:Java版本的一鍵安裝啟動指令碼

arthas-client:telnet client程式碼

arthas-common:一些共用的工具類和列舉類

arthas-core:核心庫,各種arthas命令的互動和實現

arthas-demo:示例程式碼

arthas-memorycompiler:記憶體編繹器程式碼

arthas-packaging:maven打包相關的

arthas-site:arthas站點

arthas-spy:編織到目標類中的各個切面

static:靜態資源

arthas-testcase:測試

整體流程

首先我們先放出一張整體巨集觀的模組呼叫圖,下面我們會按照整個 Arthas 啟動流程逐步分析,紅色部分本篇文章將不涉及,會在後續文章中單獨分析

啟動方式介紹

 

 

使用 arthas-boot 啟動(推薦)

下載 arthas-boot.jar,然後用 java -jar 的方式啟動:

可以加 -h 引數,列印幫助資訊:

如果下載速度比較慢,可以使用aliyun的映象:

                                   java -jar arthas-boot.jar --repo-mirror aliyun --use-http

使用 as.sh 指令碼啟動

Arthas 支援在 Linux/Unix/Mac 等平臺上一鍵安裝,請複製以下內容,並貼上到命令列中,敲回車執行即可:

上述命令會下載啟動指令碼檔案 as.sh 到當前目錄,你可以放在任何地方或將其加入到 $PATH 中。

直接在shell下面執行./as.sh,就會進入互動介面。

也可以執行./as.sh -h來獲取更多引數資訊。

Arthas 是如何啟動的

既然官方推薦用 arthas-boot 啟動,那下面我們就一起來看下 arthas-boot 是如何啟動的。

首先我們在 arthas-boot 的 pom 檔案中找到啟動類:

從pom檔案中,我們可以發現arthas-boot的啟動類為com.taobao.arthas.boot.Bootstrap,下面我們就去看看 Bootstrap 是如何啟動 arthas 的,有興趣的同學也可以自行看下另外一種啟動方式as.sh。

歸然將整個啟動的過程全部通過註釋在程式碼中體現出來了,所以:

 

 

 

 

 

 

 

到此,Arthas 的啟動流程就結束了,在這其中,我們發現了兩個關鍵的 jar 包,arthas-core 和 arthas-agent,那麼這兩個jar又做了什麼事情呢,咱們繼續往下走,想要了解這兩個jar包的作用,首先我們要先普及一個知識點——Java探針。

Java探針

Java探針主要涉及兩個知識點:

JavaAgent

JavaAgent 是一種能夠在不影響正常編譯的情況下,修改位元組碼的技術。java作為一種強型別的語言,不通過編譯就不能能夠進行jar包的生成。

有了 JavaAgent 技術,就可以在位元組碼這個層面對類和方法進行修改。也可以把 JavaAgent 理解成一種程式碼注入的方式,但是這種注入比起 Spring的 AOP 更加的優美。

從JDK6開始,有兩種代理方式:

        ·     通過命令列(-javaagent)的形式在應用程式啟動前處理(premain方式)

        ·     在應用程式啟動後的某個時機處理(agentmain方式)

ASM位元組碼

ASM 是一個通用的 Java 位元組碼操作和分析框架,它可以用於修改現有類或直接以二進位制形式動態生成類。

ASM 提供了一些常見的位元組碼轉換和分析演算法,可以從中構建自定義複雜轉換和程式碼分析工具。

ASM 提供與其他Java位元組碼框架類似的功能,但專注於效能。因為它的設計和實現儘可能小而且快,所以它非常適合在動態系統中使用(但當然也可以以靜態方式使用,例如在編譯器中)。

ASM 用於許多專案,包括:

        ·     OpenJDK,生成lambda呼叫站點,以及Nashorn編譯器

        ·     Groovy 編譯器和 Kotlin 編譯器

        ·     Cobertura 和 Jacoco,為了衡量程式碼覆蓋率,儀器類

        ·     CGLIB,用於動態生成代理類(用於其他專案,如Mockito和EasyMock)

        ·     Gradle,在執行時生成一些類

明白了這兩個知識點後,我們一起來看下 Arthas 中的 JavaAgent——arthas-agent

Arthas-Agent

首先我們從Pom檔案看起,找到premain和agentmain

從這裡我們很清楚地看到了 premain 和 agentmain 的方法被放在了com.taobao.arthas.agent.AgentBootstrap中。

那麼接下來我們就走進AgentBootstrap類中,瞭解下它的實現。

在AgentBootstrap類中,我們很快發現了這兩個方法

這兩個方法都同時指向當前類中的main方法,並傳遞了兩個引數,下面我們先對著兩個引數做個解讀

        ·     String args

這個引數是我們在 arthas-boot.jar 中啟動 arthas-core.jar 時傳入的引數

        ·     Instrumentation inst

java.lang.instrument.Instrumentation 例項,由 JVM 自動傳入,集中了幾乎所有功能方法,如:類操作、classpath 操作等

瞭解了這兩個引數以後,我們走進main方法看下實現

這裡主要重點講下上面圈出來的兩點,首先我們來看下第一段的程式碼。

第一步先將我們的arthas-spy.jar新增到 BootstrapClassLoader 中,在 Java Instrumention 的實現中,這行程式碼應該是很常見的。為什麼要這樣做呢?

在Java中,Java類載入器分為 BootstrapClassLoader、ExtensionClassLoader和SystemClassLoader。

BootstrapClassLoader 主要載入的是JVM自身需要的類,由於雙親委派機制的存在,越基礎的類由越上層的載入器進行載入,因此,如果需要在由 BootstrapClassLoader 載入的類的方法中呼叫由 SystemClassLoader 載入的arthas-spy.jar,這違反了雙親委派機制。

而arthas-spy.jar新增到 BootstrapClassLoader 的 classpath 中,由 BootstrapClassLoader載入,就解決了這個問題。

initSpy這個方法則使用ArthasClassloader載入com.taobao.arthas.core.advisor.AdviceWeaver類(在後續文章中會詳細解讀),並將其中的methodOnBegin、methodOnReturnEnd、methodOnThrowingEnd等方法取出,並賦給Spy類。後面在通過ASM做類增強的時候,Spy就是連線業務類和Arthas類的橋樑。

接著我們看下第二段藍色框中的程式碼。這裡面主要是做了一些服務端啟動的事情。

這段程式碼中,主要通過反射的手段,呼叫了ArthasBootstrap類中的bind方法來啟動 Arthas 服務端,接下來我們就一起來看下 Arthas 服務端啟動的原始碼。

Arthas服務端啟動

廢話不多說,先上程式碼。

這段程式碼主要是圍繞 ShellServer 做一些配置,並呼叫listen方法啟動監聽

在listen方法中,主要是根據之前註冊的TermServer來逐個啟動,這裡以TelnetTermServer為例講解,接下來看下TelnetTermServer中的listen方法。

我們跟蹤下start程式碼,發現最後呼叫的是NettyTelnetBootstrap的start方法。

主要是通過netty來啟動網路服務。下面我們看下對輸入的處理類TermServerTermHandler。

主要是通過呼叫shellServer的handleTerm方法。

這裡的 session 就是客戶端的連線,而readline方法就是用來處理使用者的輸入的。

至於每個命令是如何工作的,且聽下回分解

總結

arthas中涉及到的知識點很多的瞭解

        ·     netty

        ·     termd

        ·     cli

        ·     asm

        ·     JavaAgent

大家如果感興趣的話,可以花點時間研究下,相信這些框架會讓大家受益匪淺。

歡迎工作一到五年的Java工程師朋友們加入我的個人粉絲群Java填坑之路:789337293

群內提供免費的Java架構學習資料(裡面有高可用、高併發、高效能及分散式、Jvm效能調優、Spring原始碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的