JVM TI: cara membuat plugin untuk mesin virtual



Apakah Anda ingin menambahkan beberapa fitur yang bermanfaat ke JVM? Secara teoritis, setiap pengembang dapat berkontribusi pada OpenJDK, namun dalam praktiknya, setiap perubahan non-sepele untuk HotSpot tidak diterima dari samping, dan bahkan dengan siklus rilis yang diperpendek saat ini, mungkin diperlukan bertahun-tahun sebelum pengguna JDK melihat fitur Anda.

Namun demikian, dalam beberapa kasus dimungkinkan untuk memperluas fungsionalitas mesin virtual tanpa menyentuh kodenya. Antarmuka Alat JVM, API standar untuk berinteraksi dengan JVM, membantu.

Dalam artikel ini saya akan menunjukkan dengan contoh nyata apa yang bisa dilakukan dengan itu, memberi tahu apa yang telah berubah di Jawa 9 dan 11, dan jujur ​​memperingatkan tentang kesulitan (spoiler: saya harus berurusan dengan C ++).

Saya juga berbicara tentang materi ini di JPoint. Jika Anda lebih suka video, Anda dapat menonton laporan video .

Entri


Jejaring sosial Odnoklassniki, tempat saya bekerja sebagai insinyur terkemuka, hampir seluruhnya ditulis di Jawa. Tetapi hari ini saya akan memberi tahu Anda tentang bagian lain, yang tidak sepenuhnya di Jawa.

Seperti yang Anda ketahui, masalah paling populer dengan pengembang Java adalah NullPointerException. Suatu ketika, saat bertugas di portal, saya juga menemukan NPE dalam produksi. Kesalahan disertai dengan sesuatu seperti jejak tumpukan ini:



Tentu saja, pada stack stack, Anda dapat melacak tempat di mana pengecualian terjadi hingga baris tertentu dalam kode. Hanya dalam kasus ini tidak membuat saya merasa lebih baik, karena di sini NPE dapat bertemu banyak di mana:



Akan lebih bagus jika JVM menyarankan persis di mana kesalahan ini, misalnya, seperti ini:
java.lang.NullPointerException: Called 'getUsers()' method on null object

Tapi, sayangnya, NPE sekarang tidak mengandung hal semacam itu. Meskipun mereka telah meminta ini sejak lama, setidaknya dengan Java 1.4: bug ini telah berusia 16 tahun. Secara berkala, semakin banyak bug dibuka pada topik ini, tetapi bug itu selalu ditutup sebagai "Tidak akan Perbaiki":



Ini tidak terjadi di mana-mana. Volker Simonis dari SAP mengatakan bagaimana mereka telah mengimplementasikan fitur ini di SAP JVM sejak lama dan membantunya lebih dari sekali. Karyawan SAP lainnya sekali lagi mengirimkan bug di OpenJDK dan mengajukan diri untuk menerapkan mekanisme yang mirip dengan apa yang ada di SAP JVM. Dan, lihatlah, kali ini bug tidak ditutup - ada kemungkinan fitur ini akan memasuki JDK 14.

Tetapi kapan JDK 14 akan dirilis, dan kapan kita akan beralih ke itu? Apa yang harus dilakukan jika Anda ingin menyelidiki masalah di sini dan sekarang?

Anda tentu saja dapat mempertahankan garpu OpenJDK Anda. Fitur pelaporan NPE itu sendiri tidak begitu rumit, kami bisa menerapkannya dengan sangat baik. Tetapi pada saat yang sama, akan ada semua masalah untuk mendukung majelis Anda sendiri. Akan sangat bagus untuk mengimplementasikan fitur sekali, dan kemudian cukup menghubungkannya ke versi JVM sebagai plugin. Dan ini sangat mungkin! JVM memiliki API khusus (awalnya dikembangkan untuk semua jenis debugger dan profiler): JVM Tool Interface.

Yang terpenting, API ini standar. Dia memiliki spesifikasi yang ketat, dan ketika mengimplementasikan fitur sesuai dengan itu, Anda dapat yakin bahwa itu akan berfungsi dalam versi baru JVM.

Untuk menggunakan antarmuka ini, Anda perlu menulis program kecil (atau besar, tergantung pada apa tugas Anda). Asli: biasanya ditulis dalam C atau C ++. jdk/include/jvmti.h JDK standar memiliki file header jdk/include/jvmti.h yang ingin Anda sertakan.

Program dikompilasi ke dalam perpustakaan dinamis, dan dihubungkan oleh parameter -agentpath selama dimulainya JVM. Penting untuk tidak membingungkannya dengan parameter lain yang serupa: -javaagent . Bahkan, agen Java adalah kasus khusus agen JVM TI. Lebih lanjut dalam teks di bawah kata "agen" yang dimaksud justru agen asli.

Mulai dari mana


Mari kita lihat dalam praktiknya bagaimana menulis agen TI JVM yang paling sederhana, semacam "halo dunia".

 #include <jvmti.h> #include <stdio.h> JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { jvmtiEnv* jvmti; vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0); char* vm_name = NULL; jvmti->GetSystemProperty("java.vm.name", &vm_name); printf("Agent loaded. JVM name = %s\n", vm_name); fflush(stdout); return 0; } 

Baris pertama saya sertakan file header yang sama. Berikutnya adalah fungsi utama yang perlu diimplementasikan dalam agen: Agent_OnLoad() . Mesin virtual itu sendiri menyebutnya ketika agen melakukan boot, melewati sebuah pointer ke objek JavaVM* .

Dengan menggunakannya, Anda bisa mendapatkan pointer ke lingkungan JVM TI: jvmtiEnv* . Dan melaluinya, pada gilirannya, sudah memanggil fungsi-JVM TI. Misalnya, menggunakan GetSystemProperty, baca nilai properti sistem.

Jika sekarang saya menjalankan "hello world" ini, meneruskan file dll yang dikompilasi ke -agentpath , baris yang dicetak oleh agen kami akan muncul di konsol sebelum program Java mulai berjalan:



Pengayaan NPE


Karena hello world bukan contoh yang paling menarik, mari kembali ke pengecualian kami. Kode agen lengkap yang melengkapi laporan NPE ada di GitHub .

Beginilah Agent_OnLoad() jika saya ingin meminta mesin virtual untuk memberi tahu kami tentang semua pengecualian:

 JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM* vm, char* options, void* reserved) { jvmtiEnv* jvmti; vm->GetEnv((void**) &jvmti, JVMTI_VERSION_1_0); jvmtiCapabilities capabilities = {0}; capabilities.can_generate_exception_events = 1; jvmti->AddCapabilities(&capabilities); jvmtiEventCallbacks callbacks = {0}; callbacks.Exception = ExceptionCallback; jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_EXCEPTION, NULL); return 0; } 

Pertama saya meminta JVM TI untuk kapabilitas yang sesuai (can_generate_exception_events). Kami akan berbicara tentang kemampuan secara terpisah.

Langkah selanjutnya adalah berlangganan acara Pengecualian. Setiap kali JVM melempar pengecualian (tidak peduli apakah mereka tertangkap atau tidak), fungsi ExceptionCallback() akan dipanggil.

Langkah terakhir adalah memanggil SetEventNotificationMode() untuk memungkinkan pengiriman pemberitahuan.

Dalam ExceptionCallback, JVM melewati semua yang kita butuhkan untuk menangani pengecualian.
 void JNICALL ExceptionCallback(jvmtiEnv* jvmti, JNIEnv* env, jthread thread, jmethodID method, jlocation location, jobject exception, jmethodID catch_method, jlocation catch_location) { jclass NullPointerException = env->FindClass("java/lang/NullPointerException"); if (!env->IsInstanceOf(exception, NullPointerException)) { return; } jclass Throwable = env->FindClass("java/lang/Throwable"); jfieldID detailMessage = env->GetFieldID(Throwable, "detailMessage", "Ljava/lang/String;"); if (env->GetObjectField(exception, detailMessage) != NULL) { return; } char buf[32]; sprintf(buf, "at location %id", (int) location); env->SetObjectField(exception, detailMessage, env->NewStringUTF(buf)); } 


Di sini ada objek dari thread yang melemparkan exception (thread), dan tempat terjadinya hal ini (metode, lokasi), dan objek pengecualian (pengecualian), dan bahkan tempat dalam kode yang menangkap pengecualian ini (catch_method, catch_location).

Yang penting: dalam panggilan balik ini, selain penunjuk ke lingkungan TI JVM, lingkungan JNI (env) juga dilewati. Ini berarti bahwa kita dapat menggunakan semua fungsi JNI di dalamnya. Artinya, JVM TI dan JNI hidup berdampingan dengan sempurna, saling melengkapi.

Dalam agen saya, saya menggunakan keduanya. Secara khusus, melalui JNI saya memeriksa bahwa pengecualian saya adalah tipe NullPointerException , dan kemudian saya mengganti bidang detailMessage pesan kesalahan.

Karena JVM itu sendiri melewati kita lokasi - indeks bytecode di mana pengecualian terjadi, maka saya hanya menempatkan lokasi ini di sini di pesan:



Angka 66 menunjukkan indeks dalam bytecode di mana pengecualian ini terjadi. Tetapi menganalisis bytecode secara manual itu suram: Anda perlu mendekompilasi file kelas, mencari instruksi ke-66, mencoba memahami apa yang dilakukannya ... Akan sangat bagus jika agen kami sendiri dapat menunjukkan sesuatu yang lebih dapat dibaca oleh manusia.

Namun, dalam hal ini, JVM TI memiliki semua yang Anda butuhkan. Benar, Anda harus meminta fitur tambahan dari JVM TI: dapatkan metode bytecode dan pool konstan.

 jvmtiCapabilities capabilities = {0}; capabilities.can_generate_exception_events = 1; capabilities.can_get_bytecodes = 1; capabilities.can_get_constant_pool = 1; jvmti->AddCapabilities(&capabilities); 

Sekarang saya akan memperluas ExceptionCallback: melalui fungsi JVM TI GetBytecodes() Saya akan mendapatkan tubuh metode untuk memeriksa apa yang ada di dalamnya dengan indeks lokasi. Berikutnya datang instruksi bytecode sakelar besar: jika ini adalah akses ke array, akan ada satu pesan kesalahan, jika akses ke bidang adalah pesan lain, jika pemanggilan metode adalah yang ketiga, dan seterusnya.

Kode ExceptionCallback
 jint bytecode_count; u1* bytecodes; if (jvmti->GetBytecodes(method, &bytecode_count, &bytecodes) != 0) { return; } if (location >= 0 && location < bytecode_count) { const char* message = get_exception_message(bytecodes[location]); if (message != NULL) { ... env->SetObjectField(exception, detailMessage, env->NewStringUTF(buf)); } } jvmti->Deallocate(bytecodes); 


Tetap hanya untuk mengganti nama bidang atau metode. Anda bisa mendapatkannya dari kumpulan konstan , yang tersedia lagi berkat JVM TI.

 if (jvmti->GetConstantPool(holder, &cpool_count, &cpool_bytes, &cpool) != 0) { return strdup("<unknown>"); } 

Berikutnya datang sedikit keajaiban, tetapi pada kenyataannya tidak ada yang rumit, hanya sesuai dengan spesifikasi format file kelas kami menganalisis kumpulan konstan dan dari sana kami mengisolasi garis - nama metode.

Analisis kumpulan konstan
 u1* ref = get_cpool_at(cpool, get_u2(bytecodes + 1)); // CONSTANT_Fieldref u1* name_and_type = get_cpool_at(cpool, get_u2(ref + 3)); // CONSTANT_NameAndType u1* name = get_cpool_at(cpool, get_u2(name_and_type + 1)); // CONSTANT_Utf8 size_t name_length = get_u2(name + 1); char* result = (char*) malloc(name_length + 1); memcpy(result, name + 3, name_length); result[name_length] = 0; 


Poin penting lainnya: beberapa fungsi JVM TI, misalnya GetConstantPool() atau GetBytecodes() , mengalokasikan struktur tertentu dalam memori asli, yang perlu dibebaskan ketika Anda selesai bekerja dengannya.

 jvmti->Deallocate(cpool); 

Jalankan program sumber dengan agen kami yang diperluas, dan ini adalah deskripsi yang sangat berbeda dari pengecualian: ia melaporkan bahwa kami memanggil metode longValue () pada objek nol.



Aplikasi lain


Secara umum, pengembang sering ingin menangani pengecualian dengan caranya sendiri. Sebagai contoh, secara otomatis restart JVM jika StackOverflowError .

Keinginan ini dapat dipahami, karena StackOverflowError adalah kesalahan fatal yang sama dengan OutOfMemoryError , setelah kejadiannya, tidak mungkin lagi menjamin operasi program yang benar. Atau, misalnya, terkadang untuk menganalisis masalah, saya ingin menerima dump thread atau heap dump ketika pengecualian terjadi.



Dalam keadilan, JDK IBM memiliki peluang seperti itu di luar kebiasaan. Tapi sekarang kita sudah tahu bahwa menggunakan agen TI JVM, Anda dapat menerapkan hal yang sama di HotSpot. Cukup dengan berlangganan callback pengecualian dan menganalisis pengecualian. Tetapi bagaimana cara menghapus thread dump atau heap dump dari agen kami? JVM TI memiliki semua yang Anda butuhkan untuk kasus ini:



Sangat tidak nyaman untuk menerapkan seluruh mekanisme melewati tumpukan dan membuat dump. Tetapi saya akan membagikan rahasia cara membuatnya lebih mudah dan lebih cepat. Benar, ini tidak lagi termasuk dalam standar JVM TI, tetapi merupakan ekstensi pribadi Hotspot.

Anda perlu menghubungkan file header jmm.h dari sumber HotSpot dan memanggil fungsi JVM_GetManagement() :

 #include "jmm.h" JNIEXPORT void* JNICALL JVM_GetManagement(jint version); void JNICALL ExceptionCallback(jvmtiEnv* jvmti, JNIEnv* env, ...) { JmmInterface* jmm = (JmmInterface*) JVM_GetManagement(JMM_VERSION_1_0); jmm->DumpHeap0(env, env->NewStringUTF("dump.hprof"), JNI_FALSE); } 

Ini akan mengembalikan pointer ke HotSpot Management Interface, yang dalam satu panggilan akan menghasilkan Heap Dump atau Thread Dump. Kode lengkap untuk contoh dapat ditemukan dalam jawaban saya untuk Stack Overflow.

Secara alami, Anda dapat menangani tidak hanya pengecualian, tetapi juga banyak peristiwa lainnya yang terkait dengan operasi JVM: memulai / menghentikan thread, kelas pemuatan, pengumpulan sampah, metode kompilasi, metode masuk / keluar, bahkan mengakses atau memodifikasi bidang khusus objek Java.

Saya punya contoh agen vmtrace lain yang berlangganan banyak acara JVM TI standar dan mencatatnya. Jika saya menjalankan program sederhana dengan agen ini, saya akan mendapatkan log terperinci, yang bila selesai, dengan cap waktu:



Seperti yang Anda lihat, untuk sekadar mencetak hello world, ratusan kelas dimuat, puluhan dan ratusan metode dihasilkan dan dikompilasi. Menjadi jelas mengapa Java membutuhkan waktu begitu lama untuk dijalankan. Segala sesuatu tentang semuanya membutuhkan lebih dari dua ratus milidetik.

Apa yang bisa dilakukan JVM TI


Selain penanganan acara, JVM TI memiliki banyak fitur lainnya. Mereka dapat dibagi menjadi dua kelompok.

Salah satunya adalah wajib, yang harus diterapkan oleh JVM mana pun yang mendukung JVM TI. Ini termasuk operasi menganalisis metode, bidang, aliran, kemampuan untuk menambahkan kelas baru ke classpath, dan sebagainya.

Ada fitur opsional yang membutuhkan permintaan kemampuan pendahuluan. JVM tidak diperlukan untuk mendukung semuanya, namun HotSpot mengimplementasikan seluruh spesifikasi secara penuh. Fitur opsional dibagi menjadi dua subkelompok: yang dapat dihubungkan hanya pada awal JVM (misalnya, kemampuan untuk mengatur breakpoint atau menganalisis variabel lokal), dan yang dapat dihubungkan kapan saja (khususnya, bytecode atau pool konstan, yang saya digunakan di atas).



Anda mungkin memperhatikan bahwa daftar fitur sangat mirip dengan fitur debugger. Sebenarnya, debugger Java tidak lebih dari kasus khusus agen JVM TI, yang memanfaatkan semua kemampuan ini dan meminta semua kemampuan.

Pemisahan kapabilitas menjadi kapabilitas yang dapat diaktifkan kapan saja, dan kapabilitas yang hanya pada saat boot, dilakukan dengan sengaja. Tidak semua fitur gratis, ada yang membawa overhead.

Jika semuanya jelas dengan overhead langsung yang menyertai penggunaan fitur, maka ada yang tidak langsung bahkan lebih jelas yang muncul bahkan jika Anda tidak menggunakan fitur, tetapi hanya melalui kemampuan Anda menyatakan bahwa itu akan dibutuhkan suatu saat di masa depan. Ini disebabkan oleh fakta bahwa mesin virtual dapat mengkompilasi kode secara berbeda atau menambahkan pemeriksaan tambahan ke runtime.

Misalnya, kemampuan yang sudah dianggap untuk berlangganan pengecualian (can_generate_exception_events) mengarah pada fakta bahwa semua pengecualian melempar akan berjalan lambat. Pada prinsipnya, ini tidak begitu menakutkan, karena pengecualian adalah hal yang langka dalam program Java yang baik.

Situasi dengan variabel lokal sedikit lebih buruk. Untuk can_access_local_variables, yang memungkinkan Anda mendapatkan nilai variabel lokal kapan saja, Anda perlu menonaktifkan beberapa optimasi penting. Secara khusus, Escape Analysis sepenuhnya berhenti bekerja, yang dapat memberikan overhead yang terlihat: tergantung pada aplikasi, 5-10%.

Oleh karena itu kesimpulannya: jika Anda menjalankan Java dengan agen debug dihidupkan, bahkan tanpa menggunakannya, aplikasi akan berjalan lebih lambat. Bagaimanapun, memasukkan agen debugging dalam produksi bukanlah ide yang baik.

Sejumlah fitur, misalnya, mengatur breakpoint atau melacak semua input / output dari suatu metode, membawa overhead yang jauh lebih serius. Secara khusus, beberapa peristiwa JVM TI (FieldAccess, MethodEntry / Exit) hanya berfungsi di interpreter.

Satu agen baik, dan dua lebih baik


Anda dapat menghubungkan beberapa agen ke satu proses dengan hanya menentukan beberapa parameter- -agentpath . Setiap orang akan memiliki lingkungan TI JVM mereka sendiri. Ini berarti bahwa setiap orang dapat berlangganan kemampuan mereka dan mencegat acara mereka secara mandiri.

Dan jika dua agen berlangganan acara Breakpoint, dan dalam satu breakpoint diatur dalam beberapa metode, maka ketika metode ini dieksekusi, akankah agen kedua menerima acara tersebut?

Pada kenyataannya, situasi seperti itu tidak dapat terjadi (setidaknya di HotSpot JVM). Karena ada beberapa kemampuan yang hanya dimiliki oleh salah satu agen pada waktu tertentu. Ini termasuk breakpoint_events khususnya. Oleh karena itu, jika agen kedua meminta kemampuan yang sama, itu akan menerima kesalahan sebagai respons.

Ini adalah kesimpulan penting: agen harus selalu memeriksa hasil permintaan kemampuan, bahkan jika Anda menjalankan di HotSpot dan tahu bahwa semuanya tersedia. Spesifikasi JVM TI tidak mengatakan apa-apa tentang kemampuan eksklusif, tetapi HotSpot memiliki fitur implementasi seperti itu.

Benar, isolasi agen tidak selalu bekerja dengan sempurna. Selama pengembangan async-profiler, saya menemukan masalah ini: ketika kami memiliki dua agen dan satu meminta generasi acara kompilasi metode, maka semua agen menerima peristiwa ini. Tentu saja, saya mengajukan bug , tetapi Anda harus ingat bahwa peristiwa yang tidak Anda harapkan dapat terjadi pada agen Anda.

Penggunaan dalam program reguler


JVM TI mungkin tampak seperti hal yang sangat spesifik untuk debugger dan profiler, tetapi juga dapat digunakan dalam program Java biasa. Pertimbangkan sebuah contoh.

Paradigma pemrograman reaktif sekarang tersebar luas ketika semuanya asinkron, tetapi ada masalah dengan paradigma ini.

 public class TaskRunner { private static void good() { CompletableFuture.runAsync(new AsyncTask(GOOD)); } private static void bad() { CompletableFuture.runAsync(new AsyncTask(BAD)); } public static void main(String[] args) throws Exception { good(); bad(); Thread.sleep(200); } } 

Saya menjalankan dua tugas asinkron yang hanya berbeda dalam parameter. Dan jika terjadi kesalahan, pengecualian muncul:



Dari jejak stack, sama sekali tidak jelas tugas mana yang menyebabkan masalah. Karena pengecualian terjadi pada utas yang sama sekali berbeda, di mana kita tidak memiliki konteks. Bagaimana memahami tugas yang mana?

Sebagai salah satu solusi, Anda dapat menambahkan informasi tentang di mana kami membuatnya ke konstruktor tugas asinkron kami:

 public AsyncTask(String arg) { this.arg = arg; this.location = getLocation(); } 

Artinya, ingat lokasi - tempat tertentu dalam kode, sampai ke garis dari mana konstruktor dipanggil. Dan dalam hal pengecualian untuk menjaminkannya:

 try { int n = Integer.parseInt(arg); } catch (Throwable e) { System.err.println("ParseTask failed at " + location); e.printStackTrace(); } 

Sekarang, ketika pengecualian terjadi, kita akan melihat bahwa ini terjadi pada baris 14 di TaskRunner (di mana tugas dengan parameter BAD dibuat):



Tetapi bagaimana cara mendapatkan tempat dalam kode dari mana konstruktor dipanggil? Sebelum ke Java 9, ada satu-satunya cara legal untuk melakukan ini: dapatkan jejak stack, lewati beberapa frame yang tidak relevan, dan sedikit lebih rendah pada stack akan menjadi tempat kode kita dipanggil.

 String getLocation() { StackTraceElement caller = Thread.currentThread().getStackTrace()[3]; return caller.getFileName() + ':' + caller.getLineNumber(); } 

Tapi ada masalah. Mendapatkan StackTrace lengkap sangat lambat. Saya memiliki seluruh laporan yang ditujukan untuk ini.

Ini tidak akan menjadi masalah besar jika jarang terjadi. Tetapi, misalnya, kami memiliki layanan web - frontend yang menerima permintaan HTTP. Ini adalah aplikasi hebat, jutaan baris kode. Dan untuk menangkap kesalahan render, kami menggunakan mekanisme yang serupa: di komponen untuk rendering, kami ingat tempat pembuatannya. Kami memiliki jutaan komponen seperti itu, jadi untuk mendapatkan semua jejak tumpukan memerlukan waktu nyata untuk memulai aplikasi, bukan hanya satu menit. Oleh karena itu, fitur ini sebelumnya dinonaktifkan dalam produksi, meskipun untuk analisis masalah diperlukan dalam produksi.

Java 9 memperkenalkan cara baru untuk mem-bypass stream stream: StackWalker, yang melalui Stream API dapat melakukan semua ini dengan malas, sesuai permintaan. Artinya, kita dapat melewati jumlah frame yang tepat dan hanya mendapatkan satu yang menarik bagi kita.

 String getLocation() { return StackWalker.getInstance().walk(s -> { StackWalker.StackFrame frame = s.skip(3).findFirst().get(); return frame.getFileName() + ':' + frame.getLineNumber(); }); } 

Ini bekerja sedikit lebih baik daripada mendapatkan jejak tumpukan penuh, tetapi tidak dengan urutan besarnya atau bahkan berkali-kali. Dalam kasus kami, ternyata sekitar satu setengah kali lebih cepat:



Ada masalah yang diketahui dengan implementasi StackWalker yang kurang optimal, dan kemungkinan besar itu akan diperbaiki di JDK 13. Tetapi sekali lagi, apa yang harus kita lakukan sekarang di Java 8, di mana StackWalker bahkan tidak lambat?

JVM TI datang untuk menyelamatkan lagi. Ada fungsi GetStackTrace() yang dapat melakukan semua yang Anda butuhkan: dapatkan fragmen jejak tumpukan dengan panjang tertentu, mulai dari bingkai yang ditentukan, dan tidak melakukan apa-apa lagi.

 GetStackTrace(jthread thread, jint start_depth, jint max_frame_count, jvmtiFrameInfo* frame_buffer, jint* count_ptr) 

Hanya ada satu pertanyaan yang tersisa: bagaimana cara memanggil fungsi JVM TI dari program Java kami? Sama seperti metode asli lainnya: muat pustaka asli dengan System.loadLibrary() , di mana implementasi JNI dari metode kami akan.

 public class StackFrame { public static native String getLocation(int depth); static { System.loadLibrary("stackframe"); } } 

Pointer ke lingkungan JVM TI dapat diperoleh tidak hanya dari Agent_OnLoad (), tetapi juga saat program sedang berjalan, dan untuk terus menggunakannya dari metode JNI asli asli:

 JNIEXPORT jstring JNICALL Java_StackFrame_getLocation(JNIEnv* env, jclass unused, jint depth) { jvmtiFrameInfo frame; jint count; jvmti->GetStackTrace(NULL, depth, 1, &frame, &count); 

:



, JDK : - . -. , , , JDK. JDK 8u112, JVM TI-, (GetMethodName, GetMethodDeclaringClass ), .

, , : JVM TI- , , -. , C++, jvmtiEnter.xsl .

: HotSpot XSLT-. HotSpot.

? , . , - jmethodID , . , .


, JVM TI Java- , System.loadLibrary .

, , JVM TI- -agentpath JVM.

: (dynamic attach).

? , - , , JVM TI- .

JDK 9, jcmd:

 jcmd <pid> JVMTI.agent_load /path/to/agent.so [arguments] 

JDK jattach . , async-profiler , - JVM-, jattach.

JVM TI- , , Agent_OnLoad() , Agent_OnAttach() . : Agent_OnAttach() capabilities, .

, , Agent_OnAttach() .

. IntelliJ IDEA: Java-, , - .

process ID IDEA, jattach JVM TI- patcher.dll:
jattach 8648 load patcher.dll true

:



? Java- ( javax.swing.AbstractButton ) JNI setBackground() . .

Java 9


JVM TI , , , API, . Java 9.

, Java 9 , . , «» JDK, .

, JDK Direct ByteBuffer. API:



, Cassandra , MappedByteBuffer, , JVM .

JDK 9, IllegalAccessError:



Reflection: .

, Java Linux. - java.io.FileDescriptor JNI - . , JDK 9, :



, JVM, . , . , Cassandra Java 11, :

 --add-exports java.base/jdk.internal.misc=ALL-UNNAMED --add-exports java.base/jdk.internal.ref=ALL-UNNAMED --add-exports java.base/sun.nio.ch=ALL-UNNAMED --add-exports java.management.rmi/com.sun.jmx.remote.internal.rmi=ALL-UNNAMED --add-exports java.rmi/sun.rmi.registry=ALL-UNNAMED --add-exports java.rmi/sun.rmi.server=ALL-UNNAMED --add-exports java.sql/java.sql=ALL-UNNAMED --add-opens java.base/java.lang.module=ALL-UNNAMED --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens java.base/jdk.internal.ref=ALL-UNNAMED --add-opens java.base/jdk.internal.reflect=ALL-UNNAMED --add-opens java.base/jdk.internal.math=ALL-UNNAMED --add-opens java.base/jdk.internal.module=ALL-UNNAMED --add-opens java.base/jdk.internal.util.jar=ALL-UNNAMED --add-opens jdk.management/com.sun.management.internal=ALL-UNNAMED 

JVM TI :

  • GetAllModules
  • AddModuleExports
  • AddModuleOpens
  • . .

, : JVM, , , .

Direct ByteBuffer:

 public static void main(String[] args) { ByteBuffer buf = ByteBuffer.allocateDirect(1024); ((sun.nio.ch.DirectBuffer) buf).cleaner().clean(); System.out.println("Buffer cleaned"); } 

, IllegalAccessError. agentpath antimodule , . .

Java 11


Java 11. , ! : SampledObjectAlloc , , .

callback , : , , , , . SetHeapSampingInterval , .



? , , . Java Flight Recorder.

, , , , .

Thread Local Allocation Buffer . TLAB , . , .



, TLAB, . JVM runtime .

, , — 5%.

, , JDK 7, Flight Recorder. API async-profiler. , JDK 11, API , JVM TI, . , YourKit . API, , .

. , , , , .



Kesimpulan


JVM TI — .

, ++, JVM . , JVM TI .

GitHub . , .

Source: https://habr.com/ru/post/id458812/


All Articles