Debugging Jarak Jauh Aplikasi Java

1. Ikhtisar

Debugging Aplikasi Java jarak jauh dapat berguna dalam lebih dari satu kasus.

Dalam tutorial ini, kita akan menemukan cara melakukannya menggunakan perkakas JDK.

2. Aplikasi

Mari kita mulai dengan menulis aplikasi. Kami akan menjalankannya di lokasi jarak jauh dan men-debugnya secara lokal melalui artikel ini:

public class OurApplication { private static String staticString = "Static String"; private String instanceString; public static void main(String[] args) { for (int i = 0; i < 1_000_000_000; i++) { OurApplication app = new OurApplication(i); System.out.println(app.instanceString); } } public OurApplication(int index) { this.instanceString = buildInstanceString(index); } public String buildInstanceString(int number) { return number + ". Instance String !"; } } 

3. JDWP: Java Debug Wire Protocol

The Java Debug Kawat Protocol adalah protokol yang digunakan di Jawa untuk komunikasi antara debuggee dan debugger . Debuggee adalah aplikasi yang sedang di-debug sementara debugger adalah aplikasi atau proses yang menghubungkan ke aplikasi yang sedang di-debug.

Kedua aplikasi berjalan di mesin yang sama atau di mesin yang berbeda. Kami akan fokus pada yang terakhir.

3.1. Opsi JDWP

Kami akan menggunakan JDWP dalam argumen baris perintah JVM saat meluncurkan aplikasi debuggee.

Permintaannya membutuhkan daftar opsi:

  • transportasi adalah satu-satunya pilihan yang sepenuhnya diperlukan. Ini mendefinisikan mekanisme transportasi yang akan digunakan. dt_shmem hanya berfungsi di Windows dan jika kedua proses berjalan di mesin yang sama sementara dt_socket kompatibel dengan semua platform dan memungkinkan proses berjalan di mesin yang berbeda
  • server bukanlah pilihan wajib. Bendera ini, jika aktif, menentukan cara menempel ke debugger. Itu baik mengekspos proses melalui alamat yang ditentukan dalam opsi alamat . Jika tidak, JDWP memperlihatkan yang default
  • suspend menentukan apakah JVM harus menangguhkan dan menunggu debugger dilampirkan atau tidak
  • address adalah opsi yang berisi alamat, umumnya port, yang diekspos oleh debuggee. Itu juga dapat mewakili alamat yang diterjemahkan sebagai rangkaian karakter (seperti javadebug jika kita menggunakan server = y tanpa memberikan alamat di Windows)

3.2. Luncurkan Command

Mari kita mulai dengan meluncurkan aplikasi jarak jauh. Kami akan memberikan semua opsi yang tercantum sebelumnya:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8000 OurApplication 

Hingga Java 5, argumen JVM runjdwp harus digunakan bersama dengan opsi debug lainnya :

java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000

Cara menggunakan JDWP ini masih didukung tetapi akan dihentikan di rilis mendatang. Kami lebih memilih penggunaan notasi yang lebih baru jika memungkinkan.

3.3. Sejak Java 9

Terakhir, salah satu opsi JDWP telah berubah dengan dirilisnya Java versi 9. Ini adalah perubahan yang cukup kecil karena hanya menyangkut satu opsi tetapi akan membuat perbedaan jika kita mencoba men-debug aplikasi jarak jauh.

Perubahan ini memengaruhi cara alamat berperilaku untuk aplikasi jarak jauh. Alamat notasi lama = 8000 hanya berlaku untuk localhost . Untuk mencapai perilaku lama, kita akan menggunakan tanda bintang dengan titik dua sebagai awalan untuk alamat tersebut (misalnya alamat = *: 8000 ).

Menurut dokumentasi, ini tidak aman dan disarankan untuk menentukan alamat IP debugger jika memungkinkan:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=127.0.0.1:8000

4. JDB: Java Debugger

JDB, Java Debugger, adalah alat yang disertakan dalam JDK yang dirancang untuk menyediakan klien debugger yang nyaman dari baris perintah.

Untuk meluncurkan JDB, kami akan menggunakan mode lampirkan . Mode ini memasang JDB ke JVM yang sedang berjalan. Mode berjalan lain ada, seperti mendengarkan atau menjalankan tetapi sebagian besar nyaman saat men-debug aplikasi yang berjalan secara lokal:

jdb -attach 127.0.0.1:8000 > Initializing jdb ... 

4.1. Breakpoints

Mari lanjutkan dengan meletakkan beberapa breakpoint dalam aplikasi yang disajikan di bagian 1.

Kami akan menetapkan breakpoint pada konstruktor:

> stop in OurApplication. 

Kami akan menetapkan satu sama lain di metode utama utama , menggunakan nama kelas String yang sepenuhnya memenuhi syarat :

> stop in OurApplication.main(java.lang.String[]) 

Terakhir, kami akan menetapkan yang terakhir pada metode instance buildInstanceString :

> stop in OurApplication.buildInstanceString(int) 

Kita sekarang harus memperhatikan aplikasi server berhenti dan berikut ini dicetak di konsol debugger kita:

> Breakpoint hit: "thread=main", OurApplication.(), line=11 bci=0 

Sekarang mari tambahkan breakpoint pada baris tertentu, yang mana variabel app.instanceString sedang dicetak:

> stop at OurApplication:7 

Kita perhatikan bahwa at digunakan setelah stop, bukan di saat breakpoint didefinisikan pada baris tertentu.

4.2. Menavigasi dan Mengevaluasi

Sekarang kita telah menetapkan breakpoint kita, mari gunakan cont untuk melanjutkan eksekusi thread kita sampai kita mencapai breakpoint pada baris 7.

Kita harus melihat yang berikut tercetak di konsol:

> Breakpoint hit: "thread=main", OurApplication.main(), line=7 bci=17 

Sebagai pengingat, kami telah berhenti di baris yang berisi potongan kode berikut:

System.out.println(app.instanceString); 

Stopping on this line could have also been done by stopping on the main method and typing step twice. step executes the current line of code and stops the debugger directly on the next line.

Now that we've stopped, the debugee is evaluating our staticString, the app‘s instanceString, the local variable i and finally taking a look at how to evaluate other expressions.

Let's print staticField to the console:

> eval OurApplication.staticString OurApplication.staticString = "Static String" 

We explicitly put the name of the class before the static field.

Let's now print the instance field of app:

> eval app.instanceString app.instanceString = "68741. Instance String !" 

Next, let's see the variable i:

> print i i = 68741 

Unlike the other variables, local variables don't require to specify a class or an instance. We can also see that print has exactly the same behavior as eval: they both evaluate an expression or a variable.

We'll evaluate a new instance of OurApplication for which we've passed an integer as a constructor parameter:

> print new OurApplication(10).instanceString new OurApplication(10).instanceString = "10. Instance String !" 

Now that we've evaluated all the variables we needed to, we'll want to delete the breakpoints set earlier and let the thread continue its processing. To achieve this, we'll use the command clear followed by the breakpoint's identifier.

The identifier is exactly the same as the one used earlier with the command stop:

> clear OurApplication:7 Removed: breakpoint OurApplication:7 

Untuk memverifikasi apakah breakpoint telah dihapus dengan benar, kami akan menggunakan clear tanpa argumen. Ini akan menampilkan daftar breakpoint yang ada tanpa yang baru saja kita hapus:

> clear Breakpoints set: breakpoint OurApplication. breakpoint OurApplication.buildInstanceString(int) breakpoint OurApplication.main(java.lang.String[]) 

5. Kesimpulan

Pada artikel singkat ini, kami telah menemukan cara menggunakan JDWP bersama dengan JDB, keduanya merupakan alat JDK.

Informasi lebih lanjut tentang perkakas, tentu saja, dapat ditemukan di referensi masing-masing: JDWP dan JDB - untuk masuk lebih dalam ke perkakas.