Bagaimana cara menganalisis thread dump

Ada beberapa topik tentang internal JVM di program kursus Java Developer . Kami memahami mekanisme kerja pengumpulan, bytecode, pengumpul sampah, dll. Hari ini kami menawarkan perhatian Anda pada terjemahan artikel yang agak menarik tentang thread dump. Apa itu, bagaimana cara mendapatkannya dan bagaimana menggunakannya.

Ingin mempelajari cara menganalisis dump thread? Pergilah ke bawah kucing untuk mempelajari lebih lanjut tentang cara mendapatkan thread dump di Jawa dan apa yang harus dilakukan nanti.

Sebagian besar aplikasi Java modern multithreaded. Multithreading secara signifikan dapat memperluas fungsionalitas aplikasi, pada saat yang sama memperkenalkan kompleksitas yang signifikan.

Dalam aplikasi single-threaded, semua sumber daya (memori bersama, operasi input / output, dll.) Dapat digunakan tanpa sinkronisasi, karena pada waktu tertentu, hanya satu utas yang menggunakan sumber daya.

Dalam kasus aplikasi multi-utas, perlu untuk menemukan kompromi antara menyulitkan program dan kemungkinan peningkatan kinerja, ketika beberapa utas dapat menggunakan semua prosesor inti (CPU) yang tersedia (seringkali lebih dari satu). Jika semuanya dilakukan dengan benar, kemudian menggunakan multithreading (diformalkan dalam Hukum Amdahl ), Anda dapat mencapai peningkatan kinerja aplikasi yang signifikan. Namun, kita harus ingat untuk memberikan akses simultan dari beberapa aliran ke sumber daya bersama. Dalam kebanyakan kasus, kerangka kerja seperti Spring merangkum kerja dengan utas dan menyembunyikan banyak detail teknis dari pengguna. Namun, dalam hal penggunaan kerangka kerja modern yang kompleks, sesuatu mungkin salah, dan kami, sebagai pengguna, akan menghadapi kesulitan untuk menyelesaikan bug multithreading.

Untungnya, Java dilengkapi dengan mekanisme khusus untuk memperoleh informasi tentang keadaan saat ini dari semua utas pada waktu tertentu - ini adalah penumpukan ulir (semacam snapshot). Pada artikel ini, kita akan belajar cara mendapatkan dump thread untuk aplikasi berukuran realistis dan bagaimana menganalisis dump ini.

Diasumsikan bahwa pembaca memiliki informasi dasar tentang pemrograman multithread dan mengetahui masalah sinkronisasi utas dan penggunaan sumber daya bersama. Meskipun demikian, tidak akan berlebihan untuk menyegarkan beberapa istilah dan konsep dasar.

Terminologi dasar


Pada pandangan pertama, dump thread Java mungkin tampak seperti "huruf Cina," konsep berikut adalah kunci untuk memahaminya. Secara umum, mari kita ulangi istilah dasar multithreading, yang akan kita gunakan untuk menganalisis dump.

  • Utas atau utas adalah unit multithreading diskrit yang dikelola oleh Java Virtual Machine (JVM). Utas JVM sesuai dengan utas di sistem operasi (OS) - utas asli, yang menerapkan mekanisme eksekusi kode.

    Setiap utas memiliki pengidentifikasi dan nama yang unik. Streaming dapat menjadi "setan" dan "bukan setan."

    Program berakhir ketika semua utas non-daemon berakhir atau metode Runtime.exit dipanggil . Bekerja "setan" tidak memengaruhi penyelesaian program. Yaitu JVM sedang menunggu semua "non-setan" diselesaikan dan dimatikan, mereka tidak memperhatikan "non-setan".

    Untuk informasi lebih lanjut, lihat dokumentasi kelas Thread .
    Aliran mungkin berada di salah satu dari kondisi berikut:

    • Alive thread atau "live" - ​​utas yang berfungsi (keadaan normal).
    • Thread yang diblokir atau "diblokir" - utas yang mencoba masuk ke bagian yang disinkronkan (disinkronkan), tetapi utas lain sudah berhasil memasuki blok ini terlebih dahulu, dan semua utas berikut yang mencoba memasuki blok yang sama diblokir.
    • Thread menunggu atau "menunggu" - utas yang disebut metode tunggu (mungkin dengan batas waktu) dan sekarang sedang menunggu metode lain untuk mengeksekusi notify atau nonifyAll pada objek yang sama.

      Harap dicatat bahwa utas tidak dianggap sebagai "menunggu" jika disebut menunggu dengan batas waktu dan batas waktu ini telah kedaluwarsa.
    • Thread tidur atau "tidur" - utas yang saat ini tidak berjalan, karena melakukan metode Thread.sleep (menunjukkan durasi "tidur").
  • Monitor adalah mekanisme yang digunakan oleh JVM untuk menyediakan akses multi-utas ke satu objek. Mekanisme ini mulai menggunakan kata kunci tersinkronisasi khusus.

    Setiap objek di Jawa memiliki monitor yang dapat disinkronkan utasnya, mis. atur kunci, yang menjamin bahwa tidak ada utas lain yang akan mendapatkan akses ke objek ini hingga kunci dilepaskan, mis. utas - pemilik kunci tidak akan keluar dari blok yang disinkronkan .

    Lihat bagian Sinkronisasi (17.1) dari Spesifikasi Java Langauge (JLS) untuk informasi lebih lanjut .
  • Jalan buntu adalah situasi di mana utas, katakanlah A, memblokir sumber daya, ia membutuhkan sumber daya lain yang diblokir oleh utas lain, misalnya B. Aliran B tidak melepaskan sumber daya ini, karena Untuk menyelesaikan operasi tertentu, ia membutuhkan sumber daya yang diblokir oleh utas A. Ternyata utas A sedang menunggu sumber daya dibuka oleh utas B, yang sedang menunggu sumber lain dibuka dengan utas A. Dan, dengan demikian, utas sedang menunggu satu sama lain. Akibatnya, seluruh program hang dan menunggu utas untuk entah bagaimana membuka dan terus bekerja. Mungkin ada banyak utas di jalan buntu. Masalah ini dikenal sebagai "Problem of Philosophers Makan . "


  • Livelock adalah situasi di mana ulir A memaksa ulir B untuk melakukan beberapa tindakan, yang pada gilirannya menyebabkan ulir A melakukan tindakan awal, yang sekali lagi menyebabkan aksi ulir B. Ketergantungan siklik diperoleh. Ini bisa dibayangkan sebagai anjing yang mengejar ekornya. Demikian pula untuk Deadlock , dalam situasi Livelock, program tidak membuat kemajuan, mis. tidak melakukan tindakan yang bermanfaat, namun, dalam situasi ini, utas tidak diblokir.

Terminologi yang disajikan tidak lengkap untuk menggambarkan dunia multithreading, tetapi ini cukup untuk mulai menganalisis dump benang.

Informasi lebih rinci dapat ditemukan di sumber-sumber ini:

Bagian 17 dari JLS dan Java Concurrency dalam Praktek

Dengan menggunakan konsep sederhana tentang aliran di Jawa, kita dapat membuat aplikasi pengujian. Untuk aplikasi ini kami akan mengkompilasi dump thread. Kami akan menganalisis dump dan ekstrak informasi yang berguna tentang arus aplikasi saat ini.

Membuat program sampel


Sebelum membuat thread dump, kita perlu mengembangkan aplikasi Java. Tradisional "halo, dunia!" terlalu sederhana untuk tujuan kita, dan dump aplikasi ukuran menengah mungkin terlalu rumit untuk ditunjukkan. Berdasarkan ini, kami akan membuat aplikasi yang cukup sederhana di mana dua utas dibuat. Dan utas kebuntuan:

public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceB, resourceA)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized(firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized(secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } } 

Program ini menciptakan dua sumber: resourceA dan resourceB, dan memulai dua utas: utasLockingResourceAFirst dan utasLockingResourceBFirst, yang memblokir sumber daya masing-masing.

Penyebab kebuntuan adalah "lintas" pemblokiran sumber daya oleh utas.

Alasan terjadinya kebuntuan adalah upaya untuk "saling" merebut sumber daya, mis. threadLockingResourceAF thread pertama menangkap resourceA sumber daya, threadLockingResourceB thread pertama menangkap resourceB sumber daya. Setelah itu, threadLockingResourceAFirst, tanpa melepaskan sumber dayanya, mencoba meraih resourceB, dan threadLockingResourceBFirst, tanpa melepaskan sumber dayanya, mencoba meraih resourceA. Akibatnya, utas diblokir. Penundaan 1s telah ditambahkan untuk menjamin pemblokiran. Utas sedang menunggu rilis sumber daya yang diperlukan, tetapi ini tidak akan pernah terjadi.

Output dari program akan seperti ini (angka-angka setelah java.lang.Object @ akan berbeda untuk setiap peluncuran):

 Thread-0: locked resource -> java.lang.Object@149bc794 Thread-1: locked resource -> java.lang.Object@17c10009 

Setelah output dari pesan-pesan ini, program akan terlihat seperti sedang berjalan (proses menjalankan program ini tidak selesai), sementara program tidak melakukan pekerjaan apa pun. Beginilah jalan buntu dalam praktik. Untuk menyelesaikan masalah, kita perlu membuat dump tapak dan menganalisis status utas secara manual.

Pembuatan Dump Thread


Dalam praktiknya, program Java mungkin macet saat membuat dump thread. Namun, dalam beberapa kasus (misalnya, dalam kasus deadlock), program tidak berakhir dan thread dump tidak tercipta, hanya hang. Untuk membuat dump dari program yang digantung seperti itu, pertama-tama, Anda perlu mengetahui pengenal proses program, yaitu ID Proses (PID). Untuk melakukan ini, Anda dapat menggunakan utilitas Status Proses JVM (JPS), yang, dimulai dengan versi 7, merupakan bagian dari Java Development Kit (JDK). Untuk menemukan proses PID dari program kami yang macet, kami cukup menjalankan jps di terminal (Windows atau Linux):

 $ jps 11568 DeadlockProgram 15584 Jps 15636 

Kolom pertama adalah pengenal mesin virtual lokal (Local VM ID, mis. Lvmid) untuk proses Java yang sedang berjalan. Dalam konteks JVM lokal, lvmid menunjuk ke PID dari proses Java.

Perlu dicatat bahwa nilai ini cenderung berbeda dari nilai di atas. Kolom kedua adalah nama aplikasi, yang dapat menunjuk ke nama kelas utama, file jar, atau sama dengan "Tidak Dikenal". Itu semua tergantung pada bagaimana aplikasi itu diluncurkan.

Dalam kasus kami, nama aplikasi DeadlockProgram adalah nama kelas utama yang diluncurkan ketika program dimulai. Dalam contoh PID program 11568 di atas, informasi ini cukup untuk menghasilkan thread dump. Untuk menghasilkan dump, kita akan menggunakan utilitas jstack , yang merupakan bagian dari JDK, mulai dari versi 7. Untuk mendapatkan dump, kita akan meneruskan PID program kita ke jstack dan menentukan flag -l (membuat daftar panjang). Output dari utilitas akan dialihkan ke file teks, mis. thread_dump.txt:

 jstack -l 11568 > thread_dump.txt 

File thread_dump.txt yang dihasilkan berisi dump thread dari program kami yang digantung dan berisi informasi penting untuk mendiagnosis penyebab kebuntuan.

Jika JDK digunakan hingga versi 7, maka untuk menghasilkan dump, Anda dapat menggunakan utilitas Linux - kill dengan flag -3. Memanggil kill -3 akan mengirimkan program sinyal SIGQUIT.

Dalam kasus kami, panggilan akan seperti ini:

 kill -3 11568 

Analisis Dump Thread Sederhana


Membuka file thread_dump.txt, kita akan melihat sesuatu seperti berikut:

 2018-06-19 16:44:44
 Utang penuh Java HotSpot (TM) 64-Bit Server VM (10.0.1 + 10 mode campuran):
 Info SMR utas kelas:
 _java_thread_list = 0x00000250e5488a00, panjang = 13, elemen = {
 0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
 0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
 0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
 0x00000250e54d0800
 }
 "Handler Referensi" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 menunggu dalam kondisi [0x000000b82a9ff000]
    java.lang.Thread.State: RUNNABLE
     di java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
     di java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
     di java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
     di java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Finalizer" # 3 daemon prio = 8 os_prio = 1 tid = 0x00000250e4982800 nid = 0x2a54 di Object.wait () [0x000000b82aaff000]
    java.lang.Thread.State: WAITING (pada monitor objek)
     di java.lang.Object.wait (java.base@10.0.1/Native Method)
     - menunggu <0x0000000089509410> (a java.lang.ref.ReferenceQueue $ Lock)
     di java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
     - menunggu untuk mengunci kembali wait () <0x0000000089509410> (a java.lang.ref.ReferenceQueue $ Lock)
     di java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 172)
     di java.lang.ref.Finalizer $ FinalizerThread.run (java.base@10.0.1/Finalizer.java: 216)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Signal Dispatcher" # 4 daemon prio = 9 os_prio = 2 tid = 0x00000250e52f2800 nid = 0x2184 runnable [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Lampirkan Pendengar" # 5 daemon prio = 5 os_prio = 2 tid = 0x00000250e4992800 nid = 0x1624 menunggu dalam kondisi [0x00000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "C2 CompilerThread0" # 6 daemon prio = 9 os_prio = 2 tid = 0x00000250e4995800 nid = 0x4198 menunggu dalam kondisi [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Tidak ada tugas kompilasi
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "C2 CompilerThread1" # 7 daemon prio = 9 os_prio = 2 tid = 0x00000250e49a5800 nid = 0x3b98 menunggu dalam kondisi [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Tidak ada tugas kompilasi
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "C1 CompilerThread2" # 8 daemon prio = 9 os_prio = 2 tid = 0x00000250e49ae800 nid = 0x1a84 menunggu dalam kondisi [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Tidak ada tugas kompilasi
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Sweeper thread" # 9 daemon prio = 9 os_prio = 2 tid = 0x00000250e5324000 nid = 0x5f0 runnable [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Service Thread" # 10 daemon prio = 9 os_prio = 0 tid = 0x00000250e54cd800 nid = 0x169c runnable [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Common-Cleaner" # 11 daemon prio = 8 os_prio = 1 tid = 0x00000250e54cf000 nid = 0x1610 di Object.wait () [0x000000b82b2fe000]
    java.lang.Thread.State: TIMED_WAITING (pada monitor objek)
     di java.lang.Object.wait (java.base@10.0.1/Native Method)
     - menunggu <0x000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
     di java.lang.ref.ReferenceQueue.remove (java.base@10.0.1/ReferenceQueue.java: 151)
     - menunggu untuk mengunci kembali wait () <0x00000000008943e600> (a java.lang.ref.ReferenceQueue $ Lock)
     di jdk.internal.ref.CleanerImpl.run (java.base@10.0.1/CleanerImpl.java: 148)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
     di jdk.internal.misc.InnocuousThread.run (java.base@10.0.1/InnocuousThread.java: 134)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec menunggu entri monitor [0x000000b82b4ff000]
    java.lang.Thread.State: BLOCKED (pada objek monitor)
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465b0> (a java.lang.Object)
     - terkunci <0x00000000894465a0> (a java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "Thread-1" # 13 prio = 5 os_prio = 0 tid = 0x00000250e54d2000 nid = 0x415c menunggu entri monitor [0x000000b82b5ff000]
    java.lang.Thread.State: BLOCKED (pada objek monitor)
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465a0> (a java.lang.Object)
     - terkunci <0x00000000894465b0> (sebuah java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "DestroyJavaVM" # 14 prio = 5 os_prio = 0 tid = 0x00000250e54d0800 nid = 0x2b8c menunggu dalam kondisi [0x000000000000000000]
    java.lang.Thread.State: RUNNABLE
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada
 "VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 runnable  
 "GC Thread # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c runnable  
 "GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 runnable  
 "GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 runnable  
 "GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 runnable  
 "G1 Main Marker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 runnable  
 "G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 runnable  
 "G1 Saring # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c runnable  
 "G1 Saring # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 runnable  
 "G1 Saring # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 dapat dijalankan  
 "G1 Saring # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 dapat dijalankan  
 "G1 Young RemSet Sampling" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 runnable  
 "Utas Tugas Berkala VM" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 menunggu dengan syarat  
 Referensi global JNI: 2
 Menemukan satu kebuntuan tingkat-Jawa:
 ===============================
 "Utas-0":
   menunggu untuk mengunci monitor 0x00000250e4982480 (objek 0x00000000894465b0, sebuah java.lang.Object),
   yang dipegang oleh "Thread-1"
 "Utas-1":
   menunggu untuk mengunci monitor 0x00000250e4982380 (objek 0x00000000894465a0, a java.lang.Object),
   yang dipegang oleh "Thread-0"
 Informasi tumpukan Java untuk utas yang tercantum di atas:
 =================================================== =
 "Utas-0":
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465b0> (a java.lang.Object)
     - terkunci <0x00000000894465a0> (a java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 "Utas-1":
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465a0> (a java.lang.Object)
     - terkunci <0x00000000894465b0> (sebuah java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 Menemukan 1 kebuntuan.

Informasi pengantar


Meskipun pada pandangan pertama file ini mungkin tampak terlalu rumit dan membingungkan, pada kenyataannya itu cukup sederhana jika Anda membongkar bagian demi bagian.

Baris pertama menunjukkan waktu ketika dump dibentuk, informasi diagnostik kedua tentang JVM, tempat dump diterima:

 2018-06-19 16:44:44 Full thread dump Java HotSpot(TM) 64-Bit Server VM (10.0.1+10 mixed mode): 

Tidak ada informasi aliran di bagian ini. Di sini konteks umum sistem tempat pengumpulan dikumpulkan diatur.

Informasi Aliran Umum


Bagian berikut memberikan informasi tentang utas yang sedang berjalan di sistem pada saat pengumpulan sampah:

 Info SMR utas kelas:
 _java_thread_list = 0x00000250e5488a00, panjang = 13, elemen = {
 0x00000250e4979000, 0x00000250e4982800, 0x00000250e52f2800, 0x00000250e4992800,
 0x00000250e4995800, 0x00000250e49a5800, 0x00000250e49ae800, 0x00000250e5324000,
 0x00000250e54cd800, 0x00000250e54cf000, 0x00000250e54d1800, 0x00000250e54d2000,
 0x00000250e54d0800
 }

Bagian berikut mencantumkan:

Informasi Safe Memory Reclamation (SMR)

Ini berisi informasi tentang utas di luar JVM, mis. ini bukan utas mesin virtual atau utas pengumpulan sampah. Jika Anda melihat alamat utas ini, Anda akan melihat bahwa mereka sesuai dengan nilai tid - alamat "alami, besi" (asli) dalam sistem operasi, dan bukan Thread ID.

Elips digunakan untuk menyembunyikan informasi yang berlebihan:

 "Handler Referensi" # 2 ... tid = 0x00000250e4979000 ...
 "Penyelesaikan" # 3 ... tid = 0x00000250e4982800 ...
 "Pengirim Sinyal" # 4 ... tid = 0x00000250e52f2800 ...
 "Lampirkan Pendengar" # 5 ... tid = 0x00000250e4992800 ...
 "C2 CompilerThread0" # 6 ... tid = 0x00000250e4995800 ...
 "C2 CompilerThread1" # 7 ... tid = 0x00000250e49a5800 ...
 "C1 CompilerThread2" # 8 ... tid = 0x00000250e49ae800 ...
 "Utas penyapu" # 9 ... tid = 0x00000250e5324000 ...
 "Layanan Thread" # 10 ... tid = 0x00000250e54cd800 ...
 "Pembersih Umum" # 11 ... tid = 0x00000250e54cf000 ...
 "Thread-0" # 12 ... tid = 0x00000250e54d1800 ...
 "Thread-1" # 13 ... tid = 0x00000250e54d2000 ...
 "DestroyJavaVM" # 14 ... tid = 0x00000250e54d0800 ...

Streaming


Tepat setelah blok SMR adalah daftar utas. Utas pertama pada daftar kami adalah Handler Referensi:

 "Handler Referensi" # 2 daemon prio = 10 os_prio = 2 tid = 0x00000250e4979000 nid = 0x3c28 menunggu dalam kondisi [0x000000b82a9ff000]
    java.lang.Thread.State: RUNNABLE
     di java.lang.ref.Reference.waitForReferencePendingList (java.base@10.0.1/Native Method)
     di java.lang.ref.Reference.processPendingReferences (java.base@10.0.1/Reference.java: 174)
     di java.lang.ref.Reference.access $ 000 (java.base@10.0.1/Reference.java: 44)
     di java.lang.ref.Reference $ ReferenceHandler.run (java.base@10.0.1/Reference.java: 138)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada

Deskripsi singkat tentang aliran


Baris pertama untuk setiap utas menyediakan deskripsi umum. Deskripsi berisi item-item berikut:
BagianContohDeskripsi
Nama"Handler Referensi"Nama aliran yang dapat dibaca manusia. Nama dapat ditentukan dengan memanggil metode setName dari objek Thread . Dan melalui panggilan ke getName
ID# 2ID unik yang ditetapkan untuk setiap objek dari kelas Thread . ID dihasilkan untuk utas dalam sistem. Nilai awal adalah 1. Setiap utas yang baru dibuat akan diberi ID-nya sendiri, yang sebelumnya meningkat sebesar 1. Properti utas baca-saja ini dapat diperoleh dengan menggunakan fungsi getId dari objek kelas Thread .
Status daemondaemonBendera adalah tanda bahwa utas adalah setan. Jika itu adalah iblis, maka bendera akan ditetapkan. Misalnya, utas -0 bukan daemon.
Prioritasprio = 10Prioritas numerik dari aliran Java. Perhatikan bahwa prioritas ini tidak selalu sesuai dengan prioritas utas terkait di sistem operasi. Untuk mengatur prioritas, Anda dapat
gunakan metode setPriority dari objek Thread kelas, dan untuk mendapatkannya
metode getPriority .
Prioritas Utas OSos_prio = 2Utas prioritas dalam sistem operasi. Prioritas ini mungkin berbeda dari yang ditetapkan untuk utas Java tertaut.
Alamattid = 0x00000250e4979000Alamat aliran Java. Alamat ini adalah penunjuk ke objek asli Java Native Interface (JNI) dari kelas Thread (objek C ++ Thread yang terhubung ke utas Java melalui JNI). Nilai ini diperoleh dengan memberikan pointer ke ini
(objek C ++ yang dikaitkan dengan utas Java ini) ke integer. Lihat
baris 879 di hotspot / share / runtime / thread.cpp :
 st-> print ("tid =" INTPTR_FORMAT "", p2i (ini));

Meskipun kunci untuk objek ini ( tid ) mungkin terlihat seperti ID aliran,
sebenarnya, ini adalah alamat objek yang terhubung JNI C ++ Thread , dan ini bukan nilai itu
mengembalikan metode getId dari utas Java.
ID Utas OSnid = 0x3c28Pengidentifikasi unik dari utas sistem operasi yang terikat pada utas Java.
Nilai ini dikeluarkan dengan kode berikut:
baris 42 di hotspot / share / runtime / osThread.cpp :
 st-> print ("nid = 0x% x", thread_id ());

Statusmenunggu dengan syaratStatus dapat dibaca manusia dari utas saat ini.
Baris ini menampilkan informasi tambahan tentang status aliran yang sederhana (lihat di bawah), yang bisa jadi
terbiasa memahami apa yang akan dilakukan utas (mis. apakah utas sedang mencoba untuk mendapatkan kunci
atau menunggu kondisi pembukaan kunci terpenuhi).
Pointer Java Stack Terakhir Diketahui[0x000000b82a9ff000]Penunjuk tumpukan (SP) terakhir yang diketahui terkait dengan aliran ini.
Nilai ini diperoleh dengan menggunakan kode C ++ asli dicampur dengan kode Java menggunakan JNI. Nilai dikembalikan oleh fungsi last_Java_sp () ,
baris 2886 di hotspot / share / runtime / thread.cpp :
   st-> print_cr ("[" INTPTR_FORMAT "]", 
     (intptr_t) last_Java_sp () & ~ right_n_bits (12));

Untuk dump thread sederhana, informasi ini hampir tidak berguna. Namun, dalam kasus kompleks, SP mungkin
digunakan untuk melacak kunci.

Status aliran


Baris kedua adalah kondisi aliran saat ini. Status streaming yang mungkin tercantum dalam enum:
Thread.State :

BARU
DAPAT DIBERI
DIBLOKIR
MENUNGGU
TIMED_WAITING
TERMINASI

Lihat dokumentasi untuk lebih jelasnya.

Jejak tumpukan ulir


Bagian selanjutnya berisi jejak tumpukan aliran pada saat pembuangan diambil. Jejak tumpukan ini sangat mirip dengan jejak tumpukan, yang dilemparkan oleh pengecualian tanpa tertangkap. Dan itu berisi nama-nama kelas dan string yang dieksekusi pada saat dump terbentuk. Dalam kasus aliran Reference Handler, kami melihat tidak ada yang menarik.

Namun, ada sesuatu yang menarik tentang jejak thread-02 yang berbeda dari jejak standar:

 "Thread-0" # 12 prio = 5 os_prio = 0 tid = 0x00000250e54d1800 nid = 0xdec menunggu entri monitor [0x000000b82b4ff000]
    java.lang.Thread.State: BLOCKED (pada objek monitor)
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465b0> (a java.lang.Object)
     - terkunci <0x00000000894465a0> (a java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
    Sinkronisasi yang dapat dimiliki yang dikunci:
     - Tidak ada

Dalam penelusuran, kami melihat bahwa informasi tentang kunci telah ditambahkan. Utas ini mengharapkan kunci pada objek dengan alamat 0x00000000894465b0 (tipe objek java.lang.Object). Selain itu, utas itu sendiri memegang kunci dengan alamat 0x00000000894465a0 (juga java.lang.Object). Informasi ini akan berguna bagi kita nanti untuk diagnosis kebuntuan.

Primitif sinkronisasi yang diambil (Sinkronisasi yang Dapat Dimiliki)


Bagian terakhir berisi daftar primitif sinkronisasi yang ditangkap oleh aliran. Ini adalah objek yang dapat digunakan untuk menyinkronkan utas, misalnya, mengunci.

Menurut dokumentasi resmi Java, Ownable Synchronizer adalah keturunan dari AbstractOwnableSynchronizer (atau subkelasnya), yang dapat secara eksklusif ditangkap oleh stream untuk keperluan sinkronisasi.

ReentrantLock dan write-lock , tetapi bukan read-lock dari kelas ReentrantReadWriteLock adalah dua contoh bagus dari "sinkronisasi yang dapat dimiliki" yang ditawarkan oleh platform.

Untuk informasi lebih lanjut tentang subjek ini, Anda dapat merujuk ini
pos .

Utas JVM


Bagian dump berikutnya berisi informasi tentang utas teknis JVM yang bukan bagian dari aplikasi dan dikaitkan dengan utas sistem operasi. Karena aliran ini bekerja di luar aplikasi, mereka tidak memiliki pengidentifikasi aliran. Paling sering, ini adalah pengumpul sampah dan utas JVM teknis lainnya:

 "VM Thread" os_prio = 2 tid = 0x00000250e496d800 nid = 0x1920 runnable  
 "GC Thread # 0" os_prio = 2 tid = 0x00000250c35b5800 nid = 0x310c runnable  
 "GC Thread # 1" os_prio = 2 tid = 0x00000250c35b8000 nid = 0x12b4 runnable  
 "GC Thread # 2" os_prio = 2 tid = 0x00000250c35ba800 nid = 0x43f8 runnable  
 "GC Thread # 3" os_prio = 2 tid = 0x00000250c35c0800 nid = 0x20c0 runnable  
 "G1 Main Marker" os_prio = 2 tid = 0x00000250c3633000 nid = 0x4068 runnable  
 "G1 Conc # 0" os_prio = 2 tid = 0x00000250c3636000 nid = 0x3e28 runnable  
 "G1 Saring # 0" os_prio = 2 tid = 0x00000250c367e000 nid = 0x3c0c runnable  
 "G1 Saring # 1" os_prio = 2 tid = 0x00000250e47fb800 nid = 0x3890 runnable  
 "G1 Saring # 2" os_prio = 2 tid = 0x00000250e47fc000 nid = 0x32a8 dapat dijalankan  
 "G1 Saring # 3" os_prio = 2 tid = 0x00000250e47fd800 nid = 0x3d00 dapat dijalankan  
 "G1 Young RemSet Sampling" os_prio = 2 tid = 0x00000250e4800800 nid = 0xef4 runnable  
 "Utas Tugas Berkala VM" os_prio = 2 tid = 0x00000250e54d6800 nid = 0x3468 menunggu dengan syarat

Tautan Global JNI


Bagian ini menunjukkan jumlah referensi global yang digunakan oleh JVM melalui JNI. Tautan ini tidak dilayani oleh pengumpul sampah dan dapat menyebabkan kebocoran memori dalam keadaan tertentu.

 Referensi global JNI: 2

Dalam kebanyakan kasus sederhana, informasi ini tidak digunakan. Namun, pentingnya referensi global harus dipahami. Lihat posting ini untuk lebih jelasnya.

Utas kebuntuan


Bagian terakhir berisi informasi tentang kebuntuan yang ditemukan.
Jika ini tidak ditemukan, maka bagian itu akan kosong. Karena Kami secara khusus mengembangkan aplikasi dengan kunci, dalam kasus kami bagian ini. Kunci terdeteksi selama dumping dan disajikan dengan pesan berikut:

 Menemukan satu kebuntuan tingkat-Jawa:
 ===============================
 "Utas-0":
   menunggu untuk mengunci monitor 0x00000250e4982480 (objek 0x00000000894465b0, sebuah java.lang.Object),
   yang dipegang oleh "Thread-1"
 "Utas-1":
   menunggu untuk mengunci monitor 0x00000250e4982380 (objek 0x00000000894465a0, a java.lang.Object),
   yang dipegang oleh "Thread-0"
 Informasi tumpukan Java untuk utas yang tercantum di atas:
 =================================================== =
 "Utas-0":
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465b0> (a java.lang.Object)
     - terkunci <0x00000000894465a0> (a java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 "Utas-1":
     di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34)
     - menunggu untuk mengunci <0x00000000894465a0> (a java.lang.Object)
     - terkunci <0x00000000894465b0> (sebuah java.lang.Object)
     di java.lang.Thread.run (java.base@10.0.1/Thread.java: 844)
 Menemukan 1 kebuntuan.

Subbagian pertama menjelaskan skenario kebuntuan:

Thread-0 berharap dapat menangkap monitor (ini adalah akses ke blok disinkronkan (secondResource) dalam aplikasi kami), pada saat yang sama thread ini memegang monitor yang mencoba menangkap Thread-1 (ini mengakses fragmen kode yang sama: disinkronkan (secondResource) ) dalam aplikasi kita).

Kunci bundar ini disebut deadlock . Pada gambar di bawah ini
situasi ini disajikan dalam bentuk grafik:



Di subbagian kedua, tumpukan jejak diberikan untuk kedua utas yang diblokir.

Jejak tumpukan ini memungkinkan kami melacak operasi setiap utas hingga terjadi penguncian.
Dalam kasus kami, jika kami melihat garis:

di DeadlockProgram $ DeadlockRunnable.run (DeadlockProgram.java:34) , maka kita akan melihat bagian masalah dari kode:

 printLockedResource (sumber daya kedua);

Baris ini adalah baris pertama dari blok yang disinkronkan, yang merupakan alasan untuk kunci, dan memberitahu kita bahwa sinkronisasi pada sumber daya kedua adalah alasan untuk kunci bersama. Untuk memperbaiki situasi, kita harus memastikan bahwa kedua utas memiliki urutan sinkronisasi yang sama pada sumber dayaA dan sumber dayaB. Jika kami melakukan ini, kami akan datang ke aplikasi berikut:

 public class DeadlockProgram { public static void main(String[] args) throws Exception { Object resourceA = new Object(); Object resourceB = new Object(); Thread threadLockingResourceAFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); Thread threadLockingResourceBFirst = new Thread(new DeadlockRunnable(resourceA, resourceB)); threadLockingResourceAFirst.start(); Thread.sleep(500); threadLockingResourceBFirst.start(); } private static class DeadlockRunnable implements Runnable { private final Object firstResource; private final Object secondResource; public DeadlockRunnable(Object firstResource, Object secondResource) { this.firstResource = firstResource; this.secondResource = secondResource; } @Override public void run() { try { synchronized (firstResource) { printLockedResource(firstResource); Thread.sleep(1000); synchronized (secondResource) { printLockedResource(secondResource); } } } catch (InterruptedException e) { System.out.println("Exception occurred: " + e); } } private static void printLockedResource(Object resource) { System.out.println(Thread.currentThread().getName() + ": locked resource -> " + resource); } } } 

Aplikasi ini akan berakhir tanpa interlocking, dan sebagai hasilnya kita akan mendapatkan output berikut (perhatikan bahwa alamat kelas Object telah berubah):

 Thread-0: sumber daya terkunci -> java.lang.Object@1ad895d1
 Thread-0: sumber daya terkunci -> java.lang.Object@6e41d7dd
 Thread-1: resource terkunci -> java.lang.Object@1ad895d1
 Thread-1: resource terkunci -> java.lang.Object@6e41d7dd

, , thread dump, . ( deadlock-). , .

Thread Dump-


.

JVM . ( , ).

.

- β€” Thread Dump Analyzers (TDAs). Java thread dump- - , . , . , .

TDA:


. .

Kesimpulan


Thread dump- β€” Java-, . , .

deadlock, . . , β€” .

, Java- thread dump-. , .

, thread dump β€” Β« Β» , , Java-.

Java . , deadlock- .

, .

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


All Articles