Bellek Sızıntılarını Teşhis Etme - İzin verilen # baytlık bellek boyutu tükendi


98

Korkunç hata mesajıyla karşılaştım, muhtemelen zahmetli bir çabayla, PHP'de bellek kalmadı:

123. satırdaki file.php dosyasında #### baytlık izin verilen bellek boyutu tükendi (#### bayt ayırmaya çalıştı)

Sınırı artırmak

Ne yaptığınızı biliyorsanız ve sınırı artırmak istiyorsanız memory_limit'e bakın :

ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit

Dikkat! Sorunu değil, yalnızca belirtiyi çözüyor olabilirsiniz!

Sızıntının teşhisi:

Hata mesajı, bellek sızdırdığına ya da gereksiz yere biriktirdiğine inandığım bir döngüye sahip bir çizgiye işaret ediyor. memory_get_usage()Her yinelemenin sonunda ifadeler yazdırdım ve sayının sınıra ulaşana kadar yavaşça arttığını görebiliyorum:

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
}

Bu sorunun amaçları doğrultusunda, akla gelebilecek en kötü spagetti kodunun $userveya içinde bir yerde küresel kapsamda saklandığını varsayalım Task.

Hangi araçlar, PHP hileleri veya hata ayıklama voodoo sorunu bulup düzeltmeme yardımcı olabilir?


Not - Son zamanlarda bu tür şeylerle ilgili bir sorunla karşılaştım. Ne yazık ki, php'nin bir alt nesne imha sorunu olduğunu da buldum. Bir ana nesnenin ayarını kaldırırsanız, alt nesneleri serbest bırakılmaz. Tüm alt nesnelere __destruct vb. Özyinelemeli bir çağrı içeren değiştirilmiş bir unset kullandığımdan emin olmam gerekiyor. Ayrıntılar burada: paul-m-jones.com/archives/262 :: şuna benzer bir şey yapıyorum: function super_unset ($ öğe) {if (is_object ($ öğe) && method_exists ($ öğe, "__destruct")) {$ item -> __ destroyt (); } ayarlanmamış ($ öğe); }
Josh

Yanıtlar:


48

PHP'nin çöp toplayıcısı yok. Belleği yönetmek için referans sayımı kullanır. Bu nedenle, bellek sızıntılarının en yaygın kaynağı döngüsel referanslar ve global değişkenlerdir. Bir çerçeve kullanıyorsanız, korkarım bulmak için içinden geçmeniz gereken çok sayıda kodunuz olacaktır. En basit araç, aramaları seçmeli olarak yerleştirmek memory_get_usageve kodun sızdığı yere daraltmaktır. Kodun bir izini oluşturmak için xdebug da kullanabilirsiniz . Kodu yürütme izlemeleri ve show_mem_delta.


3
Ama dikkat edin ... oluşturulan izleme dosyaları ENORMOUS olacaktır. Bir Zend Framework uygulamasında ilk kez bir xdebug izlemesi çalıştırdığımda, çalıştırmak çok uzun bir zaman aldı ve çok GB (kb veya MB ... GB değil) boyutlu bir dosya oluşturdu. Sadece bunun farkında olun.
rg88

1
Evet, oldukça ağır .. GB'ler kulağa biraz fazla geliyor - büyük bir senaryonuz yoksa. Belki sadece birkaç satırı işlemeyi deneyin (Sızıntıyı tanımlamak için yeterli olmalıdır). Ayrıca, xdebug uzantısını üretim sunucusuna yüklemeyin.
troelskn

31
5.3'ten beri PHP aslında bir çöp toplayıcısına sahiptir. Öte yandan, xdebug :(
wdev

3
+1 sızıntıyı buldu! Döngüsel referansları olan bir sınıf! Bu referanslar ayarlanmadığında (), nesneler beklendiği gibi çöp olarak toplandı! Teşekkürler! :)
rinogo

@rinogo peki sızıntıyı nasıl öğrendin? Hangi adımları attığınızı paylaşır mısınız?
JohnnyQ

11

İşte hangi komut dosyalarının sunucumuzda en fazla belleği kullandığını belirlemek için kullandığımız bir numara.

Aşağıdaki parçacığı şuradaki bir dosyaya kaydedin, örneğin /usr/local/lib/php/strangecode_log_memory_usage.inc.php:

<?php
function strangecode_log_memory_usage()
{
    $site = '' == getenv('SERVER_NAME') ? getenv('SCRIPT_FILENAME') : getenv('SERVER_NAME');
    $url = $_SERVER['PHP_SELF'];
    $current = memory_get_usage();
    $peak = memory_get_peak_usage();
    error_log("$site current: $current peak: $peak $url\n", 3, '/var/log/httpd/php_memory_log');
}
register_shutdown_function('strangecode_log_memory_usage');

Aşağıdakileri httpd.conf'a ekleyerek çalıştırın:

php_admin_value auto_prepend_file /usr/local/lib/php/strangecode_log_memory_usage.inc.php

Ardından günlük dosyasını şu adreste analiz edin: /var/log/httpd/php_memory_log

touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_logWeb kullanıcınızın günlük dosyasına yazabilmesi için bunu yapmanız gerekebilir .


8

Bir kez eski bir betikte PHP'nin "as" değişkenini foreach döngümden sonra bile kapsamdaki gibi tutacağını fark ettim. Örneğin,

foreach($users as $user){
  $user->doSomething();
}
var_dump($user); // would output the data from the last $user 

Gördüğümden beri gelecekteki PHP sürümlerinin bunu düzeltip düzeltmediğinden emin değilim. Bu durumda, satırın unset($user)arkasından doSomething()onu bellekten temizleyebilirsiniz. YMMV.


13
PHP, C / Java / vb. Gibi döngüleri / koşulları kapsamaz. Bir döngü / koşul içinde bildirilen her şey döngüden / koşulludan çıktıktan sonra bile kapsamdadır (tasarım gereği [?]). Öte yandan yöntemler / işlevler, beklediğiniz gibi kapsam dahilindedir - her şey işlevin yürütülmesi sona erdiğinde serbest bırakılır.
Frank Farmer

Bunun tasarım gereği olduğunu varsaymıştım. Bunun bir yararı, bir döngüden sonra bulduğunuz son öğeyle çalışabilmenizdir, örneğin belirli kriterleri karşılayan.
joachim

Yapabilirsin unset(), ama nesneler için tek yaptığın değişkeninin gösterdiği yeri değiştirmek olduğunu unutma - onu aslında bellekten kaldırmadın. PHP, kapsam dışına çıktığında belleği otomatik olarak boşaltacaktır, bu nedenle daha iyi çözüm (OP'nin sorusu değil, bu yanıt açısından) kısa işlevler kullanmaktır, böylece döngüden o değişkene bağlı kalmasınlar. uzun.
Rich Court

@patcoll Bunun bellek sızıntılarıyla ilgisi yok. Bu basitçe dizi göstericisinin değişmesidir. Buraya bir göz atın: 3a sürümünde prismnet.com/~mcmahon/Notes/arrays_and_pointers.html .
Harm Smits

7

Php'de birkaç olası bellek sızıntısı noktası vardır:

  • php'nin kendisi
  • php uzantısı
  • kullandığınız php kitaplığı
  • php kodunuz

Derin tersine mühendislik veya php kaynak kodu bilgisi olmadan ilk 3'ü bulup düzeltmek oldukça zordur. Sonuncusu için memory_get_usage ile bellek sızıntı kodu için ikili aramayı kullanabilirsiniz.


91
Cevabınız, alabileceği kadar genel
TravisO

2
Php 7.2'nin bile çekirdek php bellek sızıntılarını düzeltememesi utanç verici. İçinde uzun süre çalışan işlemler çalıştıramazsınız.
Aftab Naveed

6

Geçenlerde benzer koşullar altında topladığım bir uygulamada bu sorunla karşılaştım. Birçok yinelemede döngü yapan PHP'nin klibinde çalışan bir betik. Betiğim birkaç temel kitaplığa bağlıdır. Bunun nedeninin belirli bir kitaplık olduğundan şüpheleniyorum ve boşuna sınıflarına uygun imha yöntemlerini eklemek için boşuna birkaç saat harcadım. Farklı bir kütüphaneye uzun bir dönüştürme süreciyle karşı karşıya kaldım (aynı sorunlara sahip olabilir), benim durumumdaki problem için kaba bir çalışma buldum.

Benim durumumda, bir linux klibinde, bir grup kullanıcı kaydı üzerinde dolaşıyordum ve her biri için oluşturduğum birkaç sınıfın yeni bir örneğini oluşturuyordum. PHP'nin exec yöntemini kullanarak sınıfların yeni örneklerini oluşturmaya karar verdim, böylece bu süreç "yeni bir iş parçacığı" içinde çalışacaktı. İşte bahsettiğim şeyin gerçekten temel bir örneği:

foreach ($ids as $id) {
   $lines=array();
   exec("php ./path/to/my/classes.php $id", $lines);
   foreach ($lines as $line) { echo $line."\n"; } //display some output
}

Açıkçası, bu yaklaşımın sınırları vardır ve bir tavşan işi yaratmak kolay olacağından, bunun tehlikelerinin farkında olunması gerekir, ancak bazı nadir durumlarda, daha iyi bir düzeltme bulunana kadar zor bir noktayı aşmaya yardımcı olabilir. benim durumumda olduğu gibi.


6

Aynı problemle karşılaştım ve benim çözümüm foreach'i normal for ile değiştirmekti. Ayrıntılardan emin değilim, ama görünüşe göre foreach nesnenin bir kopyasını (veya bir şekilde yeni bir referans) oluşturuyor. Normal bir for döngüsü kullanarak öğeye doğrudan erişirsiniz.


5

PHP kılavuzunu kontrol etmenizi veya gc_enable()çöpleri toplamak için işlevi eklemenizi öneririm ... Bu bellek sızıntıları kodunuzun çalışma şeklini etkilemez.

Not: php, gc_enable()argüman almayan bir çöp toplayıcısına sahiptir.


3

Yakın zamanda PHP 5.3 lambda işlevlerinin kaldırıldıklarında fazladan bellek kullandığını fark ettim.

for ($i = 0; $i < 1000; $i++)
{
    //$log = new Log;
    $log = function() { return new Log; };
    //unset($log);
}

Neden olduğundan emin değilim, ancak işlev kaldırıldıktan sonra bile her lambda fazladan 250 bayt alıyor gibi görünüyor.


2
Ben de aynısını söyleyecektim. Bu, 5.3.10 ( # 60139 ) itibarıyla düzeltildi
Kristopher Ives

@KristopherIves, güncelleme için teşekkürler! Haklısın, bu artık bir sorun değil, bu yüzden onları deli gibi kullanmaktan korkmamalıyım.
Xeoncross

2

PHP hakkında söyledikleriniz, yalnızca bir işlevden sonra GC yapmak doğruysa, döngünün içeriğini bir geçici çözüm / deney olarak bir işlevin içine kaydırabilirsiniz.


1
@DavidKullmann Aslında cevabımın yanlış olduğunu düşünüyorum. Sonuçta run(), çağrılan aynı zamanda sonunda GC'nin gerçekleşmesi gereken bir işlevdir.
Bart van Heukelom

2

Karşılaştığım büyük bir problem, create_function kullanmaktı . Lambda işlevlerinde olduğu gibi, üretilen geçici adı bellekte bırakır.

Bellek sızıntılarının bir diğer nedeni (Zend Framework durumunda) Zend_Db_Profiler'dir. Zend Framework altında betik çalıştırıyorsanız bunun devre dışı bırakıldığından emin olun. Örneğin, application.ini dosyamda şunlar vardı:

resources.db.profiler.enabled    = true
resources.db.profiler.class      = Zend_Db_Profiler_Firebug

Yaklaşık 25.000 sorgu + ondan önce çok fazla işlem çalıştırmak, belleği güzel bir 128Mb'ye (Maksimum bellek limitim) getirdi.

Sadece ayarlayarak:

resources.db.profiler.enabled    = false

20 Mb'ın altında tutmak yeterliydi

Ve bu komut dosyası CLI'de çalışıyordu, ancak Zend_Application'ı başlatıyor ve Bootstrap'ı çalıştırıyordu, bu nedenle "geliştirme" yapılandırmasını kullanıyordu.

Komut dosyasını xDebug profillemeyle çalıştırmaya gerçekten yardımcı oldu



1

Bu konuşmaya biraz geç kaldım ama Zend Framework ile ilgili bir şey paylaşacağım.

Php 5.2.9 ile geliştirilmiş bir ZF uygulamasıyla çalışmak için php 5.3.8'i (phpfarm kullanarak) yükledikten sonra bellek sızıntısı sorunu yaşadım. Bellek sızıntısının Apache'nin httpd.conf dosyasında, yazdığı sanal ana bilgisayar tanımımda tetiklendiğini keşfettim SetEnv APPLICATION_ENV "development". Bu satırı yorumladıktan sonra, bellek sızıntıları durdu. PHP betiğimde bir satır içi geçici çözüm bulmaya çalışıyorum (esas olarak ana index.php dosyasında manuel olarak tanımlayarak).


1
Soru CLI'de koştuğunu söylüyor. Bu, Apache'nin sürece hiç dahil olmadığı anlamına gelir.
Maxime

1
@Maxime İyi nokta, bunu yakalayamadım, teşekkürler. Pekala, umarım rastgele bir Google çalışanı, sorunumu çözmeye çalışırken bu sayfa benim için açıldığından, yine de burada bıraktığım nottan faydalanacaktır.
fronzee

Bu soruya cevabımı kontrol edin, belki de bu sizin durumunuzdu.
Andy

Uygulamanız, ortama bağlı olarak farklı konfigürasyonlara sahip olmalıdır. "development"Çevre genellikle giriş ve diğer ortamlar olmayabilir o profilleme bir demet vardır. Çizgiyi yorumlamak, uygulamanızın bunun yerine genellikle "production"veya olan varsayılan ortamı kullanmasına neden oldu "prod". Bellek sızıntısı hala var; onu içeren kod o ortamda çağrılmıyor.
Marco Roy

0

Burada bahsedildiğini görmedim, ancak yardımcı olabilecek bir şey, refcount'u görmek için xdebug ve xdebug_debug_zval ('variableName') kullanmaktır.

Ayrıca, yolunuza çıkan bir php uzantısı örneği verebilirim: Zend Sunucusunun Z-Ray'i. Veri toplama etkinleştirilirse, bellek kullanımı her yinelemede çöp toplama kapalıymış gibi balonlaşır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.