Saya telah menemukan pesan kesalahan yang ditakuti, mungkin melalui upaya yang melelahkan, PHP kehabisan memori:
Ukuran memori yang diizinkan dari #### byte habis (mencoba mengalokasikan #### byte) di file.php pada baris 123
Meningkatkan batas
Jika Anda tahu apa yang Anda lakukan dan ingin meningkatkan batasnya, lihat memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Awas! Anda mungkin hanya menyelesaikan gejalanya dan bukan masalahnya!
Mendiagnosis kebocoran:
Pesan kesalahan menunjuk ke sebuah baris dalam sebuah loop yang saya yakini telah bocor, atau terakumulasi secara tidak perlu, memori. Saya telah mencetak memory_get_usage()
pernyataan di akhir setiap iterasi dan dapat melihat jumlahnya perlahan-lahan bertambah hingga mencapai batas:
foreach ($users as $user) {
$task = new Task;
$task->run($user);
unset($task); // Free the variable in an attempt to recover memory
print memory_get_usage(true); // increases over time
}
Untuk keperluan pertanyaan ini, mari kita asumsikan kode spaghetti terburuk yang bisa dibayangkan bersembunyi dalam lingkup global di suatu tempat di $user
atau Task
.
Alat, trik PHP, atau debugging voodoo apa yang dapat membantu saya menemukan dan memperbaiki masalah?
sumber
Jawaban:
PHP tidak memiliki pengumpul sampah. Ini menggunakan penghitungan referensi untuk mengelola memori. Jadi, sumber kebocoran memori yang paling umum adalah referensi siklik dan variabel global. Jika Anda menggunakan kerangka kerja, Anda akan memiliki banyak kode untuk dijelajahi untuk menemukannya, saya khawatir. Instrumen paling sederhana adalah melakukan panggilan ke tempat yang selektif
memory_get_usage
dan mempersempitnya ke tempat kode bocor. Anda juga dapat menggunakan xdebug untuk membuat jejak kode. Jalankan kode dengan jejak eksekusi danshow_mem_delta
.sumber
Berikut adalah trik yang kami gunakan untuk mengidentifikasi skrip mana yang menggunakan paling banyak memori di server kami.
Simpan potongan berikut dalam file di, misalnya,
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:Gunakan dengan menambahkan berikut ini ke httpd.conf:
Kemudian analisis file log di
/var/log/httpd/php_memory_log
Anda mungkin perlu melakukannya
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
sebelum pengguna web Anda dapat menulis ke file log.sumber
Saya melihat suatu kali dalam skrip lama bahwa PHP akan mempertahankan variabel "as" seperti dalam cakupan bahkan setelah perulangan foreach saya. Sebagai contoh,
Saya tidak yakin apakah versi PHP yang akan datang memperbaiki ini atau tidak karena saya sudah melihatnya. Jika demikian, Anda dapat menghapus baris
unset($user)
setelahnyadoSomething()
dari memori. YMMV.sumber
unset()
melakukannya, tetapi perlu diingat bahwa untuk objek, yang Anda lakukan hanyalah mengubah ke mana variabel Anda mengarah - Anda belum benar-benar menghapusnya dari memori. PHP akan secara otomatis membebaskan memori setelah berada di luar ruang lingkup, jadi solusi yang lebih baik (dalam hal jawaban ini, bukan pertanyaan OP) adalah menggunakan fungsi pendek sehingga mereka tidak bergantung pada variabel itu dari loop juga panjang.Ada beberapa kemungkinan kebocoran memori di php:
Cukup sulit untuk menemukan dan memperbaiki 3 yang pertama tanpa rekayasa balik yang mendalam atau pengetahuan kode sumber php. Untuk yang terakhir Anda dapat menggunakan pencarian biner untuk kode kebocoran memori dengan memory_get_usage
sumber
Saya baru-baru ini mengalami masalah ini pada aplikasi, dalam keadaan yang saya anggap serupa. Skrip yang berjalan di cli PHP yang melakukan loop pada banyak iterasi. Skrip saya bergantung pada beberapa pustaka yang mendasarinya. Saya menduga perpustakaan tertentu adalah penyebabnya dan saya menghabiskan beberapa jam dengan sia-sia mencoba menambahkan metode penghancuran yang sesuai ke kelasnya tetapi tidak berhasil. Dihadapkan dengan proses konversi yang panjang ke perpustakaan yang berbeda (yang ternyata memiliki masalah yang sama), saya datang dengan solusi kasar untuk masalah tersebut dalam kasus saya.
Dalam situasi saya, di cli linux, saya melakukan perulangan pada banyak catatan pengguna dan untuk masing-masing dari mereka membuat contoh baru dari beberapa kelas yang saya buat. Saya memutuskan untuk mencoba membuat instance baru dari kelas menggunakan metode exec PHP sehingga proses tersebut akan berjalan di "thread baru". Berikut adalah contoh yang sangat mendasar dari apa yang saya maksud:
Tentunya pendekatan ini memiliki keterbatasan, dan kita perlu menyadari bahayanya, karena akan mudah untuk membuat pekerjaan kelinci, namun dalam beberapa kasus yang jarang terjadi, ini mungkin membantu mengatasi titik yang sulit, sampai perbaikan yang lebih baik dapat ditemukan. , seperti dalam kasus saya.
sumber
Saya menemukan masalah yang sama, dan solusi saya adalah mengganti foreach dengan yang biasa. Saya tidak yakin tentang spesifikasinya, tetapi sepertinya foreach membuat salinan (atau entah bagaimana referensi baru) ke objek. Menggunakan loop biasa, Anda mengakses item secara langsung.
sumber
Saya sarankan Anda memeriksa manual php atau menambahkan
gc_enable()
fungsi untuk mengumpulkan sampah ... Itu adalah kebocoran memori tidak mempengaruhi bagaimana kode Anda berjalan.PS: php memiliki pengumpul sampah
gc_enable()
yang tidak membutuhkan argumen.sumber
Saya baru-baru ini memperhatikan bahwa fungsi lambda PHP 5.3 meninggalkan memori tambahan yang digunakan ketika mereka dihapus.
Saya tidak yakin mengapa, tetapi tampaknya membutuhkan 250 byte ekstra setiap lambda bahkan setelah fungsi tersebut dihapus.
sumber
Jika apa yang Anda katakan tentang PHP hanya melakukan GC setelah suatu fungsi benar, Anda dapat menggabungkan konten loop di dalam fungsi sebagai solusi / eksperimen.
sumber
run()
yang disebut juga merupakan fungsi, yang pada akhirnya GC harus terjadi.Satu masalah besar yang saya hadapi adalah dengan menggunakan create_function . Seperti dalam fungsi lambda, ia meninggalkan nama sementara yang dihasilkan di memori.
Penyebab lain kebocoran memori (dalam kasus Zend Framework) adalah Zend_Db_Profiler. Pastikan itu dinonaktifkan jika Anda menjalankan skrip di bawah Zend Framework. Misalnya yang saya miliki di aplikasi saya .ini berikut ini:
Menjalankan sekitar 25.000 kueri + banyak pemrosesan sebelum itu, membawa memori ke 128Mb (Batas memori maks saya).
Dengan hanya mengatur:
itu cukup untuk menyimpannya di bawah 20 Mb
Dan skrip ini berjalan di CLI, tetapi ia membuat instance Zend_Application dan menjalankan Bootstrap, jadi ia menggunakan konfigurasi "pengembangan".
Ini sangat membantu menjalankan skrip dengan profil xDebug
sumber
Saya tidak melihatnya secara eksplisit disebutkan, tetapi xdebug melakukan pekerjaan yang baik dalam membuat profil waktu dan memori (mulai 2.6 ). Anda dapat mengambil informasi yang dihasilkannya dan menyebarkannya ke front end gui pilihan Anda: webgrind (hanya waktu), kcachegrind , qcachegrind atau lainnya dan itu menghasilkan pohon panggilan dan grafik yang sangat berguna untuk memungkinkan Anda menemukan sumber berbagai kesengsaraan Anda .
Contoh (dari qcachegrind):
sumber
Saya agak terlambat untuk percakapan ini tetapi saya akan membagikan sesuatu yang berkaitan dengan Zend Framework.
Saya mengalami masalah kebocoran memori setelah menginstal php 5.3.8 (menggunakan phpfarm) untuk bekerja dengan aplikasi ZF yang dikembangkan dengan php 5.2.9. Saya menemukan bahwa kebocoran memori dipicu di file httpd.conf Apache, dalam definisi host virtual saya, yang tertulis
SetEnv APPLICATION_ENV "development"
. Setelah mengomentari kalimat ini, kebocoran memori berhenti. Saya mencoba mencari solusi inline di skrip php saya (terutama dengan mendefinisikannya secara manual di file index.php utama).sumber
"development"
lingkungan biasanya memiliki banyak penebangan & profiling bahwa lingkungan lainnya mungkin tidak memiliki. Mengomentari baris keluar hanya membuat aplikasi Anda menggunakan lingkungan default, yang biasanya"production"
atau"prod"
. Kebocoran memori masih ada; kode yang memuatnya tidak dipanggil di lingkungan itu.Saya tidak melihatnya disebutkan di sini tetapi satu hal yang mungkin membantu adalah menggunakan xdebug dan xdebug_debug_zval ('variableName') untuk melihat refcount.
Saya juga dapat memberikan contoh ekstensi php yang menghalangi: Z-Ray Zend Server. Jika pengumpulan data diaktifkan, penggunaan memori akan membengkak pada setiap iterasi seperti jika pengumpulan sampah dimatikan.
sumber