Panduan Pasukan Lucee MY

Lucee kehabisan memori: diagnosis dan pembetulan

Lucee kehabisan memori? Panduan membaca OutOfMemoryError, beza heap, metaspace dan memori asli, dan membetulkan punca dengan jcmd dan jstat, bukan tambah -Xmx.

Lucee kehabisan memori: diagnosis dan pembetulan
0
Kandungan

Lucee kehabisan memori: diagnosis dan pembetulan

Penulis: Thalhah · Tarikh: 4 Oktober 2026

Lucee kehabisan memori apabila JVM yang menjalankannya gagal memperuntukkan memori untuk permintaan semasa, dan ia melaporkannya sebagai OutOfMemoryError dalam log. Panduan ini menerangkan tiga kawasan memori yang berbeza iaitu heap, metaspace dan memori asli, cara membaca mesej ralat dalam log, cara mengesahkan penggunaan sebenar dengan jcmd dan jstat, dan punca biasa dalam aplikasi CFML supaya anda membetulkan punca dan bukan menambah -Xmx secara membuta tuli. Semua angka diagnostik di sini ditangkap daripada rig Lucee sebenar, bukan daripada ingatan.

Heap, Metaspace dan Memori Asli

OutOfMemoryError bukan satu masalah sahaja. Ia satu keluarga ralat, dan setiap ahlinya menunjuk kawasan memori yang berbeza. JVM membahagikan memorinya kepada tiga kawasan yang sering disalah faham sebagai satu perkara.

Heap ialah kawasan tempat objek Java hidup, termasuk objek query, struct, array dan cache yang Lucee cipta untuk permintaan anda. Saiznya dikawal oleh -Xmx, dan apabila ia penuh, JVM melontar "Java heap space". Ini ralat yang paling biasa dan paling mudah dikaitkan dengan kod aplikasi.

Metaspace ialah kawasan di luar heap yang menyimpan metadata kelas, iaitu struktur yang JVM perlukan untuk memuatkan kelas CFML yang anda tulis, komponen CFC, dan kelas daripada sambungan. Pada JVM moden, metaspace tidak dihadkan secara lalai, jadi ia boleh membesar sehingga memakan memori asli proses jika aplikasi memuatkan kelas secara berlebihan. Ralatnya berbunyi "Metaspace".

Memori asli ialah segala-galanya yang bukan heap dan bukan metaspace: tindanan setiap utas, penimbal terus, kod yang dikompil JIT, dan struktur dalaman JVM. Ralat "unable to create native thread" dan proses yang dibunuh oleh kernel tanpa sebarang mesej dalam log aplikasi biasanya berpunca di sini, dan menambah -Xmx tidak membantu langsung kerana masalahnya bukan pada heap.

Kenapa -Xmx 512MB Memadai untuk Satu Aplikasi Kecil

Imej Docker rasmi Lucee bermula dengan -Xms64m -Xmx512m, iaitu heap bermula pada 64 MB dan membesar sehingga 512 MB. Ramai pembangun melihat 512 MB dan menganggapnya terlalu kecil, lalu menaikkannya kepada 2 GB atau 4 GB sebelum aplikasi sempat menunjukkan sebarang gejala.

Untuk satu aplikasi kecil dengan beberapa jadual, trafik sederhana dan permintaan yang selesai dalam beberapa ratus milisaat, 512 MB hampir selalu cukup. Sebabnya mudah: objek yang dicipta untuk satu permintaan sepatutnya dikutip semula apabila permintaan itu tamat. Heap penuh bermakna sesuatu memegang objek lebih lama daripada sepatutnya, dan masalah itu tidak hilang dengan memberi lebih banyak ruang, ia hanya ditangguhkan.

Menambah -Xmx secara membuta tuli juga memburukkan keadaan. Pengumpul sampah G1 menangguhkan kerja selagi ia melihat ruang kosong, jadi heap yang besar dan jarang penuh akan menimbun objek mati sebelum GC bertindak, menghasilkan jeda yang lebih lama. Pada bekas dengan had memori, -Xmx yang terlalu besar membuatkan JVM melanggar had bekas, dan kernel membunuh proses tanpa amaran. Ukur dahulu, kemudian tala, seperti dalam panduan menala JVM.

Membaca OutOfMemoryError dalam Log

Lucee menulis ralat ke exception.log dan Tomcat ke catalina.out, seperti dalam panduan log dan pemantauan. Apabila JVM kehabisan memori, mesej dalam log memberitahu kawasan mana yang penuh, dan baris pertama ialah penunjuk paling penting:

java.lang.OutOfMemoryError: Java heap space
java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError: Metaspace
java.lang.OutOfMemoryError: unable to create native thread: possibly out of memory or process/resource limits reached

Baris ini ialah rentetan mesej piawai JVM. Setiap satu menunjuk kawasan yang berbeza: "Java heap space" dan "GC overhead limit exceeded" menunjuk heap, "Metaspace" menunjuk metadata kelas, dan "unable to create native thread" menunjuk memori asli atau had utas sistem. Ralat ini tidak dapat dicetuskan dengan selamat pada rig kongsi, jadi ia ditulis daripada spesifikasi JVM, bukan dengan menjatuhkan pelayan ujian.

Mengesahkan Penggunaan dengan jcmd dan jstat

Jangan meneka, ukur. Dua alat yang dihantar bersama JDK memberi angka sebenar tanpa pemasangan tambahan. Cari PID proses Java dahulu, kemudian jalankan jcmd untuk maklumat heap dan bendera, dan jstat untuk kadar GC:

# cari PID proses Java (contoh melalui /proc)
ps aux | grep catalina | grep -v grep
# maklumat heap semasa dan bendera JVM yang sebenar
jcmd <pid> GC.heap_info
jcmd <pid> VM.flags
# kadar penggunaan setiap kawasan dan statistik GC
jstat -gcutil <pid>

Dalam rig Lucee yang menjalankan artikel ini, proses Java ialah PID 1 di dalam bekas. Output sebenar bagi tiga arahan itu ialah:

# jcmd 1 GC.heap_info
1:
garbage-first heap   total reserved 524288K, committed 114688K, used 85185K [0x00000000e0000000, 0x0000000100000000)
 region size 1024K, 24 young (24576K), 6 survivors (6144K)

# jcmd 1 VM.flags (dipendekkan kepada bendera yang berkaitan)
-XX:InitialHeapSize=67108864 -XX:MaxHeapSize=536870912 -XX:SoftMaxHeapSize=536870912 -XX:+UseG1GC

# jstat -gcutil 1
  S0     S1     E      O      M     CCS    YGC     YGCT     FGC    FGCT     CGC    CGCT       GCT
     - 100.00  45.95  86.41  98.62  94.82     32     0.192     0     0.000    18     0.084     0.276

Output ini menceritakan keadaan sebenar rig: heap maksimum 536870912 bait, iaitu 512 MB seperti yang diiklankan oleh imej Docker. Heap sedang menggunakan 85185 KB daripada 114688 KB yang dikomitkan, jauh di bawah had. Lajur jstat menunjukkan metaspace (M) pada 98.62 peratus, tetapi FGC bernilai 0 bermakna tiada kutipan sampah penuh pernah berlaku, jadi rig sihat. Ini contoh baik: angka tinggi pada satu lajur belum tentu masalah selagi tiada full GC dan heap tidak penuh.

Gejala OutOfMemoryError dan Puncanya

Jadual ini memetakan mesej ralat kepada punca dan tindakan pertama. Gunakannya sebelum mengubah sebarang tetapan.

Gejala atau mesejKawasanPunca biasaTindakan pertama
OutOfMemoryError: Java heap spaceHeapObjek memegang lebih lama daripada perlu, query memuat terlalu banyak baris, cache tanpa hadSemak punca dalam kod, hadkan data, kurangkan -Xmx hanya selepas mengukur
GC overhead limit exceededHeapGC menghabiskan lebih 98 peratus masa tanpa mengembalikan memoriCari kebocoran memori dalam skop application atau cache
OutOfMemoryError: MetaspaceMetaspaceTerlalu banyak kelas dimuatkan, classloader tidak dibuangSemak bilangan CFC dan sambungan, tetapkan had metaspace secara sedar
unable to create native threadMemori asliHad utas sistem, tindanan utas membesar, terlalu banyak cfthreadKurangkan bilangan utas, semak had ulimit dan had bekas

Punca Biasa dalam Aplikasi CFML

Enam corak berikut ialah punca yang paling kerap dilihat dalam aplikasi CFML, dan kesemuanya boleh dibetulkan dalam kod tanpa menyentuh -Xmx.

Pertama, query besar tanpa maxrows. Apabila cfquery memuatkan seluruh jadual sedangkan anda hanya memaparkan 20 baris pertama, anda membayar memori untuk ribuan objek yang tidak pernah dilihat pengguna. Kedua, objek yang membesar dalam skop application. Setiap nilai yang anda simpan dalam application kekal sepanjang hayat pelayan, jadi menambah ke dalam array atau struct di sana tanpa sempadan ialah kebocoran perlahan.

Ketiga, cache tanpa had. Lucee menawarkan cache objek dan template, dan kedua-duanya membesar jika tiada had ditetapkan. Keempat, muat naik fail besar yang dibaca sepenuhnya ke dalam memori dan bukannya distrim ke cakera. Kelima, cfdocument dengan imej besar, yang memuatkan imej dan membina PDF dalam memori. Dan keenam, cfthread yang mencipta utas tanpa sempadan, kerana setiap utas mengambil tindakan dan memori asli sendiri. Untuk penjanaan PDF dengan imej besar, rujuk panduan cfdocument.

Pembetulan Mengikut Punca

Peraturan emasnya: betulkan punca, bukan gejala. Menambah -Xmx menangguhkan ralat dan menutup punca sebenar, manakala menambah maxrows pada satu query membetulkannya untuk selama-lamanya.

Untuk query besar, tambahkan maxrows atau gunakan had dalam SQL. Untuk objek dalam skop application, simpan hanya apa yang berubah perlahan dan bersihkan data yang tidak lagi diperlukan. Untuk cache, tetapkan had saiz dan masa tamat, seperti yang dijelaskan dalam panduan caching dan prestasi. Untuk muat naik fail, tulis ke cakera dan proses sebagai aliran. Untuk cfdocument, kecilkan imej sebelum membina PDF. Untuk utas, hadkan bilangan cfthread serentak dan gunakan kumpulan tetap.

Hanya selepas semua punca ini disemak dan aplikasi masih sah memerlukan lebih banyak memori, barulah anda menaikkan -Xmx, dan itupun secara beransur-ansur sambil memerhati jstat.

Kod Salah Berbanding Kod Betul

Corak paling kerap ialah cfquery tanpa had. Kedua-dua blok ini sah dan berjalan, tetapi yang pertama memuatkan seluruh jadual, yang kedua satu baris sahaja:

<!--- salah: memuatkan seluruh jadual ke dalam memori --->
<cfquery name="qSemua" datasource="luceemy">
    SELECT id, name, price FROM products ORDER BY id
</cfquery>

<!--- betul: hadkan baris yang dimuatkan --->
<cfquery name="qHad" datasource="luceemy" maxrows="1">
    SELECT id, name, price FROM products ORDER BY id
</cfquery>

Kedua-dua blok ini dijalankan dalam rig yang sama seperti di atas. Dengan jadual products berisi dua baris, versi tanpa maxrows memulangkan dua rekod manakala versi dengan maxrows memulangkan satu, dan rig membalas HTTP 200: output sebenar semua=2 had=1. Pada jadual dengan ratusan ribu baris, perbezaan antara memuatkan dua baris dan memuatkan keseluruhan jadual ialah perbezaan antara aplikasi yang sihat dan OutOfMemoryError.

Perhatikan bahawa maxrows mengehadkan baris yang dikembalikan, bukan jumlah kerja pangkalan data. Untuk jadual besar, pasangkan dengan klausa LIMIT dalam SQL supaya pangkalan data tidak perlu mengimbas seluruh jadual. Untuk melindungi query daripada suntikan SQL, gunakan cfqueryparam pada setiap nilai input.

Soalan Lazim (FAQ)

Apakah perbezaan antara heap, metaspace dan memori asli?

Heap menyimpan objek Java dan dikawal oleh -Xmx. Metaspace menyimpan metadata kelas dan berada di luar heap. Memori asli merangkumi tindanan utas, penimbal terus dan struktur dalaman JVM. Setiap satu melontar mesej OutOfMemoryError yang berbeza.

Adakah 512 MB cukup untuk Lucee?

Untuk satu aplikasi kecil dengan trafik sederhana, ya. Imej Docker rasmi menggunakan -Xms64m -Xmx512m, dan angka jcmd dalam artikel ini menunjukkan heap 512 MB hanya menggunakan kira-kira 85 MB. Naikkan hanya selepas mengukur dan membetulkan punca sebenar.

Bagaimana saya membaca OutOfMemoryError dalam log?

Cari baris pertama mesej dalam exception.log atau catalina.out. "Java heap space" menunjuk heap, "Metaspace" menunjuk metadata kelas, dan "unable to create native thread" menunjuk memori asli atau had utas sistem.

Bagaimana saya menyemak penggunaan memori sebenar JVM?

Gunakan jcmd untuk maklumat heap dan bendera, contohnya jcmd 1 GC.heap_info dan jcmd 1 VM.flags, dan jstat -gcutil untuk kadar penggunaan setiap kawasan dan statistik GC. Kedua-duanya dihantar bersama JDK.

Kenapa saya tidak patut menambah -Xmx sahaja?

Kerana -Xmx yang lebih besar hanya menangguhkan ralat dan boleh memburukkan jeda GC atau melanggar had bekas. Punca biasa seperti query tanpa maxrows, cache tanpa had dan objek yang membesar dalam skop application tidak hilang dengan memberi lebih banyak memori.

Sumber

0 komen

0 komen

Log masuk untuk menjawab atau memberi komen.

Log masuk