☆淺談JPDA中JVMTI模塊

0 前言

上一節(jié)《Java Instrument 功能使用及原理》文章中,講解Instrument使用時谜洽,簡單提了一句JVMTI的概念萝映,可能有很多小伙伴感到很陌生,雖然Java Instrument的使用基本沒什么問題阐虚,但對于Instrument基于JVMTI的實現(xiàn)原理還是處于混沌狀態(tài)序臂。所以本節(jié)的重點就在于講解JVMTI,正如標(biāo)題实束。

但由于JVMTI在整個JVM JPDA體系中只是其中的一個小模塊奥秆,為了使大家在整體上能有個清晰的認(rèn)識,那我們先從JPDA體系開始吧咸灿。

1 JPDA 介紹

所有的程序員都會遇到 bug构订,對于運行態(tài)的錯誤,我們往往需要一些方法來觀察和測試運行態(tài)中的環(huán)境避矢。作為一個合格的Developer鲫咽,最基本的技能就是要掌握語言在不同IDE的Debug技能签赃。

Intellij IDEA 就提供一個功能非常全面,操作非常簡單的調(diào)試器分尸,如下圖:

Intellij IDEA 調(diào)試界面

有時甚至不用 IDE 提供的圖形界面锦聊,使用 JDK 自帶的 jdb 工具,以文本命令的形式來調(diào)試您的 Java 程序箩绍。這些形形色色的調(diào)試器都 支持本地和遠(yuǎn)程的程序調(diào)試孔庭,那么它們是如何被開發(fā)的?它們之間存在著什么樣的聯(lián)系呢材蛛?

我們不得不提及 Java 的調(diào)試體系—— JPDA圆到,它是我們通向虛擬機,考察虛擬機運行態(tài)的一個通道卑吭,一套工具 芽淡。

Java 程序都是運行在 Java 虛擬機上的,我們要調(diào)試 Java 程序豆赏,事實上就需要向 Java 虛擬機請求當(dāng)前運行態(tài)的狀態(tài)挣菲,并對虛擬機發(fā)出一定的指令,設(shè)置一些回調(diào)等等掷邦,那么 Java 的調(diào)試體系——JPDA白胀,就是虛擬機的一整套用于調(diào)試的工具和接口

顧名思義抚岗,這個體系為開發(fā)人員提供了 一整套用于調(diào)試 Java 程序的 API或杠,是一套用于開發(fā) Java 調(diào)試工具的接口和協(xié)議

通過這些 JPDA 提供的接口和協(xié)議宣蔚,調(diào)試器開發(fā)人員就能根據(jù)特定開發(fā)者的需求向抢,擴展定制 Java 調(diào)試應(yīng)用程序,開發(fā)出吸引開發(fā)人員使用的調(diào)試工具胚委。

但我們要注意的是挟鸠,JPDA 是一套標(biāo)準(zhǔn),任何的 JDK 實現(xiàn)都必須完成這個標(biāo)準(zhǔn)篷扩,因此,通過 JPDA 開發(fā)出來的調(diào)試工具先天 具有跨平臺茉盏、不依賴虛擬機實現(xiàn)鉴未、JDK 版本無關(guān)等移植優(yōu)點,因此大部分的調(diào)試工具都是基于這個體系鸠姨。

1.1 JPDA 模塊

JPDA 定義了一個完整獨立的體系铜秆,它由三個相對獨立的層次共同組成,而且規(guī)定了它們?nèi)咧g的交互方式讶迁,或者說定義了它們通信的接口连茧。

三個層次由低到高分別是 Java 虛擬機工具接口(JVMTI),Java 調(diào)試協(xié)議(JDWP)以及 Java 調(diào)試接口(JDI)。這三個模塊把調(diào)試過程分解成幾個很自然的概念:調(diào)試者(debugger)和被調(diào)試者(debuggee)啸驯,以及他們中間的通信器客扎。

被調(diào)試者(JVMTI):運行于我們想調(diào)試的 Java 虛擬機之上,它可以通過 JVMTI 這個標(biāo)準(zhǔn)接口罚斗,監(jiān)控當(dāng)前虛擬機的信息徙鱼;

調(diào)試者(JDI):定義了用戶可使用的調(diào)試接口,通過這些接口针姿,用戶可以對被調(diào)試虛擬機發(fā)送調(diào)試命令袱吆,同時調(diào)試者接受并顯示調(diào)試結(jié)果

中間通信器(JDWP)在調(diào)試者和被調(diào)試者之間距淫,調(diào)試命令和調(diào)試結(jié)果绞绒,都是通過 JDWP 的通訊協(xié)議傳輸?shù)?/strong>;所有的命令被封裝成 JDWP 命令包榕暇,通過傳輸層發(fā)送給被調(diào)試者蓬衡,被調(diào)試者接收到 JDWP 命令包后,解析這個命令并轉(zhuǎn)化為 JVMTI 的調(diào)用拐揭,在被調(diào)試者上運行撤蟆;類似的,JVMTI 的運行結(jié)果堂污,被格式化成 JDWP 數(shù)據(jù)包家肯,發(fā)送給調(diào)試者并返回給 JDI 調(diào)用。而調(diào)試器開發(fā)人員就是通過 JDI 得到數(shù)據(jù)盟猖,發(fā)出指令讨衣;

上述整個過程,如下圖所示:

JPDA 模塊層次

當(dāng)然式镐,開發(fā)人員完全可以不使用完整的三個層次反镇,而是 基于其中的某一個層次開發(fā)自己的應(yīng)用。比如:完全可以僅僅依靠通過 JVMTI 函數(shù)開發(fā)一個調(diào)試工具娘汞,而不使用 JDWP 和 JDI歹茶,只使用自己的通訊和命令接口。當(dāng)然你弦,除非是有特殊的需求惊豺,利用已有的實現(xiàn)會事半功倍,避免重復(fù)發(fā)明輪子禽作。

下面尸昧,我們就分別講解下JPDA的三種組成模塊:

  1. Java 虛擬機工具接口(JVMTI)

    JVMTI(Java Virtual Machine Tool Interface)即指 Java 虛擬機工具接口,它是 一套由虛擬機直接提供的 native 接口旷偿,它處于整個 JPDA 體系的最底層烹俗,所有調(diào)試功能本質(zhì)上都需要通過 JVMTI 來提供爆侣。通過這些接口,開發(fā)人員不僅調(diào)試在該虛擬機上運行的 Java 程序幢妄,還能 查看它們運行的狀態(tài)兔仰,設(shè)置回調(diào)函數(shù),控制某些環(huán)境變量磁浇,從而優(yōu)化程序性能斋陪。

  2. Java 調(diào)試線協(xié)議(JDWP)

    JDWP(Java Debug Wire Protocol)是一個為 Java 調(diào)試而設(shè)計的一個通訊交互協(xié)議,它定義了調(diào)試器和被調(diào)試程序之間傳遞的信息的格式置吓。

    在 JPDA 體系中无虚,作為前端(front-end)的調(diào)試者(debugger)進(jìn)程和后端(back-end)的被調(diào)試程序(debuggee)進(jìn)程之間的交互數(shù)據(jù)的格式就是由 JDWP 來描述的,它詳細(xì)完整地定義了請求命令衍锚、回應(yīng)數(shù)據(jù)和錯誤代碼友题,保證了前端和后端的 JVMTI 和 JDI 的通信通暢。

    比如:在 Sun 公司提供的實現(xiàn)中戴质,它提供了一個名為 jdwp.dll(jdwp.so)的動態(tài)鏈接庫文件度宦,這個動態(tài)庫文件實現(xiàn)了一個 Agent,它會負(fù)責(zé)解析前端發(fā)出的請求或者命令告匠,并將其轉(zhuǎn)化為 JVMTI 調(diào)用戈抄,然后將 JVMTI 函數(shù)的返回值封裝成 JDWP 數(shù)據(jù)發(fā)還給后端

    另外后专,這里需要注意的是 JDWP 本身并不包括傳輸層的實現(xiàn)划鸽,傳輸層需要獨立實現(xiàn),但是 JDWP 包括了和傳輸層交互的嚴(yán)格的定義戚哎,就是說裸诽,JDWP 協(xié)議雖然不規(guī)定我們是通過 EMS 還是快遞運送貨物的,但是它規(guī)定了我們傳送的貨物的擺放的方式型凳。在 Sun 公司提供的 JDK 中丈冬,在傳輸層上,它提供了 socket 方式甘畅,以及在 Windows 上的 shared memory 方式埂蕊。當(dāng)然,傳輸層本身無非就是本機內(nèi)進(jìn)程間通信方式和遠(yuǎn)端通信方式疏唾,也可以按 JDWP 的標(biāo)準(zhǔn)自己實現(xiàn)蓄氧。

  3. Java 調(diào)試接口(JDI)

    JDI(Java Debug Interface)是三個模塊中最高層的接口,在多數(shù)的 JDK 中荸实,它是由 Java 語言實現(xiàn)的匀们。 JDI 由針對前端定義的接口組成缴淋,通過它准给,調(diào)試工具開發(fā)人員就能通過前端虛擬機上的調(diào)試器來遠(yuǎn)程操控后端虛擬機上被調(diào)試程序的運行泄朴,JDI 不僅能幫助開發(fā)人員格式化 JDWP 數(shù)據(jù),而且還能為 JDWP 數(shù)據(jù)傳輸提供隊列露氮、緩存等優(yōu)化服務(wù)祖灰。從理論上說,開發(fā)人員只需使用 JDWP 和 JVMTI 即可支持跨平臺的遠(yuǎn)程調(diào)試畔规,但是直接編寫 JDWP 程序費時費力局扶,而且效率不高。因此基于 Java 的 JDI 層的引入叁扫,簡化了操作三妈,提高了開發(fā)人員開發(fā)調(diào)試程序的效率

JPDA 層次比較

1.2 JPDA 實現(xiàn)

每一個虛擬機都應(yīng)該實現(xiàn) JVMTI 接口莫绣,但是 JDWP 和 JDI 本身與虛擬機并非是不可分的畴蒲,這三個層之間是通過標(biāo)準(zhǔn)所定義的交互的接口和協(xié)議聯(lián)系起來的,因此它們可以被獨立替換或取代对室,但不會影響到整體調(diào)試工具的開發(fā)和使用模燥。因此,開發(fā)和使用自己的 JDWP 和 JDI 接口實現(xiàn)是可能的掩宜。

Java 軟件開發(fā)包(SDK)標(biāo)準(zhǔn)版里提供了 JPDA 三個層次的標(biāo)準(zhǔn)實現(xiàn)蔫骂,事實上,調(diào)試工具開發(fā)人員還有很多其他開源實現(xiàn)可以選擇牺汤,比如 Apache Harmony 提供了 JDWP 的實現(xiàn)辽旋。而 JDI,我們可以在 Eclipse 一個子項目 org.eclipse.jdt.debug 里找到其完整的實現(xiàn)(Harmony 也使用了這套實現(xiàn)慧瘤,作為其 J2SE 類庫的一部分)戴已。通過標(biāo)準(zhǔn)協(xié)議,Eclipse IDE 的調(diào)試工具就可以完全在 Harmony 的環(huán)境上運行锅减。

2 JVMTI 介紹

JVMTI(JVM Tool Interface)是 Java 虛擬機所提供的 native 編程接口糖儡,是 JVMPI(Java Virtual Machine Profiler Interface)和 JVMDI(Java Virtual Machine Debug Interface)的替代版本。

從這個 API 的替代軌跡中可知怔匣,JVMTI 提供了可用于 debug 和 profiler 的接口握联;同時,在 Java 5/6 中每瞒,JVMTI 接口也增加了 監(jiān)聽(Monitoring)金闽,線程分析(Thread analysis)以及覆蓋率分析(Coverage Analysis) 等功能。正是由于 JVMTI 的強大功能剿骨,它是實現(xiàn) Java 調(diào)試器代芜,以及其它 Java 運行態(tài)測試與分析工具的基礎(chǔ)。

JVMTI 是一套本地代碼接口浓利,可以使開發(fā)者直接與 C/C++ 以及 JNI 打交道挤庇。

那么钞速,開發(fā)者是如何來使用JVMTI所提供的接口呢?事實上嫡秕,一般采用建立一個 Agent 的方式來使用 JVMTI渴语,這個Agent的表現(xiàn)形式是一個以c/c++語言編寫的動態(tài)鏈接庫

把 Agent 編譯成一個動態(tài)鏈接庫昆咽,Java啟動或運行時驾凶,動態(tài)加載一個外部基于JVMTI 編寫的dynamic module到Java進(jìn)程內(nèi),然后觸發(fā) JVM源生線程Attach Listener來執(zhí)行這個dynamic module的回調(diào)函數(shù) 掷酗。

在回調(diào)函數(shù)體內(nèi)调违,可以 獲取各種各樣的VM級信息,注冊感興趣的VM事件泻轰,甚至控制VM行為翰萨。

2.1 Agent 工作過程

2.1.1 啟動

JVMTI有兩種啟動方式,第一種是隨Java進(jìn)程啟動時糕殉,自動載入共享庫亩鬼,下文簡稱 啟動時載入。另一種方式是阿蝶,Java運行時雳锋,通過attach api動態(tài)載入,下文簡稱 運行時載入羡洁。

啟動時載入玷过,通過在Java命令行啟動時傳遞一個特殊的option,如下:

  1. java -agentlib:<agent-lib-name>=<options> Sample
    注意筑煮,這里的共享庫路徑是環(huán)境變量路徑辛蚊,例如 java -agentlib:foo=opt1,opt2,java啟動時會從linux的LD_LIBRARY_PATH或windows的PATH環(huán)境變量定義的路徑處裝載foo.so或foo.dll真仲,找不到則拋異常

  2. java -agentpath:<path-to-agent>=<options> Sample
    這是 以絕對路徑的方式裝載共享庫袋马,例如 java -agentpath:/home/admin/agentlib/foo.so=opt1,opt2

啟動時載入,處于虛擬機初始化的早期秸应,在這個時間點上:

  1. 所有的 Java 類都未被初始化虑凛;
  2. 所有的 Java 對象實例都未被創(chuàng)建;
  3. 因而软啼,沒有任何 Java 代碼被執(zhí)行桑谍;

但在這個時候,我們已經(jīng)可以:

  1. 操作 JVMTI 的 Capability 參數(shù)祸挪;
  2. 使用系統(tǒng)參數(shù)锣披;

動態(tài)庫被加載之后,虛擬機會先尋找一個 Agent 入口函數(shù):

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)

在這個Agent_OnLoad函數(shù)中,虛擬機傳入了三個參數(shù):

  1. JavaVM *vmJVM上下文雹仿,通過 JavaVM榜跌,可以獲得 JVMTI 的指針,并獲得 JVMTI 函數(shù)的使用能力盅粪;
  2. char *options外部傳入的參數(shù),比如上面例子中給的 opt1, opt2悄蕾,它僅僅是一個字符串票顾;
  3. void *reserved:一個預(yù)留參數(shù),不必關(guān)心它帆调;

運行時載入奠骄,通過attach api,這是一套純Java的API番刊,它負(fù)責(zé)動態(tài)地將dynamic module attach到指定進(jìn)程id的Java進(jìn)程內(nèi)并觸發(fā)回調(diào)含鳞。例子如下:

import java.io.IOException;
import com.sun.tools.attach.VirtualMachine;

public class VMAttacher {
    public static void main(String[] args) throws Exception {
         // args[0]為java進(jìn)程id
         VirtualMachine virtualMachine = com.sun.tools.attach.VirtualMachine.attach(args[0]);
         // args[1]為共享庫路徑,args[2]為傳遞給agent的參數(shù)
         virtualMachine.loadAgentPath(args[1], args[2]);
         virtualMachine.detach();
    }
}

Attach API位于$JAVA_HOME/lib/tools.jar芹务,所以在編譯時蝉绷,需要將這個jar放入classpath。例如:

javac -cp $JAVA_HOME/lib/tools.jar VMAttacher.java pid /home/admin/agentlib/foo.so opt1,opt2

運行時載入枣抱,虛擬機會在運行時監(jiān)聽并接受 Agent 的加載熔吗,在這個時候,它會使用 Agent 的:

JNIEXPORT jint JNICALL Agent_OnAttach(JavaVM* vm, char *options, void *reserved);

2.1.2 卸載

最后佳晶,Agent 完成任務(wù)桅狠,或者虛擬機關(guān)閉的時候,虛擬機都會調(diào)用一個類似于類析構(gòu)函數(shù)的方法來完成最后的清理任務(wù)

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm)

2.2 JVMTI 環(huán)境和錯誤處理

使用 JVMTI 的過程轿秧,主要是 設(shè)置 JVMTI 環(huán)境中跌,監(jiān)聽虛擬機所產(chǎn)生的事件,以及在某些事件上加上回調(diào)函數(shù)菇篡。

2.2.1 JVMTI 環(huán)境

通過操作 jvmtiCapabilities 來查詢漩符、增加、修改 JVMTI 的環(huán)境參數(shù)驱还。標(biāo)準(zhǔn)的 jvmtiCapabilities 定義了一系列虛擬機的功能陨仅,比如:

  1. can_redefine_any_class:定義了虛擬機是否支持重定義類;
  2. can_retransform_classes:定義了虛擬機是否支持運行的時候改變類定義铝侵;

如果熟悉 Java Instrumentation灼伤,一定不會對此感到陌生,因為 Instrumentation 就是對這些在 Java 層上的包裝咪鲜。對用戶來說狐赡,只需獲得 jvmtiCapabilities指針,就可以查看當(dāng)前 JVMTI 環(huán)境疟丙,了解虛擬機具有的一系列變量功能颖侄。

// 取得 jvmtiCapabilities 指針
err = (*jvmti)->GetCapabilities(jvmti, &capa);
if (err == JVMTI_ERROR_NONE) {
    // 查看是否支持重定義類 
    if (capa.can_redefine_any_class) { ... }
} 

另外鸟雏,虛擬機有自己的一些功能,一開始并未被啟動览祖,那么 增加或修改 jvmtiCapabilities 也是可能的孝鹊,但不同的虛擬機對這個功能的處理也不太一樣,多數(shù)的虛擬機允許增改展蒂,但是有一定的限制又活,比如:僅支持在 Agent_OnLoad 時,即虛擬機啟動時作出锰悼,它某種程度上反映了虛擬機本身的構(gòu)架柳骄。開發(fā)人員無需要考慮 Agent 的性能和內(nèi)存占用,就可以在 Agent 被加載的時候啟用所有功能:

// 取得所有可用的功能
err = (*jvmti)->GetPotentialCapabilities(jvmti, &capa);
if (err == JVMTI_ERROR_NONE) { 
    err = (*jvmti)->AddCapabilities(jvmti, &capa); 
    ... 
}

最后要注意的箕般,JVMTI 的函數(shù)調(diào)用都有其時間性耐薯,即特定的函數(shù)只能在特定的虛擬機狀態(tài)下才能調(diào)用

比如:SuspendThread(掛起線程)這個動作丝里,僅在 Java 虛擬機處于運行狀態(tài)(live phase)才能調(diào)用曲初,否則導(dǎo)致一個內(nèi)部異常。

2.2.2 JVMTI 錯誤處理

JVMTI 沿用了基本的錯誤處理方式杯聚,即使用返回的錯誤代碼通知當(dāng)前的錯誤复斥,幾乎所有的 JVMTI 函數(shù)調(diào)用都具有以下模式:

jvmtiError err = jvmti->someJVMTImethod (somePara … );

其中 err 就是返回的錯誤代碼,不同函數(shù)的錯誤信息可以在 Java 規(guī)范里查到械媒。

2.3 JVMTI 基本功能

JVMTI 的功能非常豐富目锭,包含了 虛擬機中線程、內(nèi)存 / 堆 / 棧纷捞,類 / 方法 / 變量痢虹,事件 / 定時器處理等等 20 多類功能,從功能上大致可以分為4類主儡,如下:

  1. Heap:獲取所有類的信息奖唯,對象信息,對象引用關(guān)系糜值,F(xiàn)ull GC開始/結(jié)束丰捷,對象回收事件等;
  2. 線程與堆棧:獲取所有線程的信息寂汇,線程組信息病往,控制線程(start,suspend,resume,interrupt…), Thread Monitor(Lock),得到線程堆棧骄瓣,控制出棧停巷,方法強制返回,方法棧本地變量等目代;
  3. Class & Object & Method & Field 元信息:class信息牺弄,符號表弄屡,方法表扬霜,redefine class(hotswap), retransform class,object信息莉恼,fields信息囚枪,method信息等发笔;
  4. 工具類:線程cpu消耗缸榛,classloader路徑修改吝羞,系統(tǒng)屬性獲取等;

2.3.1 事件處理和回調(diào)函數(shù)

從上文我們知道仔掸,使用 JVMTI 一個基本的方式就是設(shè)置回調(diào)函數(shù),在某些事件發(fā)生的時候觸發(fā)并作出相應(yīng)的動作医清。

因此這一部分的功能非称鹉海基本,當(dāng)前版本的 JVMTI 提供了許多事件(Event)的回調(diào)会烙,包括 虛擬機初始化负懦、開始運行、結(jié)束柏腻,類的加載纸厉,方法出入,線程始末等等五嫂。如果想對這些事件進(jìn)行處理颗品,需要首先為該事件寫一個函數(shù),然后在 jvmtiEventCallbacks 這個結(jié)構(gòu)中指定相應(yīng)的函數(shù)指針沃缘。

比如:我們對線程啟動感興趣躯枢,并寫了一個 HandleThreadStart 函數(shù),那么我們需要在 Agent_OnLoad 函數(shù)里加入:

jvmtiEventCallbacks eventCallBacks; 
// 初始化
memset(&ecbs, 0, sizeof(ecbs));
// 設(shè)置函數(shù)指針
eventCallBacks.ThreadStart = &HandleThreadStart;
...

在設(shè)置了這些回調(diào)之后槐臀,就可以調(diào)用下述方法锄蹂,來最終完成設(shè)置。在接下來的虛擬機運行過程中水慨,一旦有線程開始運行發(fā)生得糜,虛擬機就會回調(diào) HandleThreadStart 方法。

jvmti->SetEventCallbacks(eventCallBacks, sizeof(eventCallBacks));

設(shè)置回調(diào)函數(shù)的時候晰洒,開發(fā)者需要注意以下幾點

  1. 如同 Java 異常機制一樣朝抖,如果在回調(diào)函數(shù)中自己拋出一個異常(Exception),或者在調(diào)用 JNI 函數(shù)的時候制造了一些麻煩谍珊,讓 JNI 丟出了一個異常槽棍,那么 任何在回調(diào)之前發(fā)生的異常就會丟失,這就要求開發(fā)人員要在處理錯誤的時候需要當(dāng)心
  2. 虛擬機不保證回調(diào)函數(shù)會被同步炼七,換句話說缆巧,程序有可能同時運行同一個回調(diào)函數(shù)(比如,好幾個線程同時開始運行了豌拙,這個 HandleThreadStart 就會被同時調(diào)用幾次)陕悬,那么開發(fā)人員在開發(fā)回調(diào)函數(shù)時需要處理同步的問題

2.3.2 內(nèi)存控制和對象獲取

內(nèi)存控制是一切運行態(tài)的基本功能按傅。 JVMTI 除了提供最簡單的內(nèi)存申請和撤銷之外(這塊內(nèi)存不受 Java 堆管理捉超,開發(fā)人員需要自行進(jìn)行清理工作,不然會造成內(nèi)存泄漏)唯绍,也提供了對 Java 堆的操作拼岳。

眾所周知,Java 堆中存儲了 Java 的類况芒、對象和基本類型(Primitive)惜纸,通過對堆的操作,開發(fā)人員可以很容易的查找任意的類绝骚、對象耐版,甚至可以強行執(zhí)行垃圾收集工作

JVMTI 中對 Java 堆的操作與眾不同压汪,它沒有提供一個直接獲取的方式(由此可見粪牲,虛擬機對對象的管理并非是哈希表,而是某種樹 / 圖方式)止剖,而是使用一個迭代器(iterater)的方式遍歷:

jvmtiError FollowReferences(jvmtiEnv* env, 
    jint heap_filter, 
    jclass klass, 
    jobject initial_object,// 該方式可以指定根節(jié)點
    const jvmtiHeapCallbacks* callbacks,// 設(shè)置回調(diào)函數(shù)
    const void* user_data)

或者

jvmtiError IterateThroughHeap(jvmtiEnv* env, 
    jint heap_filter, 
    jclass klass, 
    const jvmtiHeapCallbacks* callbacks, 
    const void* user_data)// 遍歷整個 heap

在遍歷的過程中腺阳,開發(fā)者可以設(shè)定一定的條件,比如:指定是某一個類的對象穿香,并設(shè)置一個回調(diào)函數(shù)舌狗,如果條件被滿足,回調(diào)函數(shù)就會被執(zhí)行扔水。開發(fā)者可以在回調(diào)函數(shù)中對當(dāng)前傳回的指針進(jìn)行打標(biāo)記(tag)操作——這又是一個特殊之處痛侍,在第一遍遍歷中,只能對滿足條件的對象進(jìn)行 tag 魔市;然后再使用 GetObjectsWithTags 函數(shù)主届,獲取需要的對象。

jvmtiError GetObjectsWithTags(jvmtiEnv* env, 
    jint tag_count, 
    const jlong* tags, // 設(shè)定特定的 tag待德,即我們上面所設(shè)置的
    jint* count_ptr, 
    jobject** object_result_ptr, 
    jlong** tag_result_ptr)

如果你僅僅想對特定 Java 對象操作君丁,應(yīng)該避免設(shè)置其他類型的回調(diào)函數(shù),否則會影響效率将宪,舉例來說绘闷,多增加一個 primitive 的回調(diào)函數(shù)橡庞,可能會使整個操作效率下降一個數(shù)量級。

2.3.3 線程和鎖

線程是 Java 運行態(tài)中非常重要的一個部分印蔗,在 JVMTI 中也提供了很多 API 進(jìn)行相應(yīng)的操作扒最,包括查詢當(dāng)前線程狀態(tài),暫停华嘹,恢復(fù)或者終端線程吧趣,還可以對線程鎖進(jìn)行操作。

開發(fā)者可以獲得特定線程所擁有的鎖:

jvmtiError GetOwnedMonitorInfo(jvmtiEnv* env, 
    jthread thread, 
    jint* owned_monitor_count_ptr, 
    jobject** owned_monitors_ptr)

也可以獲得當(dāng)前線程正在等待的鎖:

jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env, 
    jthread thread, 
    jobject* monitor_ptr)

知道這些信息耙厚,事實上我們也可以設(shè)計自己的算法來判斷是否死鎖强挫。更重要的是,JVMTI 提供了一系列的監(jiān)視器(Monitor)操作薛躬,來幫助我們在 native 環(huán)境中實現(xiàn)同步俯渤。

主要操作:構(gòu)建監(jiān)視器(CreateRawMonitor),獲取監(jiān)視器(RawMonitorEnter)型宝,釋放監(jiān)視器(RawMonitorExit)八匠,等待和喚醒監(jiān)視器 (RawMonitorWait,RawMonitorNotify) 等操作,通過這些簡單鎖诡曙,程序的同步操作可以得到保證臀叙。

2.3.4 調(diào)試功能

調(diào)試功能是 JVMTI 的基本功能之一略水,這主要包括了設(shè)置斷點价卤、調(diào)試(step)等,在 JVMTI 里面渊涝,設(shè)置斷點的 API 本身很簡單:

jvmtiError SetBreakpoint(jvmtiEnv* env, 
    jmethodID method, 
    jlocation location)

jlocation 這個數(shù)據(jù)結(jié)構(gòu)在這里代表的是對應(yīng)方法中一個可執(zhí)行代碼的行數(shù)慎璧。在斷點發(fā)生的時候,虛擬機會觸發(fā)一個事件跨释,開發(fā)者可以使用在上文中介紹過的方式對事件進(jìn)行處理胸私。

2.3.5 JVMTI 數(shù)據(jù)結(jié)構(gòu)

JVMTI 中使用的數(shù)據(jù)結(jié)構(gòu),首先也是一些標(biāo)準(zhǔn)的 JNI 數(shù)據(jù)結(jié)構(gòu)鳖谈,比如:jint岁疼,jlong ;其次缆娃,JVMTI 也定義了一些基本類型捷绒,比如:jthread,表示一個 thread贯要,jvmtiEvent暖侨,表示 jvmti 所定義的事件;更復(fù)雜的有 JVMTI 的一些需要用結(jié)構(gòu)體表示的數(shù)據(jù)結(jié)構(gòu)崇渗,比如:堆的信息(jvmtiStackInfo)字逗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末京郑,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子葫掉,更是在濱河造成了極大的恐慌些举,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件挖息,死亡現(xiàn)場離奇詭異金拒,居然都是意外死亡,警方通過查閱死者的電腦和手機套腹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評論 3 399
  • 文/潘曉璐 我一進(jìn)店門绪抛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人电禀,你說我怎么就攤上這事幢码。” “怎么了尖飞?”我有些...
    開封第一講書人閱讀 168,766評論 0 361
  • 文/不壞的土叔 我叫張陵症副,是天一觀的道長。 經(jīng)常有香客問我政基,道長贞铣,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評論 1 299
  • 正文 為了忘掉前任沮明,我火速辦了婚禮辕坝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘荐健。我一直安慰自己酱畅,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,871評論 6 398
  • 文/花漫 我一把揭開白布江场。 她就那樣靜靜地躺著纺酸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪址否。 梳的紋絲不亂的頭發(fā)上餐蔬,一...
    開封第一講書人閱讀 52,457評論 1 311
  • 那天,我揣著相機與錄音佑附,去河邊找鬼樊诺。 笑死,一個胖子當(dāng)著我的面吹牛帮匾,可吹牛的內(nèi)容都是我干的啄骇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,999評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼瘟斜,長吁一口氣:“原來是場噩夢啊……” “哼缸夹!你這毒婦竟也來了痪寻?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評論 0 277
  • 序言:老撾萬榮一對情侶失蹤虽惭,失蹤者是張志新(化名)和其女友劉穎橡类,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體芽唇,經(jīng)...
    沈念sama閱讀 46,465評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡顾画,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,543評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了匆笤。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片研侣。...
    茶點故事閱讀 40,675評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖炮捧,靈堂內(nèi)的尸體忽然破棺而出庶诡,到底是詐尸還是另有隱情,我是刑警寧澤咆课,帶...
    沈念sama閱讀 36,354評論 5 351
  • 正文 年R本政府宣布末誓,位于F島的核電站,受9級特大地震影響书蚪,放射性物質(zhì)發(fā)生泄漏喇澡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 42,029評論 3 335
  • 文/蒙蒙 一殊校、第九天 我趴在偏房一處隱蔽的房頂上張望晴玖。 院中可真熱鬧,春花似錦箩艺、人聲如沸窜醉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拜英,卻和暖如春静汤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背居凶。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評論 1 274
  • 我被黑心中介騙來泰國打工虫给, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侠碧。 一個月前我還...
    沈念sama閱讀 49,091評論 3 378
  • 正文 我出身青樓抹估,卻偏偏與公主長得像,于是被迫代替她去往敵國和親弄兜。 傳聞我的和親對象是個殘疾皇子药蜻,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,685評論 2 360

推薦閱讀更多精彩內(nèi)容