文章很長,且持續更新,建議收藏起來,慢慢讀!瘋狂創客圈總目錄 博客園版 爲您奉上珍貴的學習資源 :
免費贈送 :《尼恩Java面試寶典》 持續更新+ 史上最全 + 面試必備 2000頁+ 面試必備 + 大廠必備 +漲薪必備
免費贈送 :《尼恩技術聖經+高併發系列PDF》 ,幫你 實現技術自由,完成職業升級, 薪酬猛漲!加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷1)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷2)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 經典圖書:《Java高併發核心編程(卷3)加強版》 面試必備 + 大廠必備 +漲薪必備 加尼恩免費領
免費贈送 資源寶庫: Java 必備 百度網盤資源大合集 價值>10000元 加尼恩領取
阿里面試:Arthas原理和使用,大概說說吧?
尼恩說在前面
在40歲老架構師 尼恩的讀者交流羣(50+)中,最近有小夥伴拿到了一線互聯網企業如阿里、滴滴、極兔、有贊、希音、百度、網易、美團的面試資格,遇到很多很重要的面試題:
Arthas原理和使用,大概說下吧?
Arthas 也是大家定位和解決線上問題,非常常用的一個工具。
所以,這個題目如果答不上來, 說明平時沒怎麼解決過線上問題, 面試基本就掛。
所以,這道題目,非常重要。
這裏,尼恩把這道面試題以及參考答案,做了詳細的梳理。 大家可以收藏起來, 時不時看看,做到溫故而知新。
同時,也收入咱們的 《尼恩Java面試寶典PDF》V159版本,供後面的小夥伴參考,提升大家的 3高 架構、設計、開發水平。
《尼恩 架構筆記》《尼恩高併發三部曲》《尼恩Java面試寶典》的PDF,請到公衆號【技術自由圈】獲取
本文目錄
Arthas 在線排錯工具的巨大價值
Arthas
是 Alibaba 開源的 Java 診斷工具,深受開發者喜愛。
通常,本地開發環境無法訪問生產環境。如果在生產環境中遇到問題,則無法使用 IDE 遠程調試。
更糟糕的是,在生產環境中調試是不可接受的,因爲它會暫停所有線程,導致服務暫停。
Arthas 旨在解決這些問題。開發人員可以通過Arthas 在線解決生產問題。通過Arthas ,無需 JVM 重啓,無需代碼更改。 Arthas 作爲觀察者永遠不會暫停正在運行的線程。
Arthas 是一款線上監控診斷產品,通過全局視角實時查看應用 load、內存、gc、線程的狀態信息,並能在不修改應用代碼的情況下,對業務問題進行診斷,包括查看方法調用的出入參、異常,監測方法執行耗時,類加載信息等,大大提升線上問題排查效率。
Arthas中集成了大部分JDK工具的功能實現,因此,在線上情況時,可以通過它快速的幫助我們解決問題,如CPU佔用過高、線程阻塞、死鎖、代碼動態修改、方法執行緩慢、排查404
等。
當你遇到以下類似問題而束手無策時,Arthas可以幫助你解決:
- 這個類從哪個 jar 包加載的?爲什麼會報各種類相關的 Exception?
- 我改的代碼爲什麼沒有執行到?難道是我沒 commit?分支搞錯了?
- 遇到問題無法在線上 debug,難道只能通過加日誌再重新發布嗎?
- 線上遇到某個用戶的數據處理有問題,但線上同樣無法 debug,線下無法重現!
- 是否有一個全局視角來查看系統的運行狀況?
- 有什麼辦法可以監控到JVM的實時運行狀態?
- 怎麼快速定位應用的熱點,生成火焰圖?
- 怎樣直接從JVM內查找某個類的實例?
Arthas
支持JDK6+
,支持Linux/Mac/Winodws
,採用命令行交互模式,同時提供豐富的Tab
自動補全功能,進一步方便進行問題的定位和診斷。
阿里提供的在線的Arthas Terminal
學習方式(Arthas Tutorials (aliyun.com)),幫助大家快速上手。
arthas快速開始
方式一:使用 arthas-boot 啓動arthas
推薦使用arthas-boot
(推薦),下載arthas-boot.jar
,然後用java -jar
的方式啓動:
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar
打印幫助信息:
java -jar arthas-boot.jar -h
arthas-boot啓動過程中, 會下載一些依賴包,如果下載速度比較慢,可以使用aliyun的鏡像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
arthas-boot 完成依賴下載,並且啓動。
啓動之後,這時候,arthas會列出當前檢測到的java進程,
Arthas
會將本機中所有的Java進程查詢出來,類似於jps/ps
的作用:
上面的例子中, 有一個 java進程, Arthas 自己的進程
30162 Arthas.jar
如果你的機器中啓動了多個Java應用,此時會查詢出來一個應用列表。
要操作那個進程,我們可以根據前面的序號進行輸入。
輸入選擇自己要操作的Java應用,如上情況中,再輸入1
即可,並按回車鍵即可;
$ 1
最終,Arthas
成功啓動,接下來再通過Arthas
提供的指令進行操作即可:
方式二:使用 as.sh 腳本啓動arthas
Arthas 支持在 Linux/Unix/Mac 等平臺上一鍵安裝,有個專門的install.sh腳本
請複製以下內容,並粘貼到命令行中,敲 回車 執行即可:
curl -L https://arthas.aliyun.com/install.sh | sh
上述命令會下載啓動腳本文件 install.sh,並且下載 as.sh
到當前目錄,你可以放在任何地方或將其加入到 $PATH
中。
直接在shell下面執行./as.sh
,就會進入交互界面。
也可以執行./as.sh -h
來獲取更多參數信息。
arthas官方使用案例:
可以通過下面的方式自己動手實踐。
1、啓動 math-game
math-game是一個簡單的程序,每隔一秒生成一個隨機數,再執行質因數分解,並打印出分解結果。
math-game
源代碼:查看
curl -O https://arthas.aliyun.com/math-game.jar
java -jar math-game.jar
2、啓動 arthas
在命令行下面執行(使用和目標進程一致的用戶啓動,否則可能 attach 失敗):
curl -O https://arthas.aliyun.com/arthas-boot.jar
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
-
執行該程序的用戶需要和目標進程具有相同的權限。
比如以
admin
用戶來執行:sudo su admin && java -jar arthas-boot.jar
或sudo -u admin -EH java -jar arthas-boot.jar
。 -
如果 attach 不上目標進程,可以查看
~/logs/arthas/
目錄下的日誌。 -
如果下載速度比較慢,可以使用 aliyun 的鏡像:
java -jar arthas-boot.jar --repo-mirror aliyun --use-http
-
java -jar arthas-boot.jar -h
打印更多參數信息。
選擇應用 java 進程:
$ $ java -jar arthas-boot.jar
* [1]: 35542
[2]: 71560 math-game.jar
math-game
進程是第 2 個,則輸入 2,再輸入回車/enter
。
Arthas 會 attach 到目標進程上,並輸出日誌:
[INFO] Try to attach process 71560
[INFO] Attach process 71560 success.
[INFO] arthas-client connect 127.0.0.1 3658
,---. ,------. ,--------.,--. ,--. ,---. ,---.
/ O \ | .--. ''--. .--'| '--' | / O \ ' .-'
| .-. || '--'.' | | | .--. || .-. |`. `-.
| | | || |\ \ | | | | | || | | |.-' |
`--' `--'`--' '--' `--' `--' `--'`--' `--'`-----'
wiki: https://arthas.aliyun.com/doc
version: 3.0.5.20181127201536
pid: 71560
time: 2018-11-28 19:16:24
$
3、dashboard 展示當前進程的信息
輸入 dashboard,按回車/enter
,會展示當前進程的信息,按ctrl+c
可以中斷執行。
$ dashboard
ID NAME GROUP PRIORI STATE %CPU TIME INTERRU DAEMON
17 pool-2-thread-1 system 5 WAITIN 67 0:0 false false
27 Timer-for-arthas-dashb system 10 RUNNAB 32 0:0 false true
11 AsyncAppender-Worker-a system 9 WAITIN 0 0:0 false true
9 Attach Listener system 9 RUNNAB 0 0:0 false true
3 Finalizer system 8 WAITIN 0 0:0 false true
2 Reference Handler system 10 WAITIN 0 0:0 false true
4 Signal Dispatcher system 9 RUNNAB 0 0:0 false true
26 as-command-execute-dae system 10 TIMED_ 0 0:0 false true
13 job-timeout system 9 TIMED_ 0 0:0 false true
1 main main 5 TIMED_ 0 0:0 false false
14 nioEventLoopGroup-2-1 system 10 RUNNAB 0 0:0 false false
18 nioEventLoopGroup-2-2 system 10 RUNNAB 0 0:0 false false
23 nioEventLoopGroup-2-3 system 10 RUNNAB 0 0:0 false false
15 nioEventLoopGroup-3-1 system 10 RUNNAB 0 0:0 false false
Memory used total max usage GC
heap 32M 155M 1820M 1.77% gc.ps_scavenge.count 4
ps_eden_space 14M 65M 672M 2.21% gc.ps_scavenge.time(m 166
ps_survivor_space 4M 5M 5M s)
ps_old_gen 12M 85M 1365M 0.91% gc.ps_marksweep.count 0
nonheap 20M 23M -1 gc.ps_marksweep.time( 0
code_cache 3M 5M 240M 1.32% ms)
Runtime
os.name Mac OS X
os.version 10.13.4
java.version 1.8.0_162
java.home /Library/Java/JavaVir
tualMachines/jdk1.8.0
_162.jdk/Contents/Hom
e/jre
4、thread 命令Main Class
通過 thread 命令來獲取到math-game
進程的 Main Class
thread 1
會打印線程 ID 1 的棧,通常是 main 函數的線程。
$ thread 1 | grep 'main('
at demo.MathGame.main(MathGame.java:17)
得到了 Main Class 的全路徑名稱 demo.MathGame
5、jad命令 來反編譯 Main Class
jad 全路徑名稱, 進行源碼的反編譯
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/math-game.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
public static void main(String[] args) throws InterruptedException {
MathGame game = new MathGame();
do {
game.run();
TimeUnit.SECONDS.sleep(1L);
} while (true);
}
public void run() throws InterruptedException {
try {
int number = random.nextInt();
List<Integer> primeFactors = this.primeFactors(number);
MathGame.print(number, primeFactors);
}
.......
return result;
}
public List<Integer> primeFactors(int number) {
if (number < 2) {
++this.illegalArgumentCount;
throw new IllegalArgumentException("number is: " + number + ", need >= 2");
}
ArrayList<Integer> result = new ArrayList<Integer>();
int i = 2;
while (i <= number) {
if (number % i == 0) {
result.add(i);
number /= i;
i = 2;
continue;
}
++i;
}
return result;
}
}
Affect(row-cnt:1) cost in 970 ms.
6、watch
通過 watch命令來查看到指定函數的調用情況。
watch能觀察到的範圍爲:返回值
、拋出異常
、入參
,通過編寫 OGNL 表達式進行對應變量的查看。
例子,通過 watch命令來查看 demo.MathGame#primeFactors 函數的返回值:
$ watch demo.MathGame primeFactors returnObj
Press Ctrl+C to abort.
Affect(class-cnt:1 , method-cnt:1) cost in 107 ms.
ts=2018-11-28 19:22:30; [cost=1.715367ms] result=null
ts=2018-11-28 19:22:31; [cost=0.185203ms] result=null
ts=2018-11-28 19:22:32; [cost=19.012416ms] result=@ArrayList[
@Integer[5],
@Integer[47],
@Integer[2675531],
]
ts=2018-11-28 19:22:33; [cost=0.311395ms] result=@ArrayList[
@Integer[2],
@Integer[5],
@Integer[317],
@Integer[503],
@Integer[887],
]
ts=2018-11-28 19:22:34; [cost=10.136007ms] result=@ArrayList[
@Integer[2],
@Integer[2],
@Integer[3],
@Integer[3],
@Integer[31],
@Integer[717593],
]
ts=2018-11-28 19:22:35; [cost=29.969732ms] result=@ArrayList[
@Integer[5],
@Integer[29],
@Integer[7651739],
]
更多的功能可以查看進階教程在新窗口打開。
7、退出 arthas
如果只是退出當前的連接,可以用quit
或者exit
命令。Attach 到目標進程上的 arthas 還會繼續運行,端口會保持開放,下次連接時可以直接連接上。
如果想完全退出 arthas,可以執行stop
命令。
Arthas的核心命令詳解
Arthas
從最初的發佈開始,隨着後續社區的活躍性增強及用戶羣體的不斷壯大,指令也越發完善與豐富,至目前爲止提供了基礎命令、JVM命令、class命令以及字節碼增強命令等幾大類。
1、基礎命令
- base64 - base64 編碼轉換,和 linux 裏的 base64 命令類似
- cat - 打印文件內容,和 linux 裏的 cat 命令類似
- cls - 清空當前屏幕區域
- echo - 打印參數,和 linux 裏的 echo 命令類似
- grep - 匹配查找,和 linux 裏的 grep 命令類似
- help - 查看命令幫助信息
- history - 打印命令歷史
- keymap - Arthas 快捷鍵列表及自定義快捷鍵
- pwd - 返回當前的工作目錄,和 linux 命令類似
- quit - 退出當前 Arthas 客戶端,其他 Arthas 客戶端不受影響
- reset - 重置增強類,將被 Arthas 增強過的類全部還原,Arthas 服務端關閉時會重置所有增強過的類
- session - 查看當前會話的信息
- stop - 關閉 Arthas 服務端,所有 Arthas 客戶端全部退出
- tee - 複製標準輸入到標準輸出和指定的文件,和 linux 裏的 tee 命令類似
- version - 輸出當前目標 Java 進程所加載的 Arthas 版本號
2、類操作命令
2.1 sc 命令:
sc “Search-Class” 的簡寫,查看JVM已加載的類信息,這個命令能搜索出所有已經加載到 JVM 中的 Class 信息,
可選項如下:
class-pattern
:類名錶達式匹配(必填),如sc java.lang.String
。-E
:開啓正則表達式匹配,默認爲通配符匹配。-c
:指定class
的類加載器的哈希碼。-d
:顯示當前類的詳細信息,包含來源、聲明、類加載相關等信息。-f
:輸出當前類的屬性成員信息,與-d
一同使用。-x
:指定輸出靜態變量時屬性的遍歷深度,默認爲0
。-n
:具有詳細信息的匹配類的最大數量(默認爲100)。
提示
class-pattern 支持全限定名,支持兩種格式:
com.taobao.test.AAA,
也支持 com/taobao/test/AAA 這樣的格式,
這樣,我們從異常堆棧裏面把類名拷貝過來的時候,不需要在手動把
/
替換爲.
啦。
sc 命令 使用參考
- 模糊搜索
$ sc demo.*
demo.MathGame
Affect(row-cnt:1) cost in 55 ms.
- 打印類的詳細信息
$ sc -d demo.MathGame
class-info demo.MathGame
code-source /private/tmp/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
Affect(row-cnt:1) cost in 875 ms.
- 打印出類的 Field 信息
$ sc -d -f demo.MathGame
class-info demo.MathGame
code-source /private/tmp/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
fields modifierprivate,static
type java.util.Random
name random
value java.util.Random@522b4
08a
modifierprivate
type int
name illegalArgumentCount
Affect(row-cnt:1) cost in 19 ms.
2.2 sm 命令
“Search-Method” 的簡寫,這個命令能搜索出所有已經加載了 Class 信息的方法信息。
sm
命令只能看到由當前類所聲明 (declaring) 的方法,父類則無法看到。
sm
:查看已加載類的方法信息,可選項如下:
class-pattern
:類名錶達式匹配(必填),如sm java.lang.String
。-E
:開啓正則表達式匹配,默認爲通配符匹配。-d
:查看方法的詳細信息,配合方法名使用,如sm -d java.lang.String toString
。-c
:同sc -c
作用相同。-n
:同sc -h
作用相同。
參數說明
參數名稱 | 參數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
method-pattern | 方法名錶達式匹配 |
[d] | 展示每個方法的詳細信息 |
[E] | 開啓正則表達式匹配,默認爲通配符匹配 |
[c:] |
指定 class 的 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定執行表達式的 ClassLoader 的 class name |
[n:] |
具有詳細信息的匹配類的最大數量(默認爲 100) |
使用參考
$ sm java.lang.String
java.lang.String-><init>
java.lang.String->equals
java.lang.String->toString
java.lang.String->hashCode
java.lang.String->compareTo
java.lang.String->indexOf
java.lang.String->valueOf
.......
Affect(row-cnt:44) cost in 1342 ms.
$ sm -d java.lang.String toString
declaring-class java.lang.String
method-name toString
modifier public
annotation
parameters
return java.lang.String
exceptions
Affect(row-cnt:1) cost in 3 ms.
2.3 jad命令
jad
命令將 JVM 中實際運行的 class 的 byte code 反編譯成 java 代碼,便於你理解業務邏輯;如需批量下載指定包的目錄的 class 字節碼可以參考 dump。
-
在 Arthas Console 上,反編譯出來的源碼是帶語法高亮的,閱讀更方便
-
當然,反編譯出來的 java 代碼可能會存在語法錯誤,但不影響你進行閱讀理解
jad
:反編譯指定已加載類的源碼,可選項如下:
-c、-E
都與前面的作用相同,舉幾個案例演示用法。jad --source-only java.lang.String
:只顯示反編譯後的Java源碼。jad java.lang.String
:反編譯指定類。jad java.lang.String toString
:反編譯指定類的某個方法。
參數說明參數說明
參數名稱 | 參數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
[c:] |
類所屬 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定執行表達式的 ClassLoader 的 class name |
[E] | 開啓正則表達式匹配,默認爲通配符匹配 |
jad 全路徑名稱, 進行源碼的反編譯
前面有例子
$ jad demo.MathGame
ClassLoader:
+-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
Location:
/tmp/math-game.jar
/*
* Decompiled with CFR 0_132.
*/
package demo;
...
public class MathGame {
private static Random random = new Random();
private int illegalArgumentCount = 0;
.....
}
Affect(row-cnt:1) cost in 970 ms.
2.4 mc命令
Memory Compiler/內存編譯器,編譯.java
文件生成.class
。
mc
:內存編譯器,編譯.java
源文件爲.class
類文件,可選項如下:
-c
:指定類加載器(以哈希碼的方式指定)。-d
:指定編譯後的類文件輸出位置。
2.6 retransform命令
加載外部的.class
文件,retransform (再轉化;插樁) jvm 已加載的類, 比如在線替換 類的方法。
retransform
:起到 熱部署的作用,用於線上替換類方法。
注意點:
- ①重新替換JVM中被加載的類時,不能新增方法或屬性。
- ②正在執行的方法不能替換。
retransform 使用參考
retransform /tmp/Test.class
retransform -l
retransform -d 1 # delete retransform entry
retransform --deleteAll # delete all retransform entries
retransform --classPattern demo.* # triger retransform classes
retransform -c 327a647b /tmp/Test.class /tmp/Test\$Inner.class
retransform --classLoaderClass 'sun.misc.Launcher$AppClassLoader' /tmp/Test.class
2.7 dump命令
dump`:導出已加載類的字節碼數據到指定目錄
dump 命令將 JVM 中實際運行的 class 的 byte code dump 到指定目錄,適用場景批量下載指定包目錄的 class 字節碼;如需反編譯單一類、實時查看類信息,可參考 jad。
dump命令可選項如下:
-c、-E
作用與之前的相同。-d
:指定輸出的路徑,如dump -d /usr/data/byteCode java.lang.String
。
參數說明
參數名稱 | 參數說明 |
---|---|
class-pattern | 類名錶達式匹配 |
[c:] |
類所屬 ClassLoader 的 hashcode |
[classLoaderClass:] |
指定執行表達式的 ClassLoader 的 class name |
[d:] |
設置類文件的目標目錄 |
[E] | 開啓正則表達式匹配,默認爲通配符匹配 |
使用參考
$ dump java.lang.String
HASHCODE CLASSLOADER LOCATION
null /Users/admin/logs/arthas/classdump/java/lang/String.class
Affect(row-cnt:1) cost in 119 ms.
2.8 classloader命令
classloader
:查類加載器的繼承樹,urls,類加載信息,
classloader 命令將 JVM 中所有的 classloader 的信息統計出來,並可以展示繼承樹,urls 等。
可以讓指定的 classloader 去 getResources,打印出所有查找到的 resources 的 url。對於ResourceNotFoundException比較有用。
classloader 可選項如下:
-a
:顯示所有類加載器加載的所有類。-c
:查看指定的類加載器的加載路徑,如classloader -c 14ae5a5
。-l
:統計每個類加載器的加載信息。-r
:查找某個的資源路徑,配合-c
使用,如classloader -c 33909752 -r java/lang/String.class
。-t
:以樹結構列出每個類加載器之間的父子關係。-u
:顯示類加載器的url統計信息,如加載總數、父子關係、加載範圍等。-i
:查看每種類加載器的實例數量及其加載總量。
使用參考
按類加載類型查看統計信息
$ classloader
name numberOfInstances loadedCountTotal
com.taobao.arthas.agent.ArthasClassloader 1 2115
BootstrapClassLoader 1 1861
sun.reflect.DelegatingClassLoader 5 5
sun.misc.Launcher$AppClassLoader 1 4
sun.misc.Launcher$ExtClassLoader 1 1
Affect(row-cnt:5) cost in 3 ms.
按類加載實例查看統計信息
$ classloader -l
name loadedCount hash parent
BootstrapClassLoader 1861 null null
com.taobao.arthas.agent.ArthasClassloader@68b31f0a 2115 68b31f0a sun.misc.Launcher$ExtClassLoader@66350f69
sun.misc.Launcher$AppClassLoader@3d4eac69 4 3d4eac69 sun.misc.Launcher$ExtClassLoader@66350f69
sun.misc.Launcher$ExtClassLoader@66350f69 1 66350f69 null
Affect(row-cnt:4) cost in 2 ms.
查看 ClassLoader 的繼承樹
$ classloader -t
+-BootstrapClassLoader
+-sun.misc.Launcher$ExtClassLoader@66350f69
+-com.taobao.arthas.agent.ArthasClassloader@68b31f0a
+-sun.misc.Launcher$AppClassLoader@3d4eac69
Affect(row-cnt:4) cost in 3 ms.
查看 URLClassLoader 實際的 urls
$ classloader -c 3d4eac69
file:/private/tmp/math-game.jar
file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar
Affect(row-cnt:9) cost in 3 ms.
注意 hashcode 是變化的,需要先查看當前的 ClassLoader 信息,提取對應 ClassLoader 的 hashcode。
對於只有唯一實例的 ClassLoader 可以通過 class name 指定,使用起來更加方便:
$ classloader --classLoaderClass sun.misc.Launcher$AppClassLoader
file:/private/tmp/math-game.jar
file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar
Affect(row-cnt:9) cost in 3 ms.
使用 ClassLoader 去查找 resource
$ classloader -c 3d4eac69 -r META-INF/MANIFEST.MF
jar:file:/System/Library/Java/Extensions/MRJToolkit.jar!/META-INF/MANIFEST.MF
jar:file:/private/tmp/math-game.jar!/META-INF/MANIFEST.MF
jar:file:/Users/hengyunabc/.arthas/lib/3.0.5/arthas/arthas-agent.jar!/META-INF/MANIFEST.MF
也可以嘗試查找類的 class 文件:
$ classloader -c 1b6d3586 -r java/lang/String.class
jar:file:/Library/Java/JavaVirtualMachines/jdk1.8.0_60.jdk/Contents/Home/jre/lib/rt.jar!/java/lang/String.class
使用 ClassLoader 去加載類
$ classloader -c 3d4eac69 --load demo.MathGame
load class success.
class-info demo.MathGame
code-source /private/tmp/math-game.jar
name demo.MathGame
isInterface false
isAnnotation false
isEnum false
isAnonymousClass false
isArray false
isLocalClass false
isMemberClass false
isPrimitive false
isSynthetic false
simple-name MathGame
modifier public
annotation
interfaces
super-class +-java.lang.Object
class-loader +-sun.misc.Launcher$AppClassLoader@3d4eac69
+-sun.misc.Launcher$ExtClassLoader@66350f69
classLoaderHash 3d4eac69
3、JVM操作的命令
3.1 dashboard命令
dashboard:當前系統的實時數據面板,資源監控儀表盤,
dashboard 包含線程、內存、GC、運行環境等信息, 按 ctrl+c 退出。
dashboard
可選項如下:
-i
:刷新實時數據的間隔時間,默認爲5000ms
。-n
:刷新實時數據的次數,默認爲一直持續刷新,按ctrl+c
退出。
參數說明
參數名稱 | 參數說明 |
---|---|
[i:] | 刷新實時數據的時間間隔 (ms),默認 5000ms |
[n:] | 刷新實時數據的次數 |
使用參考
$ dashboard
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTE DAEMON
-1 C2 CompilerThread0 - -1 - 1.55 0.077 0:8.684 false true
53 Timer-for-arthas-dashboard-07b system 5 RUNNABLE 0.08 0.004 0:0.004 false true
22 scheduling-1 main 5 TIMED_WAI 0.06 0.003 0:0.287 false false
-1 C1 CompilerThread0 - -1 - 0.06 0.003 0:2.171 false true
-1 VM Periodic Task Thread - -1 - 0.03 0.001 0:0.092 false true
49 arthas-NettyHttpTelnetBootstra system 5 RUNNABLE 0.02 0.001 0:0.156 false true
16 Catalina-utility-1 main 1 TIMED_WAI 0.0 0.000 0:0.029 false false
-1 G1 Young RemSet Sampling - -1 - 0.0 0.000 0:0.019 false true
17 Catalina-utility-2 main 1 WAITING 0.0 0.000 0:0.025 false false
34 http-nio-8080-ClientPoller main 5 RUNNABLE 0.0 0.000 0:0.016 false true
23 http-nio-8080-BlockPoller main 5 RUNNABLE 0.0 0.000 0:0.011 false true
-1 VM Thread - -1 - 0.0 0.000 0:0.032 false true
-1 Service Thread - -1 - 0.0 0.000 0:0.006 false true
-1 GC Thread#5 - -1 - 0.0 0.000 0:0.043 false true
Memory used total max usage GC
heap 36M 70M 4096M 0.90% gc.g1_young_generation.count 12
g1_eden_space 6M 18M -1 33.33% 86
g1_old_gen 30M 50M 4096M 0.74% gc.g1_old_generation.count 0
g1_survivor_space 491K 2048K -1 24.01% gc.g1_old_generation.time(ms) 0
nonheap 66M 69M -1 96.56%
codeheap_'non-nmethods' 1M 2M 5M 22.39%
metaspace 46M 47M -1 98.01%
Runtime
os.name Mac OS X
os.version 10.15.4
java.version 15
java.home /Library/Java/JavaVirtualMachines/jdk-15.jdk/Contents/Home
systemload.average 10.68
processors 8
uptime 272s
數據說明
- ID: Java 級別的線程 ID,注意,這個 ID 不能跟 jstack 中的 nativeID 一一對應。
- NAME: 線程名
- GROUP: 線程組名
- PRIORITY: 線程優先級, 1~10 之間的數字,越大表示優先級越高
- STATE: 線程的狀態
- CPU%: 線程的 cpu 使用率。比如採樣間隔 1000ms,某個線程的增量 cpu 時間爲 100ms,則 cpu 使用率=100/1000=10%
- DELTA_TIME: 上次採樣之後線程運行增量 CPU 時間,數據格式爲
秒
- TIME: 線程運行總 CPU 時間,數據格式爲
分:秒
- INTERRUPTED: 線程當前的中斷位狀態
- DAEMON: 是否是 daemon 線程
JVM 內部線程
Java 8 之後支持獲取 JVM 內部線程 CPU 時間,這些線程只有名稱和 CPU 時間,沒有 ID 及狀態等信息(顯示 ID 爲-1)。
JVM 內部線程包括下面幾種:
- JIT 編譯線程: 如
C1 CompilerThread0
,C2 CompilerThread0
- GC 線程: 如
GC Thread0
,G1 Young RemSet Sampling
- 其它內部線程: 如
VM Periodic Task Thread
,VM Thread
,Service Thread
通過內部線程可以觀測到 JVM 活動,如 GC、JIT 編譯等佔用 CPU 情況,方便了解 JVM 整體運行狀況。
- 當 JVM 堆(heap)/元數據(metaspace)空間不足或 OOM 時,可以看到 GC 線程的 CPU 佔用率明顯高於其他的線程。
- 當執行
trace/watch/tt/redefine
等命令後,可以看到 JIT 線程活動變得更頻繁。因爲 JVM 熱更新 class 字節碼時, 清除了此 class 相關的 JIT 編譯結果,需要重新編譯。
3.2 thread命令
查看當前線程信息,查看線程的堆棧
thread
:查看當前線程的堆棧信息,可選項如下:
-n
:顯示最活躍的n
條線程信息,如thread -n 5
。-i
:指定活躍性統計的採樣間隔時間,如thread -i 5000
。-b
:自動檢測出應用中當前阻塞其他線程的線程。--state
:查詢目前程序中處於指定狀態的線程,如thread --state BLOCKED
。id
:查看某個線程的詳細信息,如thread 21
。
參數列表
參數名稱 | 參數說明 |
---|---|
id | 線程 id |
[n:] | 指定最忙的前 N 個線程並打印堆棧 |
[b] | 找出當前阻塞其他線程的線程 |
[i <value> ] |
指定 cpu 使用率統計的採樣間隔,單位爲毫秒,默認值爲 200 |
[--all] | 顯示所有匹配的線程 |
cpu 使用率是如何統計出來的?
這裏的 cpu 使用率與 linux 命令top -H -p <pid>
的線程%CPU
類似,一段採樣間隔時間內,當前 JVM 裏各個線程的增量 cpu 時間與採樣間隔時間的比例。
工作原理說明:
- 首先第一次採樣,獲取所有線程的 CPU 時間(調用的是
java.lang.management.ThreadMXBean#getThreadCpuTime()
及sun.management.HotspotThreadMBean.getInternalThreadCpuTimes()
接口) - 然後睡眠等待一個間隔時間(默認爲 200ms,可以通過
-i
指定間隔時間) - 再次第二次採樣,獲取所有線程的 CPU 時間,對比兩次採樣數據,計算出每個線程的增量 CPU 時間
- 線程 CPU 使用率 = 線程增量 CPU 時間 / 採樣間隔時間 * 100%
注意
注意: 這個統計也會產生一定的開銷(JDK 這個接口本身開銷比較大),因此會看到 as 的線程佔用一定的百分比,爲了降低統計自身的開銷帶來的影響,可以把採樣間隔拉長一些,比如 5000 毫秒。
eg:支持一鍵展示當前最忙的前 N 個線程並打印堆棧:
$ thread -n 3
"C1 CompilerThread0" [Internal] cpuUsage=1.63% deltaTime=3ms time=1170ms
"arthas-command-execute" Id=23 cpuUsage=0.11% deltaTime=0ms time=401ms RUNNABLE
at [email protected]/sun.management.ThreadImpl.dumpThreads0(Native Method)
at [email protected]/sun.management.ThreadImpl.getThreadInfo(ThreadImpl.java:466)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.processTopBusyThreads(ThreadCommand.java:199)
at com.taobao.arthas.core.command.monitor200.ThreadCommand.process(ThreadCommand.java:122)
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 [email protected]/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
at [email protected]/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at [email protected]/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
at [email protected]/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at [email protected]/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at [email protected]/java.lang.Thread.run(Thread.java:834)
"VM Periodic Task Thread" [Internal] cpuUsage=0.07% deltaTime=0ms time=584ms
- 沒有線程 ID,包含
[Internal]
表示爲 JVM 內部線程,參考dashboard命令的介紹。 cpuUsage
爲採樣間隔時間內線程的 CPU 使用率,與dashboard命令的數據一致。deltaTime
爲採樣間隔時間內線程的增量 CPU 時間,小於 1ms 時被取整顯示爲 0ms。time
線程運行總 CPU 時間。
注意:線程棧爲第二採樣結束時獲取,不能表明採樣間隔時間內該線程都是在處理相同的任務。建議間隔時間不要太長,可能間隔時間越大越不準確。 可以根據具體情況嘗試指定不同的間隔時間,觀察輸出結果。
thread --all, 顯示所有匹配的線程
顯示所有匹配線程信息,有時需要獲取全部 JVM 的線程數據進行分析
3.3 jvm命令
jvm
:查看JVM信息,包含線程/內存/OS/內存結構/編譯/類加載/運行環境等信息。
使用參考,請參見 官方文檔
3.4 sysprop命令
查看當前 JVM 的系統屬性(System Property
)
如sysprop java.home
。
使用參考,請參見 官方文檔
3.5 sysenv命令
sysenv
:,查看當前JVM的環境參數。
使用參考,請參見 官方文檔
3.6 vmoption命令
vmoption
:查看或修改JVM的運行時參數,如:
vmoption PrintGC
:查看PrintGC
是否開啓。vmoption PrintGC true
:更改PrintGC
參數。
使用參考,請參見 官方文檔
vmoption | arthas (aliyun.com)
3.7 getstatic命令
getstatic
:查看類的靜態屬性,用法:getstatic class_nmae field_name
。
使用參考,請參見 官方文檔
getstatic | arthas (aliyun.com)
3.8 ognl命令
ognl
:執行ognl表達式
使用參考,請參見 官方文檔
3.9 heapdump命令
heapdump
:類似於jmap
工具的堆dump
功能,使用方式:
heapdump /usr/data/dump/heap.hprof
:導出堆快照到指定文件。heapdump --live /usr/data/dump/heap.hprof
:只導出存活對象的快照。
使用參考,請參見 官方文檔
heapdump | arthas (aliyun.com)
3.10 mbean命令
mbean
:查看Mbean
的信息
使用參考,請參見 官方文檔
3.11 memory命令
memory
:查看JVM的內存劃分、內存結構以及佔用率。
使用參考,請參見 官方文檔
4、字節碼增強命令
4.1 watch命令
讓你能方便的觀察到指定函數的調用情況。能觀察到的範圍爲:返回值
、拋出異常
、入參
,通過編寫 OGNL 表達式進行對應變量的查看。
使用參考,請參見 官方文檔
watch
:觀測指定方法的執行情況,可選項如下:
-b
:在方法調用之前觀測。-s
:在方法成功執行後觀測。-e
:在方法異常執行後觀測。-f
:在方法結束後進行觀測(默認)。-n
:指定觀測的次數。- 使用示例:
watch -s -n 10 demo.MathGame primeFactors
4.2 tt 命令
watch
雖然很方便和靈活,但需要提前想清楚觀察表達式的拼寫,這對排查問題而言要求太高,因爲很多時候我們並不清楚問題出自於何方,只能靠蛛絲馬跡進行猜測。
這個時候如果能記錄下當時方法調用的所有入參和返回值、拋出的異常會對整個問題的思考與判斷非常有幫助。
於是乎,TimeTunnel 命令就誕生了。
tt
:
tt 全稱 TimeTunnel ,意思是 : 方法執行數據的時空隧道,
記錄指定方法每次執行的數據,並能在不同的時間下調用觀測,
記錄下指定方法每次調用的入參和返回信息,並能對這些不同的時間下調用進行觀測
使用參考,請參見 官方文檔
tt命令可選項如下:
<class_pattern> <method_pattern>
:指定要觀測的類名+方法名。-t
:記錄下方法每次執行的情況,如tt -t demo.MathGame primeFactors
。-i <index>
:查看某條執行記錄的執行詳情,如tt -i 1000
。-d <index>
:刪除某條執行記錄,配合-i
使用,tt- d -i 1000
。-n
:設置執行次數,如tt -t -n 10 demo.MathGame primeFactors
。-l
:顯示目前已存在的所有執行記錄。-p
:重新執行某條執行記錄,配合-i
使用,如tt -i 1001 -p
。-s
:通過OGNL
表達式進行查找。-M
:指定接收結果的字節上限,默認爲1KB
。---replay-times
:配合-p
使用,指定重新執行N
次。--replay-interval
:執行多次時,每次執行時的間隔時間。- 重新執行
3
次某記錄,每次間隔500ms
:tt -i 1001 -p --replay-times 3 --replay-interval 500
。
4.3 monitor命令
monitor
:對指定的方法執行進行監控,使用參考,請參見 官方文檔
monitor 對匹配 class-pattern
/method-pattern
/condition-express
的類、方法的調用進行監控。
monitor
命令是一個非實時返回命令.
實時返回命令是輸入之後立即返回,而非實時返回的命令,則是不斷的等待目標 Java 進程返回信息,直到用戶輸入 Ctrl+C
爲止。
服務端是以任務的形式在後臺跑任務,植入的代碼隨着任務的中止而不會被執行,所以任務關閉後,不會對原有性能產生太大影響,而且原則上,任何 Arthas 命令不會引起原有業務邏輯的改變。
可選項如下:
-c
:指定監控的週期,默認爲60s
。-n
:指定監控的週期次數。- 使用示例:
monitor -c 10 -n 3 demo.MathGame primeFactors
4.4 stack命令
stack
:輸出當前方法被調用的調用路徑。
很多時候我們都知道一個方法被執行,但這個方法被執行的路徑非常多,或者你根本就不知道這個方法是從那裏被執行了,此時你需要的是 stack 命令。
使用參考,請參見 官方文檔
4.5 trace命令
trace
命令能主動搜索 class-pattern
/method-pattern
對應的方法調用路徑,渲染和統計整個調用鏈路上的所有性能開銷和追蹤調用鏈路。
使用參考,請參見 官方文檔
trace
:方法內部調用路徑,並輸出方法路徑上的每個節點上耗時,可選項如下:
-i
:跳過JVM的本地方法。-n
:和之前的-n
同義。
5、OGNL表達式
rthas中的很多進階操作都需要依賴於OGNL
表達式進行編寫,在線上排查時,OGNL表達式往往會結合tt、watch、monitor、stack、trace
等多個命令共同使用。
arthas執行ognl表達式,獲取對應的jvm對象數據。
基本語法
ognl express -c {hashCode} --classLoaderClass {當前的全路徑 ClassLoader 信息} -x {number}
參數說明
參數名稱 | 參數說明 |
---|---|
express | 執行的表達式 |
[c:] |
執行表達式的 ClassLoader 的 hashcode,默認值是 SystemClassLoader |
[classLoaderClass:] |
指定執行表達式的 ClassLoader 的 class name |
[x] | 結果對象的展開層次,默認值 1 |
①、調用靜態屬性
ognl '@類的全限定名@靜態屬性名'
示例:
[arthas@80573]$ ognl '@demo.MathGame@random'
②、調用靜態方法
ognl '@類的全限定名@靜態方法名("參數")'
示例1:調用入參爲基本數據類型和集合的方法:
[arthas@80573]$ ognl '@demo.MathGame@print(100,{1,2,3,4})' -x 1
null
③、調用構造方法
ognl 'new 類的全限定名()'
示例1:調用無參創建對象
[arthas@80573]$ ognl 'new java.lang.Object()'
示例2:調用有參創建對象
[arthas@80573]$ ognl 'new xxx.xx.xxx("xx",x,{1,2,3})'
④、讀取不同類型的值
示例1:讀取引用對象類型的屬性值
[arthas@80573]$ ognl '@類全限定名@方法名("參數").屬性名稱'
示例2:讀取List
類型的指定元素
[arthas@80573]$ ognl '@類全限定名@方法名("參數")[下標]'
詳細的OGNL
語法可參考:官方指南
Arthas線上常用場景
Arthas中集成了大部分JDK工具的功能實現,
在線上情況時,主要使用場景:
- CPU佔用過高
- 線程阻塞
- 死鎖
- 代碼動態修改
- 方法執行緩慢
- 排查
404
- ..................等。
1、排查CPU佔用過高問題
- ①
thread -n 10
命令查看CPU佔用資源最高的10條線程。 - ②
thread
命令查看線程的執行信息,定位到具體的方法。 - ③
monitor
命令對目標方法進行監控,查看方法的調用次數與耗時。 - ④分析
monitor
命令查詢出的結果,定位問題根源,確定是由於調用過於頻繁導致的,還是內部代碼邏輯問題。 - ⑤使用
jad
命令反編譯class
文件,根據前面分析的原因排查代碼並改善。
2、排查線程阻塞問題
- ①
thread
篩選所有阻塞狀態的線程。 - ②根據線程名稱定位具體的業務模塊,再選中該業務中的一條線程查看堆棧信息。
- ③根據線程堆棧信息定位導致阻塞的具體方法,再利用
stack
命令查看方法堆棧信息。 - ④利用
jad
工具反編譯源碼,分析業務邏輯代碼並改善。
3、排查死鎖問題
- ①利用
Arthas
來檢測死鎖特別簡單,只需要執行一行命令thread -b
即可。
4、排查方法執行過慢問題
- ①通過
trace
命令排查方法執行速度,trace xx類 xx方法 '#cost>50ms'
,觀測執行時間大於50ms
的該方法的調用信息。 - ②可以結合正則表達式,同時排查多個類、多個方法,
trace -E ClassA|ClassB method1|method2|method3
。
5、動態修改線上代碼
上線之後,發現代碼有一處小地方存在邏輯錯誤需要更改,可以直接線上修改,而不用重啓。
- ①通過
jad
將要修改的類反編譯爲.java
文件,輸出到指定目錄。 - ②本地糾正
.java
文件後,通過mc
命令重新編譯.java
文件。 - ③通過
retransform
命令將剛編譯的.class
文件再次加載到JVM中。
總之,Arthas通過與目標應用程序交互的命令行界面,用戶可以方便地執行各種診斷和分析任務,以解決性能問題、內存泄漏等。
這種實現原理使Arthas成爲一個強大的Java診斷工具。
Arthas底層原理
Arthas是一個用於診斷和分析Java應用程序的開源工具,它的實現原理主要包括以下幾個關鍵方面:
-
Java Agent
Arthas作爲一個Java診斷工具,通過Java Agent技術來實現對目標Java應用程序的監控和診斷。Java Agent允許Arthas以字節碼級別修改和增強目標應用程序的類,從而使其具備監控和診斷的能力。
-
Instrumentation API
Arthas使用Java的Instrumentation API來實現Java Agent。Instrumentation API允許Arthas在目標應用程序加載類的過程中插入字節碼增強代碼,以收集信息、執行命令或修改應用程序的行爲。
-
字節碼增強
Arthas通過字節碼增強技術來修改目標應用程序的類。它可以在類加載期間動態地修改類的字節碼,以添加監控、日誌記錄或診斷代碼。這使得Arthas能夠捕獲應用程序的運行時信息,並執行一系列診斷和分析任務。
-
命令行工具
提供了一個交互式的命令行界面,用戶可以在命令行中輸入各種命令來與目標應用程序進行交互。這些命令可以查詢應用程序的狀態、分析性能問題、診斷內存泄漏等。
-
類加載和類轉換
Arthas在應用程序啓動時作爲Java Agent被加載,然後通過Instrumentation API監視目標應用程序的類加載過程。一旦目標應用程序加載了新的類,Arthas可以攔截類加載事件並對類進行必要的字節碼增強。
-
類加載器隔離
Arthas採用了一種類加載器隔離的機制,以確保Arthas的Agent類加載器與目標應用程序的類加載器相互隔離,防止衝突和干擾。
總之,Arthas的實現原理基於Java Agent、Instrumentation API和字節碼增強技術,使其能夠動態地監控、診斷和分析運行中的Java應用程序。
基於Java Agent、Instrumentation的產品,除了arthas,常用的還有pinpoint、skywalking這些非常有名氣 的產品。
關於Java Agent/Instrumentation 的知識,請參見尼恩的博客:
關於skywalking 的架構和源碼,請參見尼恩的視頻:
《第24章視頻:資深架構必備,徹底穿透Skywalking鏈路跟蹤源碼、JavaAgent探針技術》
參考文獻:
說在最後: “offer自由” 很容易的
Java Agent、Instrumentation、arthas 相關的面試題,是非常常見的面試題。
以上的內容,如果大家能對答如流,如數家珍,基本上 面試官會被你 震驚到、吸引到。
最終,讓面試官愛到 “不能自已、口水直流”。offer, 也就來了。
在面試之前,建議大家系統化的刷一波 5000頁《尼恩Java面試寶典PDF》,裏邊有大量的大廠真題、面試難題、架構難題。很多小夥伴刷完後, 吊打面試官, 大廠橫着走。
在刷題過程中,如果有啥問題,大家可以來 找 40歲老架構師尼恩交流。
另外,如果沒有面試機會,可以找尼恩來改簡歷、做幫扶。
尼恩指導了大量的小夥伴上岸,前段時間,剛指導一個40歲+被裁小夥伴,拿到了一個年薪100W的offer。
狠狠卷,實現 “offer自由” 很容易的, 前段時間一個武漢的跟着尼恩捲了2年的小夥伴, 在極度嚴寒/痛苦被裁的環境下, offer拿到手軟, 實現真正的 “offer自由” !
技術自由的實現路徑:
實現你的 架構自由:
《阿里二面:千萬級、億級數據,如何性能優化? 教科書級 答案來了》
《峯值21WQps、億級DAU,小遊戲《羊了個羊》是怎麼架構的?》
… 更多架構文章,正在添加中
實現你的 響應式 自由:
這是老版本 《Flux、Mono、Reactor 實戰(史上最全)》
實現你的 spring cloud 自由:
《Spring cloud Alibaba 學習聖經》 PDF
《分庫分表 Sharding-JDBC 底層原理、核心實戰(史上最全)》
《一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之間混亂關係(史上最全)》
實現你的 linux 自由:
實現你的 網絡 自由:
《網絡三張表:ARP表, MAC表, 路由表,實現你的網絡自由!!》
實現你的 分佈式鎖 自由:
實現你的 王者組件 自由:
《隊列之王: Disruptor 原理、架構、源碼 一文穿透》
《緩存之王:Caffeine 源碼、架構、原理(史上最全,10W字 超級長文)》
《Java Agent 探針、字節碼增強 ByteBuddy(史上最全)》