jvmti_拥有您的堆:使用JVMTI迭代类实例
jvmti
今天,我想談一談我們大多數(shù)人每天都不會(huì)看到和使用的另一種Java,更確切地說(shuō),是有關(guān)較低級(jí)別的綁定,一些本機(jī)代碼以及如何執(zhí)行一些小的魔術(shù)。 盡管我們不會(huì)在JVM上找到真正的魔力源,但是在單個(gè)帖子的范圍內(nèi)可以實(shí)現(xiàn)一些小奇跡。
我花了很多時(shí)間在ZeroTurnaround的RebelLabs團(tuán)隊(duì)中進(jìn)行研究,編寫和編碼,該公司為Java開(kāi)發(fā)人員創(chuàng)建工具,這些工具主要以javaagents的身份運(yùn)行。 通常情況下,如果您想在不重寫JVM的情況下增強(qiáng)JVM或在JVM上獲得任何強(qiáng)大的功能,則必須深入研究Java代理的美麗世界。 它們有兩種形式:Java javaagents和本機(jī)Javaagents。 在這篇文章中,我們將集中討論后者。
注意, XRebel產(chǎn)品負(fù)責(zé)人Anton Arhipov的這個(gè)GeeCON Prague演示文稿是學(xué)習(xí)完全用Java編寫的Javaagents的一個(gè)很好的起點(diǎn): 與Javassist一起玩 。
在本文中,我們將創(chuàng)建一個(gè)小型的本機(jī)JVM代理,探討將本機(jī)方法公開(kāi)到Java應(yīng)用程序中的可能性,并了解如何利用Java虛擬機(jī)工具接口 。
如果您正在尋找帖子的實(shí)用內(nèi)容,我們將能夠在擾亂警報(bào)的情況下計(jì)算堆中存在給定類的實(shí)例數(shù)量。
想象一下,您是圣誕老人值得信賴的黑客精靈,而這位大人物對(duì)您來(lái)說(shuō)面臨以下挑戰(zhàn):
圣誕老人: 我親愛(ài)的Hacker Elf,您能否編寫一個(gè)程序來(lái)指出JVM堆中當(dāng)前隱藏了多少個(gè)Thread對(duì)象?
另一個(gè)不愿意挑戰(zhàn)自己的小精靈會(huì)回答: 這很容易直接,對(duì)嗎?
return Thread.getAllStackTraces().size();但是,如果我們想對(duì)我們的解決方案進(jìn)行過(guò)度設(shè)計(jì)以能夠回答有關(guān)任何給定類的問(wèn)題,該怎么辦? 說(shuō)我們要實(shí)現(xiàn)以下接口?
public interface HeapInsight {int countInstances(Class klass); }是的,那是不可能的,對(duì)吧? 如果您收到String.class作為參數(shù)怎么辦? 不用擔(dān)心,我們只需要更深入地研究JVM的內(nèi)部結(jié)構(gòu)。 JVMTI作者可以使用的一件事是JVMTI (Java虛擬機(jī)工具接口)。 它是很久以前添加的,許多看似神奇的工具都在使用它。 JVMTI提供了兩件事:
- 本機(jī)API
- 一種工具API,用于監(jiān)視和轉(zhuǎn)換裝入JVM的類的字節(jié)碼。
就我們的示例而言,我們需要訪問(wèn)本機(jī)API。 我們要使用的是IterateThroughHeap函數(shù),該函數(shù)使我們可以提供一個(gè)自定義回調(diào),以對(duì)給定類的每個(gè)對(duì)象執(zhí)行該回調(diào)。
首先,讓我們創(chuàng)建一個(gè)本地代理,該代理將加載和回顯某些內(nèi)容,以確保我們的基礎(chǔ)架構(gòu)能夠正常工作。
本機(jī)代理程序是用C / C ++編寫的,并被編譯成動(dòng)態(tài)庫(kù),以便在我們甚至開(kāi)始考慮Java之前就進(jìn)行加載。 如果您不精通C ++,請(qǐng)不要擔(dān)心,沒(méi)有很多精靈,也不會(huì)很難。 我的C ++方法包括2種主要策略:巧合編程和避免段錯(cuò)誤。 因此,由于我設(shè)法編寫了這篇文章的示例代碼并對(duì)其進(jìn)行了注釋,因此我們可以一起研究一下。 注意:以上段落應(yīng)作為免責(zé)聲明,請(qǐng)勿將此代碼置于任何對(duì)您有價(jià)值的環(huán)境中。
這是創(chuàng)建第一個(gè)本機(jī)代理的方法:
#include #include using namespace std;JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {cout << "A message from my SuperAgent!" << endl;return JNI_OK; }該聲明的重要部分是聲明一個(gè)名為Agent_OnLoad的函數(shù),該函數(shù)遵循動(dòng)態(tài)鏈接的代理的文檔 。
將文件另存為例如native-agent.cpp ,讓我們看看我們可以做些什么來(lái)變成一個(gè)庫(kù)。
我在OSX上,因此我使用clang對(duì)其進(jìn)行編譯,以節(jié)省一些時(shí)間,下面是完整的命令:
clang -shared -undefined dynamic_lookup -o agent.so -I /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/include/ -I /Library/Java/JavaVirtualMachines/jdk1.8.0.jdk/Contents/Home/include/darwin native-agent.cpp這將創(chuàng)建一個(gè)agent.so文件,該文件是可以為我們服務(wù)的庫(kù)。 為了測(cè)試它,讓我們創(chuàng)建一個(gè)虛擬的hello world Java類。
package org.shelajev; public class Main {public static void main(String[] args) {System.out.println("Hello World!");} }當(dāng)你用正確的-agentpath選項(xiàng)指向agent.so運(yùn)行它,你應(yīng)該看到下面的輸出:
java -agentpath:agent.so org.shelajev.Main A message from my SuperAgent! Hello World!很好! 現(xiàn)在,我們擁有一切使之真正有用的地方。 首先,我們需要一個(gè)jvmtiEnv實(shí)例,當(dāng)我們位于Agent_OnLoad中時(shí) ,可以通過(guò)JavaVM * jvm獲得該實(shí)例 ,但以后將不可用。 因此,我們必須將其存儲(chǔ)在全球可訪問(wèn)的位置。 我們通過(guò)聲明一個(gè)全局結(jié)構(gòu)來(lái)存儲(chǔ)它。
#include #include using namespace std;typedef struct {jvmtiEnv *jvmti; } GlobalAgentData;static GlobalAgentData *gdata;JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {jvmtiEnv *jvmti = NULL;jvmtiCapabilities capa;jvmtiError error;// put a jvmtiEnv instance at jvmti.jint result = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_1_1);if (result != JNI_OK) {printf("ERROR: Unable to access JVMTI!\n");}// add a capability to tag objects(void)memset(∩a, 0, sizeof(jvmtiCapabilities));capa.can_tag_objects = 1;error = (jvmti)->AddCapabilities(∩a);// store jvmti in a global datagdata = (GlobalAgentData*) malloc(sizeof(GlobalAgentData));gdata->jvmti = jvmti;return JNI_OK; }我們還更新了代碼,以添加標(biāo)記對(duì)象的功能,這是我們遍歷堆所需的。 現(xiàn)在準(zhǔn)備工作已經(jīng)完成,我們已經(jīng)初始化了JVMTI實(shí)例并且可供我們使用。 讓我們通過(guò)JNI將其提供給我們的Java代碼。
JNI代表Java本機(jī)接口 ,這是將本機(jī)代碼調(diào)用包含到Java應(yīng)用程序中的一種標(biāo)準(zhǔn)方式。 Java部分將非常簡(jiǎn)單明了,將以下countInstances方法定義添加到Main類:
package org.shelajev;public class Main {public static void main(String[] args) {System.out.println("Hello World!");int a = countInstances(Thread.class);System.out.println("There are " + a + " instances of " + Thread.class);}private static native int countInstances(Class klass); }為了適應(yīng)本機(jī)方法,我們必須更改本機(jī)代理代碼。 我將在稍后解釋,但現(xiàn)在在其中添加以下函數(shù)定義:
extern "C" JNICALL jint objectCountingCallback(jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data) {int* count = (int*) user_data;*count += 1; return JVMTI_VISIT_OBJECTS; }extern "C" JNIEXPORT jint JNICALL Java_org_shelajev_Main_countInstances(JNIEnv *env, jclass thisClass, jclass klass) {int count = 0;jvmtiHeapCallbacks callbacks; (void)memset(&callbacks, 0, sizeof(callbacks)); callbacks.heap_iteration_callback = &objectCountingCallback;jvmtiError error = gdata->jvmti->IterateThroughHeap(0, klass, &callbacks, &count);return count; }Java_org_shelajev_Main_countInstances在這里更有趣,它的名稱遵循約定,以Java_開(kāi)頭,然后是_分隔的完全限定的類名,然后是Java代碼中的方法名。 另外,請(qǐng)不要忘記JNIEXPORT聲明,該聲明指出該函數(shù)已導(dǎo)出到Java世界中。
在Java_org_shelajev_Main_countInstances內(nèi)部,我們將objectCountingCallback函數(shù)指定為回調(diào),并使用Java應(yīng)用程序中的參數(shù)調(diào)用IterateThroughHeap 。
請(qǐng)注意,我們的本機(jī)方法是靜態(tài)的,因此C對(duì)應(yīng)項(xiàng)中的參數(shù)為:
JNIEnv *env, jclass thisClass, jclass klass對(duì)于實(shí)例方法,它們將有所不同:
JNIEnv *env, jobj thisInstance, jclass klass這里的thisInstance指向Java方法調(diào)用的this對(duì)象。
現(xiàn)在, objectCountingCallback的定義直接來(lái)自文檔 。 身體無(wú)非就是增加一個(gè)int。
繁榮! 全做完了! 感謝您的耐心等待。 如果您仍在閱讀本文,則可以測(cè)試上面的所有代碼。
再次編譯本機(jī)代理并運(yùn)行Main類。 這是我看到的:
java -agentpath:agent.so org.shelajev.Main Hello World! There are 7 instances of class java.lang.Thread如果我添加一個(gè)線程t = new Thread(); 行到main方法,我在堆上看到8個(gè)實(shí)例。 聽(tīng)起來(lái)好像真的可行。 您的線程數(shù)幾乎肯定會(huì)有所不同,不用擔(dān)心,這是正常現(xiàn)象,因?yàn)樗_實(shí)計(jì)入了JVM簿記線程,進(jìn)行編譯,GC等操作。
現(xiàn)在,如果我要計(jì)算堆上String實(shí)例的數(shù)量,只需更改參數(shù)類即可。 我希望圣誕老人是一個(gè)真正通用的解決方案。
哦,如果您有興趣,它會(huì)為我找到2423個(gè)String實(shí)例。 對(duì)于小型應(yīng)用程序來(lái)說(shuō),這個(gè)數(shù)字相當(dāng)高。 也,
return Thread.getAllStackTraces().size();給我5個(gè)而不是8個(gè),因?yàn)樗话ú居浘€程! 談?wù)摤嵥榈慕鉀Q方案,是嗎?
現(xiàn)在,您已經(jīng)掌握了這些知識(shí),并且知道了本教程,并不是說(shuō)您已經(jīng)準(zhǔn)備好編寫自己的JVM監(jiān)視或增強(qiáng)工具,但這絕對(duì)是一個(gè)開(kāi)始。
在本文中,我們從零開(kāi)始編寫了本機(jī)Java代理,該代理成功編譯,加載和運(yùn)行。 它使用JVMTI來(lái)獲取無(wú)法通過(guò)其他方式訪問(wèn)的JVM的見(jiàn)解。 相應(yīng)的Java代碼調(diào)用本機(jī)庫(kù)并解釋結(jié)果。
這通常是最神奇的JVM工具所采用的方法,我希望其中的一些魔術(shù)已為您揭開(kāi)神秘面紗。
翻譯自: https://www.javacodegeeks.com/2014/12/own-your-heap-iterate-class-instances-with-jvmti.html
jvmti
總結(jié)
以上是生活随笔為你收集整理的jvmti_拥有您的堆:使用JVMTI迭代类实例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ps5 pro发售日期(ps5 pro发
- 下一篇: 十二星座前世是什么神(双鱼座是古代公主)