Java-debug-tool
微信公眾號(hào):一字馬胡
Java-debug-tool解決什么問(wèn)題
Java-debug-tool是為了解決日常問(wèn)題排查的痛點(diǎn)而設(shè)計(jì)的钓简,問(wèn)題排查分成兩個(gè)主要階段,問(wèn)題定位和問(wèn)題修復(fù)汹想,問(wèn)題定位是說(shuō)找到問(wèn)題的原因外邓,問(wèn)題修復(fù)是說(shuō)將問(wèn)題解決,使得系統(tǒng)恢復(fù)正常運(yùn)行古掏。
對(duì)于問(wèn)題定位來(lái)說(shuō)损话,我們的需求是:
- 能知道方法入?yún)⒑头祷刂担蛘邟伋龅漠惓P畔?/li>
- 當(dāng)方法有多個(gè)出口的時(shí)候槽唾,可以知道方法是從什么地方退出的丧枪,或者是從什么地方拋出異常的;
- 一次方法調(diào)用的執(zhí)行路徑是怎么樣的庞萍,每一行代碼的耗時(shí)又是多少拧烦;
- 獲取到方法執(zhí)行過(guò)程中的局部變量信息;
本質(zhì)上钝计,問(wèn)題定位的需求是實(shí)現(xiàn)單步調(diào)試恋博,因?yàn)檫@樣是最容易發(fā)現(xiàn)問(wèn)題出在什么地方的,但是對(duì)于java來(lái)說(shuō)私恬,單步調(diào)試技術(shù)會(huì)停頓整個(gè)JVM债沮,所以只能在測(cè)試的時(shí)候使用這種技術(shù),對(duì)于生產(chǎn)環(huán)境來(lái)說(shuō)就不能使用了本鸣,所以對(duì)于線上問(wèn)題排查來(lái)說(shuō)疫衩,基本可以不用考慮單步調(diào)試,但是如果集群有流量摘除等功能的話荣德,倒是可以使用闷煤;java-debug-tool解決了這個(gè)問(wèn)題,可以模擬單步調(diào)試的同時(shí)不會(huì)停頓正在運(yùn)命爬,使用行的JVM曹傀,下面會(huì)介紹Java-debug-tool到底實(shí)現(xiàn)了一些什么功能辐脖。
找到了問(wèn)題出現(xiàn)的原因饲宛,接著就是問(wèn)題修復(fù)值纱,問(wèn)題修復(fù)最大的痛點(diǎn)其實(shí)是恢復(fù)生產(chǎn)薄湿,對(duì)于java來(lái)說(shuō),恢復(fù)生產(chǎn)意味著需要重啟JVM讯榕,這樣就會(huì)造成問(wèn)題修復(fù)時(shí)間變長(zhǎng)久锥,Java-debug-tool為此提供了技術(shù)支持Java Instrumentation技術(shù)家淤,可以在運(yùn)行時(shí)的JVM中替換類的字節(jié)碼,實(shí)現(xiàn)熱修復(fù)瑟由。
Java-debug-tool不能解決什么問(wèn)題
- (1)如果需要做性能優(yōu)化分析絮重,Java-debug-tool可能支持的力度很小,雖然可以通過(guò)Java-debug-tool觀察到每一行代碼的執(zhí)行耗時(shí),但是也僅僅是觀察青伤,所以性能問(wèn)題還是需要其他專業(yè)的工具來(lái)進(jìn)行督怜;
- (2)非JVM自身問(wèn)題,比如機(jī)器CPU狠角、磁盤I/O等問(wèn)題号杠,Java-debug-tool就無(wú)能為力了,Java-debug-tool專注于解決JVM自身的問(wèn)題丰歌;
- (3)Java-debug-tool僅支持方法級(jí)別的觀察姨蟋,無(wú)法觀察到整體的調(diào)用鏈路,后續(xù)可能會(huì)支持多級(jí)方法鏈路的觀察立帖,但可能性不大眼溶,因?yàn)橐С诌@種方法間調(diào)用鏈路追蹤,就得增強(qiáng)多個(gè)方法晓勇,而增強(qiáng)方法是對(duì)運(yùn)行時(shí)有一定損耗的偷仿,如果一個(gè)方法調(diào)用鏈路特別長(zhǎng)(對(duì)于java來(lái)說(shuō)一般調(diào)用鏈路都很長(zhǎng)),那么就悲劇了宵蕉;
- (4)Java-debug-tool不支持遞歸方法的觀察酝静,這個(gè)功能實(shí)現(xiàn)起來(lái)也是非常麻煩,而且極其不可控羡玛,所以千萬(wàn)不要用Java-debug-tool去觀察一個(gè)遞歸方法别智,切記;
如何使用
使用
首先需要下載安裝腳本:
wget https://github.com/pandening/storm-ml/releases/download/6.0/javadebug-tool-install.sh
之后執(zhí)行:
sh javadebug-tool-install.sh
如果看到屏幕輸出:
welcome to use java-debug-tool
就說(shuō)明安裝成功了稼稿,可以使用工具了薄榛!
開(kāi)發(fā)
Java-debug-tool使用Java開(kāi)發(fā),下面介紹如何使用Java-debug-tool進(jìn)行問(wèn)題排查让歼;
- (1)下載Java-debug-tool代碼敞恋;
- (2)進(jìn)入script目錄,執(zhí)行javadebug-pack.sh腳本執(zhí)行編譯打包谋右,要求JDK 1.8 +硬猫,并且一定要執(zhí)行javadebug-pack.sh腳本之后再使用;
- (3)如果是Spring項(xiàng)目改执,則只需要將下面的bean配置到項(xiàng)目中即可實(shí)現(xiàn)JVM啟動(dòng)之后Java-debug-tool Agent自動(dòng)attach到目標(biāo)JVM上的功能啸蜜,如果不是Spring項(xiàng)目,請(qǐng)看(4)
<!-- dynamic debug bean -->
<bean id = "javaDebugInitializer" class="io.javadebug.spring.JavaDebugInitializer" factory-method="initializer" destroy-method="destroy" lazy-init="false"/>
- (4)Java-debug-tool不要求在目標(biāo)JVM啟動(dòng)的時(shí)候就必須attach到目標(biāo)JVM上辈挂,可以動(dòng)態(tài)attach衬横,在目錄 /bin下有多個(gè)可用的腳本,方便用于動(dòng)態(tài)attach到目標(biāo)JVM上终蒂,無(wú)論如何蜂林,你都需要首先知道目標(biāo)JVM的進(jìn)程id遥诉,然后執(zhí)行一個(gè)腳本就可以動(dòng)態(tài)attach到目標(biāo)JVM上:
./javadebug-agent-launch.sh PID
這樣就可以在目標(biāo)JVM上啟動(dòng)一個(gè)tcp服務(wù),默認(rèn)地址為:127.0.0.1:11234噪叙,如果你想要指定其他的地址突那,可以使用下面的命令:
./javadebug-agent-launch.sh PID@IP:PORT
之后就可以在IP:PORT啟動(dòng)tcpServer,attach到目標(biāo)JVM上之后构眯,就可以連接目標(biāo)JVM進(jìn)行動(dòng)態(tài)調(diào)試了愕难,連接到目標(biāo)JVM只需要執(zhí)行下面的命令即可:
./javadebug-client-launch.sh
默認(rèn)就是連接 127.0.0.1:11234,如果attach目標(biāo)JVM的時(shí)候指定的地址不是這個(gè)惫霸,需要顯示指定地址:
./javadebug-client-launch.sh IP:PORT
命令詳解
Java-debug-tool目前支持的命令不多猫缭,下面分別介紹一下當(dāng)前支持的核心命令。首先介紹一下命令輸出界面信息介紹:
---------------------------------------------------------------------------------------------
命令 :mt
命令執(zhí)行Round :1
客戶端ID :10000
客戶端類型 :client:1
協(xié)議版本 :version:1
命令耗時(shí) :179 (ms)
STW時(shí)間 :45 (ms)
---------------------------------------------------------------------------------------------
[ReturnTest.getIntVal] with params
[1]
[0 ms] (37)
[0 ms] (43) [startTime = 1559358148073]
[0 ms] (44) [strTag = the return/throw line test tag]
[0 ms] (45)
[0 ms] (47)
[0 ms] (51)
[3 ms] (52) [paramModel = 1.1]
[0 ms] (53)
return value:[101] at line:53 with cost:5 ms
---------------------------------------------------------------------------------------------
每個(gè)輸出字段都介紹一下:
字段 | 含義 | 值 |
---|---|---|
命令 | 本次輸出執(zhí)行的命令是什么 | 就是你輸入的命令名稱 |
命令執(zhí)行Round | 這個(gè)調(diào)試客戶端和目標(biāo)JVM交互了幾次 | 交互次數(shù) |
客戶端ID | 每個(gè)客戶端首次連接服務(wù)端都會(huì)被分配一個(gè)ContextId壹店,后續(xù)的交互都需要將這個(gè)ID帶上 | 唯一ID |
客戶端類型 | 這是一個(gè)保留字段猜丹,Java-debug-tool認(rèn)為第一個(gè)連接到目標(biāo)JVM的調(diào)試客戶端應(yīng)該是一個(gè)Master Client,權(quán)限最高 | |
協(xié)議版本 | 防偽硅卢,只有是從服務(wù)端拿到的協(xié)議才能繼續(xù)交互 | |
命令耗時(shí) | 命令的執(zhí)行耗時(shí)射窒,從命令輸入處理開(kāi)始計(jì)算,到命令結(jié)果展示出來(lái)結(jié)束将塑,所以是客戶端耗時(shí) + 服務(wù)端耗時(shí) | |
STW時(shí)間 | 動(dòng)態(tài)增強(qiáng)字節(jié)碼涉及到JVM字節(jié)碼替換脉顿,會(huì)造成STW,這個(gè)時(shí)間就記錄到底STW了多長(zhǎng)時(shí)間点寥,這個(gè)時(shí)間會(huì)比實(shí)際STW的時(shí)間長(zhǎng)艾疟,只是一個(gè)粗略的時(shí)間 | 如果一個(gè)方法被一個(gè)client增強(qiáng)過(guò)了,后續(xù)的client就不能增強(qiáng)了敢辩,除非增強(qiáng)該方法的client退出蔽莱,其他client才能繼續(xù)增強(qiáng);同時(shí)戚长,一個(gè)client增強(qiáng)過(guò)的方法盗冷,其他client可以共享 |
接著就是具體方法的執(zhí)行路徑信息,比如上面這個(gè)例子同廉,說(shuō)明本次觀察的方法執(zhí)行是 "ReturnTest.getIntVal"仪糖,方法入?yún)⑹?,方法執(zhí)行路徑是37-43-44-45-47-51-52-53恤溶,最終從53行退出乓诽,其中第52行耗時(shí)3ms帜羊,其他行耗時(shí)小于1ms咒程,所以無(wú)法收集到,最終方法的執(zhí)行結(jié)果是101讼育,本次方法耗時(shí)5ms帐姻,并且可以看到43稠集、44、52行都有變量賦值信息饥瓷,格式為 varName = varVal.toString()剥纷,需要注意的是,varName可能是錯(cuò)誤的呢铆,但是varVal是正確的晦鞋,如果有多個(gè),按照賦值順序展示棺克;這是方法正常返回的結(jié)果展示悠垛,下面看一個(gè)方法拋出異常的結(jié)果展示:
---------------------------------------------------------------------------------------------
命令 :mt
命令執(zhí)行Round :1
客戶端ID :10001
客戶端類型 :client:0
協(xié)議版本 :version:1
命令耗時(shí) :75 (ms)
STW時(shí)間 :0 (ms)
---------------------------------------------------------------------------------------------
[ReturnTest.getIntVal] with params
[7]
[0 ms] (37)
[0 ms] (43) [startTime = 1559358921527]
[0 ms] (44) [strTag = the return/throw line test tag]
[0 ms] (45)
[0 ms] (47)
[0 ms] (51)
[0 ms] (54)
[0 ms] (59)
[0 ms] (73) [paramModel = ParamModel{intVal=0, doubleVal='0.0'}]
[0 ms] (74)
[0 ms] (75)
[0 ms] (76) [subVal = 200]
[0 ms] (78)
[0 ms] (82)
throw exception:[java.lang.IllegalStateException: error occ with in:7] at line:82 with cost:0 ms
---------------------------------------------------------------------------------------------
可以看到本次方法執(zhí)行路徑,參數(shù)為7娜谊,在82行拋出了異常确买,其他信息和正常返回時(shí)類似,就不做過(guò)多解釋了纱皆。
methodTrace命令
就像命令名稱一樣湾趾,這個(gè)命令是用于觀察方法執(zhí)行路徑的,可以使用mt來(lái)替代命令派草,該命令參數(shù)較多搀缠,但是大部分都是可選的,下面先介紹每一個(gè)參數(shù)的含義近迁,然后再介紹如何實(shí)現(xiàn)具體的功能胡嘿。
命令基本格式:
mt -c <class> -m <method>
可選參數(shù):
-d :如果目標(biāo)類中的目標(biāo)方法是重載方法,那么你需要提供這個(gè)參數(shù)钳踊,比如int a(int a) => desc = "(I)I"衷敌;
-
-t:選擇具體的功能類型,可選項(xiàng)為:
- return:當(dāng)方法正常退出的時(shí)候拓瞪,獲取到一次方法鏈路信息缴罗;
- throw:方方法拋出異常的時(shí)候,獲取到一次方法鏈路信息祭埂;
- record:記錄方法調(diào)用信息面氓,用于回放流量;
- custom:用于實(shí)現(xiàn)用戶自己輸入?yún)?shù)觀察蛆橡,或者回放record的流量進(jìn)行觀察舌界,當(dāng)然,如果只是想發(fā)生一次請(qǐng)求也是可以的泰演;
- watch:等待特定的參數(shù)呻拌,使用Spring表達(dá)式進(jìn)行參數(shù)匹配,當(dāng)匹配到目標(biāo)參數(shù)之后睦焕,會(huì)返回方法鏈路信息藐握,如果Spring表達(dá)式有誤靴拱,那么會(huì)直接在第一次方法調(diào)用之后返回;
-i:用于接收用戶的參數(shù)輸入猾普,比如當(dāng)t=custom的時(shí)候袜炕,i參數(shù)就是用戶指定的參數(shù),這個(gè)參數(shù)是通過(guò)特殊處理的json字符串初家,java-debug-tool將提供工具接口來(lái)生成這個(gè)字符串偎窘,當(dāng)t=watch的時(shí)候,i參數(shù)就是用于匹配參數(shù)的Spring表達(dá)式溜在。
-n:當(dāng)t=record的時(shí)候评架,n參數(shù)的含義就是需要錄制的流量數(shù)量,當(dāng)前僅允許錄制10個(gè)以內(nèi)炕泳;
-time:當(dāng)t=record的時(shí)候纵诞,該參數(shù)的含義是錄制的時(shí)間限制,超出則停止錄制培遵;
-u:當(dāng)t=custom的時(shí)候浙芙,如果提供了u參數(shù),那么i參數(shù)將被忽略籽腕,u代表record的流量下標(biāo)嗡呼,從0開(kāi)始,如果u參數(shù)獲取到了具體的流量皇耗,那么本次custom輸入的參數(shù)就會(huì)從u參數(shù)取出來(lái)的流量中拿到參數(shù)南窗,如果t=record,并且u參數(shù)合法郎楼,那么就不會(huì)進(jìn)行錄制万伤,而是會(huì)從錄制好的流量中取出代表u下標(biāo)的流量,用戶可以查看具體的流量信息(包括該流量的方法鏈路)呜袁;
-e:如果t=throw敌买,那么如果-e內(nèi)容合法,那么該參數(shù)就代表需要等待的目標(biāo)異常阶界,如果參數(shù)不合法虹钮,只要遇到一個(gè)異常,本次觀察就會(huì)結(jié)束膘融;當(dāng)t=custom的時(shí)候芙粱,該參數(shù)用于匹配自定義輸入,也就是說(shuō)氧映,如果你希望觀察自定義輸入的執(zhí)行路徑春畔,你需要在custom類型下指定-e參數(shù),內(nèi)容是用于匹配輸入的Spring表達(dá)式;
-s:有些情況下拐迁,你可能只需要看方法調(diào)用的路徑蹭劈,不需要耗時(shí)信息疗绣,或者不需要變量信息线召,那么這個(gè)參數(shù)有很有用,因?yàn)榭赡苡行┳兞亢荛L(zhǎng)多矮,展示出來(lái)很難看,而有些時(shí)候你只需要看看方法到底是從哪里退出來(lái)的塔逃,這個(gè)參數(shù)有很有幫助讯壶≌饰可以是"line"/"cost"中的一個(gè)悠菜,前者表示只需要給我方法鏈路信息德玫,后者其實(shí)是"line" + "cost"显熏;
-l:這個(gè)參數(shù)很有用棘脐,當(dāng)某個(gè)方法很長(zhǎng),那么鏈路追蹤信息打印出來(lái)會(huì)很難看旗扑,你可能只關(guān)心某一行的相關(guān)信息,比如就想看看某一行的代碼執(zhí)行耗時(shí)慈省,以及這一行相關(guān)的變量信息臀防,那么這個(gè)參數(shù)就可以派上用場(chǎng),值就是具體的行號(hào)(對(duì)照源碼);
下面根據(jù)上面的參數(shù)來(lái)實(shí)現(xiàn)不同的觀察功能袱衷,首先是用于測(cè)試的Java類:
public class ReturnTest {
private TestClass testClass = new TestClass();
public static void say(int a) {
int b = a * 10;
System.out.println("hello:" + b);
//return b;
}
public int getIntVal(int in) {
// if (in < 7) {
// System.out.println("in < 7, return");
// throw new UnsupportedOperationException("test");
// }
if (in == 5) {
String msg = null;
// produce npe
in += msg.length();
}
long startTime = System.currentTimeMillis() + fibonacci(2);
String strTag = "the return/throw line test tag";
if (in < 0) {
return strTag.charAt(0);
} else if (in == 0) {
return 1000;
}
// > 0
if (in < 2) {
double dbVal = 1.1;
return (int) (dbVal + 100);
} else if (in == 2) {
float fVal = 1.2f;
return (int) (fVal + 200);
}
// > 2
if (in % 2 == 0) {
Random random = new Random();
int rdm = random.nextInt(100);
if (rdm >= 50) {
throw new IllegalArgumentException("npe test");
} else if (rdm <= 20) {
throw new NullPointerException("< 20");
}
// end time
long end = System.currentTimeMillis();
long cost = startTime - end;
int ret = testClass.test(in);
return (int) (rdm * 10 + ret + (cost / 1000));
} else {
ParamModel paramModel = new ParamModel();
paramModel.setIntVal(in);
paramModel.setDoubleVal(1.0 * in);
int subVal = getSubIntVal(paramModel);
if (subVal == 100) {
throw new IllegalArgumentException("err occ with in:" + subVal);
}
throw new IllegalStateException("error occ with in:" + in);
}
}
/**
* 不支持遞歸函數(shù)
*
* @param n
* @return
*/
public int fibonacci(int n) {
if (n < 0) {
return -1;
}
if (n == 0) {
return 0;
}
if (n <= 2) {
return 1;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
public int getSubIntVal(ParamModel paramModel) {
if (paramModel == null) {
return -1;
}
if (paramModel.getIntVal() <= 0) {
return (int) paramModel.getDoubleVal();
} else if (paramModel.getIntVal() <= 5) {
return 100;
} else if (paramModel.getIntVal() <= 8) {
return 200;
} else {
throw new RuntimeException("ill");
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
private Random random = new Random();
private ReturnTest returnTest = new ReturnTest();
@Override
public void run() {
while (true) {
try {
System.err.println(returnTest.getIntVal(random.nextInt(10)));
TimeUnit.MILLISECONDS.sleep(5);
} catch (Exception e) {
//e.printStackTrace();
//System.out.println("error:" + e.getMessage());
}
}
}
}).start();
}
}
public class TestClass {
Aa aa = new Aa();
public int test(int in) {
if (in == 5) {
return 100;
}
String tag = "the in:" + in;
if (in < 5) {
in += 2;
} else {
in -= 1;
}
if (in > 5) {
throw new IllegalArgumentException("must <= 5");
}
if (in <= 3) {
throw new NullPointerException("must >= 3");
}
return in * 100;
}
}
- (1)觀察一次方法調(diào)用的執(zhí)行路徑
上面的圖片展示了觀察一次 "ReturnTest.getIntVal"方法調(diào)用的執(zhí)行路徑捎废,本次方法入?yún)⑹?,返回值是201致燥,是從56行代碼退出的登疗,耗時(shí)1ms;
(2)在(1)中只要方法被調(diào)用一次篡悟,那么觀察就會(huì)立刻結(jié)束谜叹,所以觀察結(jié)果可能是方法正常結(jié)束匾寝,也可能是拋出了異常搬葬,如果只是希望觀察方法正常退出,那么就可以指定-t參數(shù)為return艳悔,這樣只有當(dāng)?shù)谝淮畏椒ú粧伋霎惓M顺霾艜?huì)結(jié)束觀察急凰;
(3)和(2)相反的是,如果你希望監(jiān)控一個(gè)異常的執(zhí)行路徑猜年,比如一個(gè)方法執(zhí)行偶爾會(huì)拋出某種異常抡锈,搞得你很摸不著頭腦,那你就可以指定-t參數(shù)為throw來(lái)觀察拋出異常的執(zhí)行路徑:
當(dāng)然乔外,如果你想要觀察的是某種特定的異常床三,可以指定-e參數(shù):
- (4)自定義輸入?yún)?shù)進(jìn)行方法調(diào)用,并進(jìn)行方法執(zhí)行路徑觀察:需要注意的是杨幼,在執(zhí)行自定義參數(shù)調(diào)用之前撇簿,Java-debug-tool需要獲取到目標(biāo)對(duì)象,或者目標(biāo)方法是一個(gè)靜態(tài)方法差购,否則Java-debug-tool命令會(huì)一直等待獲取目標(biāo)對(duì)象(不會(huì)主動(dòng)創(chuàng)建目標(biāo)對(duì)象)
- (5)記錄方法調(diào)用請(qǐng)求
錄制完成后可以回放請(qǐng)求:
- (6)觀察特定輸入執(zhí)行路徑
redefineClass命令
redefineClass命令用于替換一個(gè)類的字節(jié)碼四瘫,可以簡(jiǎn)寫(xiě)成rdf,用于快速恢復(fù)生產(chǎn)環(huán)境欲逃,命令的參數(shù)沒(méi)有mt命令復(fù)雜找蜜,但是需要有幾點(diǎn)需要注意:
- 一個(gè)client對(duì)一個(gè)類執(zhí)行rdf命令,就會(huì)鎖定這個(gè)類稳析,其他client就不能對(duì)相同的類執(zhí)行rdf洗做,直至client退出;
我們把getIntVal方法的開(kāi)始部分的注釋去掉彰居,也就是:
// if (in < 7) {
// System.out.println("in < 7, return");
// throw new UnsupportedOperationException("test");
// }
這一段內(nèi)容诚纸,去掉之后,只要輸入的參數(shù)小于7裕菠,那么就會(huì)拋出異常咬清,我們使用mt命令配合custom來(lái)驗(yàn)證我們的rdf結(jié)果:
可以看到,此時(shí)輸入?yún)?shù)為5的時(shí)候拋出了那個(gè)期望的異常;
rollback命令
rollback命令用于將一個(gè)增強(qiáng)過(guò)的類恢復(fù)到初始狀態(tài)旧烧,可以使用back簡(jiǎn)寫(xiě)影钉,目前僅支持恢復(fù)到初始狀態(tài),后續(xù)會(huì)記錄增強(qiáng)stage掘剪,然后恢復(fù)到上一次增強(qiáng)過(guò)的字節(jié)碼:
findClass命令
是不是曾經(jīng)出現(xiàn)過(guò)因?yàn)閖ar包沖突導(dǎo)致的類加載錯(cuò)誤的情況呢平委?findClass命令用于快速判斷一個(gè)類是不是在目標(biāo)JVM加載了,如果加載了夺谁,是從哪個(gè)jar包中加載的(jar一般都會(huì)有版本號(hào)廉赔,可以看看是不是從期望的jar版本中加載的),是被什么類加載器加載的匾鸥,還可以僅僅使用類名(不含包名)來(lái)匹配蜡塌,甚至通過(guò)正則表達(dá)式來(lái)匹配,可以使用簡(jiǎn)寫(xiě)fc:
help命令
如果你不知道怎么使用一個(gè)命令勿负,那么可以試試help命令:
如何重復(fù)發(fā)生上一次發(fā)送的命令
有時(shí)候需要重復(fù)上一次輸入的命令馏艾,但是上一次命令輸入內(nèi)容很多,如何快速實(shí)現(xiàn)上一次命令的復(fù)制呢奴愉?下面的一些字符可以快速幫你搞定這件事情:"p","r","s","go","last"
后續(xù)將支持命令歷史記錄回放的功能琅摩,目前僅支持回放上一次輸入。
規(guī)劃中的功能
- (1)線程相關(guān)功能锭硼,包括當(dāng)前線程總數(shù)房资、活動(dòng)線程數(shù)等等信息,并能獲取到某個(gè)線程的調(diào)用堆棧等信息檀头;
- (2)GC相關(guān)信息轰异;