Thread dump dan heap dump untuk aplikasi Lucee
Ambil thread dump dan heap dump Lucee dalam Docker dengan kill -3, baca utas BLOCKED dan WAITING, dan analisis kebocoran memori guna VisualVM dan Eclipse MAT.

Kandungan
- Thread dump dan heap dump untuk aplikasi Lucee
- Bila Perlu Thread Dump dan Bila Perlu Heap Dump
- Mengambil Thread Dump dalam Bekas Docker
- Membaca Thread Dump: Keadaan Utas dan Kunci
- Mengesan Deadlock dalam Thread Dump
- Alat Diagnostik JVM dan Risikonya
- Thread Dump Sebenar daripada Rig Lucee
- Heap Dump, Saiz dan Data Pelawat
- Menganalisis dengan VisualVM dan Eclipse MAT
- Soalan Lazim
- Sumber
Thread dump dan heap dump untuk aplikasi Lucee
Penulis: Thalhah · Tarikh: 5 Oktober 2026
Thread dump ialah senarai semua utas (thread) yang sedang berjalan dalam Java, lengkap dengan keadaan dan timbunan panggilan setiap utas. Heap dump pula ialah salinan penuh objek dalam memori JVM pada satu ketika. Kedua-duanya ialah alat diagnostik utama apabila aplikasi Lucee tergantung, penggunaan CPU melonjak, atau memori bocor. Artikel ini menerangkan bila setiap satu perlu diambil, cara mengambilnya dalam bekas Docker, cara membacanya, dan amaran penting tentang data pelawat.
Bila Perlu Thread Dump dan Bila Perlu Heap Dump
Thread dump dan heap dump menjawab dua soalan yang berbeza, jadi mengambil yang salah membuang masa. Thread dump menjawab soalan "apa yang setiap utas sedang buat sekarang". Ia perlu apabila permintaan tergantung tanpa jawapan, apabila CPU mencecah 100 peratus dan pelayan menjadi perlahan, atau apabila anda mengesyaki dua utas saling menunggu (deadlock). Thread dump murah untuk diambil, cepat dijana, dan hampir tidak mengandungi data peribadi kerana ia hanya mengandungi nama kelas, kaedah dan alamat kunci.
Heap dump menjawab soalan "objek apa yang memenuhi memori". Ia perlu apabila anda melihat OutOfMemoryError, apabila memori JVM naik secara berterusan tanpa turun selepas kutipan sampah, atau apabila pelayan perlu dimulakan semula secara berkala kerana memori penuh. Heap dump ialah fail besar yang mengandungi setiap objek hidup dalam memori, termasuk rentetan permintaan, data sesi dan nilai yang dihantar pelawat. Kerana itu ia mesti dilayan sebagai data sulit, perkara yang dijelaskan di bawah.
Peraturan praktikal yang senang diingat: gejala tergantung atau CPU tinggi bermakna thread dump dahulu, gejala memori penuh bermakna heap dump dahulu. Kedua-duanya tidak saling menggantikan.
Mengambil Thread Dump dalam Bekas Docker
Dalam bekas Docker, proses Java selalunya ialah proses pertama dalam bekas, jadi PIDnya ialah 1. Sahkan dengan jps, yang menyenaraikan proses Java dan PIDnya:
sudo docker exec uji-kod jps -l
# output: 1 org.apache.catalina.startup.Bootstrap
sudo docker exec uji-kod kill -3 1
sudo docker logs --tail 300 uji-kod
Perintah kill -3 menghantar isyarat SIGQUIT kepada JVM. Tidak seperti kill -9 yang membunuh proses, SIGQUIT menyuruh JVM mencetak thread dump ke output standard dan terus berjalan seperti biasa. Dalam bekas, output standard itu ditangkap oleh Docker, jadi anda membaca dump melalui docker logs. Ini cara paling selamat kerana ia tidak mengubah keadaan aplikasi langsung.
jstack ialah pilihan kedua apabila anda mahu dump terus ke terminal dan bukan ke log bekas. Perintahnya sudo docker exec uji-kod jstack 1 mencetak thread dump yang sama ke stdout. Bezanya, jstack menulis ke terminal anda manakala kill -3 menulis ke log yang boleh anda buka semula kemudian.
Membaca Thread Dump: Keadaan Utas dan Kunci
Setiap blok dalam thread dump bermula dengan nama utas dalam tanda petik, diikuti oleh keadaan (state). Keadaan yang paling penting untuk dibaca ialah empat ini. RUNNABLE bermaksud utas sedang menjalankan kod atau sedia untuk dijalankan, dan ini normal untuk utas pekerja yang sibuk. BLOCKED bermaksud utas menunggu untuk memperoleh kunci objek yang dipegang oleh utas lain, dan ini selalunya tanda masalah. WAITING bermaksud utas menunggu tanpa had masa untuk isyarat daripada utas lain, contohnya melalui Object.wait atau park. TIMED_WAITING sama tetapi dengan had masa, contohnya tidur atau menunggu dengan timeout.
Untuk membaca punca tersekat, cari utas yang berada dalam keadaan BLOCKED, kemudian lihat dua baris penting. Baris "waiting to lock " menunjukkan kunci yang utas itu mahu, dan baris "locked " dalam utas lain menunjukkan utas yang sedang memegang kunci itu. Dengan memadankan alamat yang sama, anda tahu siapa yang menghalang siapa. Ini berkaitan terus dengan konsep konkurensi dalam panduan cfthread.
Mengesan Deadlock dalam Thread Dump
Deadlock berlaku apabila dua atau lebih utas saling menunggu kunci yang dipegang oleh satu sama lain, jadi tiada seorang pun boleh maju. Thread dump mengesannya secara automatik. Pada penghujung dump, JVM mencetak seksyen yang bermula dengan baris "Found one Java-level deadlock", diikuti senarai utas yang terlibat dan kunci yang masing-masing pegang dan mahu. Kehadiran seksyen ini ialah pengesahan muktamad, jadi anda tidak perlu meneka daripada keadaan BLOCKED sahaja.
Dalam aplikasi Lucee, deadlock biasanya timbul daripada kod CFML yang memanggil komponen Java secara bersilang, atau daripada dua permintaan yang mengunci sumber yang sama dalam susunan berbeza. Apabila seksyen deadlock muncul, kunci kepada pembetulan ialah menyusun semula supaya semua laluan mengambil kunci dalam susunan yang sama.
Alat Diagnostik JVM dan Risikonya
JDK membekalkan beberapa alat untuk mengambil dan membaca dump. Jadual ini menyenaraikan alat utama, kegunaannya dan risikonya pada pelayan yang hidup.
| Alat | Kegunaan | Risiko |
|---|---|---|
| jps | Menyenaraikan proses Java dan PIDnya | Tiada, hanya membaca senarai proses |
| jstack | Mencetak thread dump ke terminal | Output pergi ke stdout, mudah bercampur dengan log lain |
| jcmd | Satu alat banyak perintah, termasuk Thread.print dan GC.heap_dump | Perlu PID dan sintaks panjang |
| jmap | Mengambil heap dump dengan jmap -dump | Boleh menyebabkan jeda singkat pada JVM |
| kill -3 | Menghantar SIGQUIT, JVM mencetak thread dump dan terus hidup | Dump pergi ke stdout atau log, bukan fail berasingan |
jcmd ialah alat paling serba guna kerana satu alat menampung thread dump dan heap dump. jstack khusus untuk thread dump, manakala jmap khusus untuk peta memori dan heap dump.
Thread Dump Sebenar daripada Rig Lucee
Untuk membuktikan arahan di atas berfungsi, thread dump diambil daripada rig ujian Lucee yang berjalan dalam bekas bernama uji-kod, menggunakan imej Lucee 7.1.2.54 di atas Tomcat 11 dan JDK 25 Temurin. Proses Java ialah PID 1. Selepas menjalankan kill -3, dump dibaca daripada docker logs. Berikut ialah petikan sebenar daripada output itu:
Full thread dump OpenJDK 64-Bit Server VM (25.0.4.1+1-LTS mixed mode, sharing):
"http-nio-8888-exec-1" #86 [45] daemon prio=5 os_prio=0 cpu=727.73ms elapsed=7804.98s tid=0x000071572cc4e100 nid=45 waiting on condition [0x000071570483f000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@25.0.4.1/Native Method)
- parking to wait for <0x00000000e2ea9af8> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
at java.util.concurrent.locks.LockSupport.park(java.base@25.0.4.1/LockSupport.java:369)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(java.base@25.0.4.1/AbstractQueuedSynchronizer.java:1752)
at java.util.concurrent.LinkedBlockingQueue.take(java.base@25.0.4.1/LinkedBlockingQueue.java:435)
at org.apache.tomcat.util.threads.TaskQueue.take(TaskQueue.java:146)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:887)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:934)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:74)
at java.lang.Thread.run(java.base@25.0.4.1/Thread.java:1474)
Beberapa baris tengah timbunan panggilan dipendekkan supaya petikan ini ringkas. Utas ini ialah pekerja HTTP Tomcat yang sedang menunggu kerja baharu. Keadaannya WAITING (parking) dan ia menunggu pada TaskQueue.take, iaitu giliran tugasan Tomcat. Ini keadaan sihat yang normal untuk pekerja yang tidak menerima permintaan ketika itu. Dalam dump yang sama, utas http-nio-8888-Poller dan http-nio-8888-Acceptor berada dalam keadaan RUNNABLE kerana kedua-duanya sedang menguruskan sambungan rangkaian. Keseluruhan dump mengandungi 35 utas.
Heap Dump, Saiz dan Data Pelawat
Heap dump ialah salinan penuh memori JVM, jadi saiznya hampir sama dengan penggunaan heap sebenar, selalunya ratusan megabait hingga beberapa gigabait. Yang lebih penting, heap dump mengandungi data pelawat: rentetan permintaan HTTP, nilai borang, data sesi, kuki, dan kadangkala kata laluan atau token yang masih berada dalam memori. Kerana itu heap dump mesti dilayan sebagai data sulit. Jangan hantar fail itu kepada pihak luar, jangan muat naik ke perkhidmatan awam, dan padam ia sebaik sahaja analisis selesai.
Arahan untuk mengambil heap dump ialah jcmd dengan perintah GC.heap_dump. Arahan di bawah ditandakan sebagai arahan yang tidak dijalankan, kerana rig ujian ini dikongsi dan fail heap dump besar serta mengandungi data, jadi mengambilnya bukan langkah yang bertanggungjawab pada rig kongsi:
# TIDAK dijalankan pada rig kongsi: fail besar dan mengandungi data pelawat
sudo docker exec uji-kod jcmd 1 GC.heap_dump /tmp/heap-dump.hprof
# selepas selesai analisis, padam fail:
rm /tmp/heap-dump.hprof
Untuk persediaan yang sama, panduan Docker Lucee menerangkan cara bekas dijalankan, dan panduan menala JVM menerangkan cara menetapkan saiz heap supaya kebocoran memori tidak menjatuhkan pelayan.
Menganalisis dengan VisualVM dan Eclipse MAT
VisualVM ialah alat grafik percuma yang disertakan bersama JDK. Ia boleh melekat pada JVM yang sedang berjalan, memaparkan senarai utas secara langsung, dan mengambil thread dump atau heap dump dengan satu klik. Ia titik permulaan terbaik kerana ia menunjukkan keadaan utas dalam masa nyata tanpa perlu membaca teks mentah.
Eclipse Memory Analyzer (MAT) ialah alat khusus untuk heap dump. Ia membuka fail .hprof, menjalankan laporan "Leak Suspects" yang mencadangkan objek yang paling mungkin menyebabkan kebocoran, dan memaparkan dominator tree untuk melihat objek terbesar yang menahan memori. Apabila heap dump menunjukkan objek CFML tertentu membesar tanpa had, MAT membantu menjejak rantai rujukan kembali kepada kod yang menciptanya. Untuk gejala perlahan yang bukan kebocoran, baca panduan caching dan prestasi dan panduan log dan pemantauan.
Soalan Lazim
Bilakah saya perlu ambil thread dump dan bukannya heap dump?
Ambil thread dump apabila aplikasi tergantung, CPU tinggi, atau anda mengesyaki deadlock. Ambil heap dump apabila memori penuh, OutOfMemoryError muncul, atau penggunaan memori naik tanpa turun.
Bagaimana cara mengambil thread dump dalam bekas Docker?
Cari PID Java dengan jps, kemudian jalankan kill -3 dengan PID itu melalui docker exec. Dump pergi ke output standard dan dibaca dengan docker logs.
Apakah maksud keadaan BLOCKED dan WAITING dalam thread dump?
BLOCKED bermaksud utas menunggu untuk memperoleh kunci yang dipegang utas lain, manakala WAITING bermaksud utas menunggu tanpa had masa untuk isyarat daripada utas lain.
Mengapa heap dump mesti dirahsiakan dan dipadam selepas selesai?
Heap dump mengandungi data pelawat seperti rentetan permintaan, sesi, kuki dan kadangkala kata laluan dalam memori, jadi ia tidak boleh dihantar kepada pihak luar dan mesti dipadam selepas analisis.
Apakah alat untuk menganalisis thread dump dan heap dump?
Guna VisualVM untuk melihat utas secara langsung dan Eclipse Memory Analyzer (MAT) untuk menganalisis fail heap dump dan mencari punca kebocoran memori.
Sumber
- jstack, Oracle JDK Documentation
- jcmd, Oracle JDK Documentation
- jmap, Oracle JDK Documentation
- jps, Oracle JDK Documentation
- Diagnostic Tools, Oracle JDK Troubleshooting Guide
- Troubleshooting Lucee Server problems, docs.lucee.org
- VisualVM
- Eclipse Memory Analyzer (MAT)
- docker container exec, Docker Docs
- docker container logs, Docker Docs