Encontrei a temida mensagem de erro, possivelmente devido a um esforço meticuloso, o PHP ficou sem memória:
Tamanho de memória permitido de #### bytes exauridos (tentou alocar #### bytes) em file.php na linha 123
Aumentando o limite
Se você sabe o que está fazendo e deseja aumentar o limite, consulte memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
Cuidado! Você pode estar resolvendo apenas o sintoma e não o problema!
Diagnosticando o vazamento:
A mensagem de erro aponta para uma linha dentro de um loop que acredito estar vazando, ou acumulando desnecessariamente, memória. Imprimi memory_get_usage()
declarações no final de cada iteração e posso ver o número crescer lentamente até atingir o limite:
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
}
Para o propósito desta pergunta, vamos supor que o pior código espaguete imaginável está escondido no escopo global em algum lugar em $user
ou Task
.
Quais ferramentas, truques de PHP ou vodu de depuração podem me ajudar a encontrar e corrigir o problema?
fonte
Respostas:
PHP não tem um coletor de lixo. Ele usa a contagem de referência para gerenciar a memória. Portanto, a fonte mais comum de vazamentos de memória são referências cíclicas e variáveis globais. Se você usar uma estrutura, terá muito código para pesquisar para encontrá-la, infelizmente. O instrumento mais simples é colocar chamadas seletivamente para
memory_get_usage
e reduzi-lo para onde os vazamentos de código. Você também pode usar o xdebug para criar um rastreamento do código. Execute o código com rastreios de execução eshow_mem_delta
.fonte
Aqui está um truque que usamos para identificar quais scripts estão usando mais memória em nosso servidor.
Salve o seguinte snippet em um arquivo em, por exemplo
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
:Use-o adicionando o seguinte ao httpd.conf:
Em seguida, analise o arquivo de log em
/var/log/httpd/php_memory_log
Pode ser necessário
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
antes que o usuário da web possa gravar no arquivo de log.fonte
Percebi uma vez em um script antigo que o PHP mantinha a variável "as" como no escopo, mesmo após meu loop foreach. Por exemplo,
Não tenho certeza se as versões futuras do PHP corrigiram isso ou não desde que eu vi. Se for esse o caso, você pode
unset($user)
depois dadoSomething()
linha para apagá-la da memória. YMMV.fonte
unset()
isso, mas tenha em mente que, para objetos, tudo o que você está fazendo é mudar para onde sua variável está apontando - você não a removeu realmente da memória. O PHP irá liberar automaticamente a memória assim que estiver fora do escopo de qualquer maneira, então a melhor solução (em termos desta resposta, não a pergunta do OP) é usar funções curtas para que eles não fiquem presos a essa variável do loop também longo.Existem vários pontos possíveis de vazamento de memória no php:
É muito difícil encontrar e corrigir os 3 primeiros sem um profundo conhecimento de engenharia reversa ou código-fonte php. Para o último, você pode usar a pesquisa binária para código de vazamento de memória com memory_get_usage
fonte
Recentemente, encontrei esse problema em um aplicativo, em circunstâncias que considero semelhantes. Um script executado no cli do PHP que executa um loop em muitas iterações. Meu script depende de várias bibliotecas subjacentes. Suspeito que uma determinada biblioteca seja a causa e passei várias horas em vão tentando adicionar métodos de destruição apropriados às suas classes, mas sem sucesso. Diante de um longo processo de conversão para uma biblioteca diferente (que poderia ter os mesmos problemas), propus uma solução bruta para o problema no meu caso.
Na minha situação, em um Linux CLI, eu estava fazendo um loop em um monte de registros de usuário e para cada um deles criando uma nova instância de várias classes que criei. Decidi tentar criar as novas instâncias das classes usando o método exec do PHP para que esse processo fosse executado em um "novo thread". Aqui está um exemplo realmente básico do que estou me referindo:
Obviamente, essa abordagem tem limitações, e é preciso estar ciente dos perigos disso, pois seria fácil criar um trabalho de coelho, no entanto, em alguns casos raros, pode ajudar a superar um ponto difícil, até que uma solução melhor pudesse ser encontrada , como no meu caso.
fonte
Eu me deparei com o mesmo problema e minha solução foi substituir foreach por um for regular. Não tenho certeza sobre os detalhes, mas parece que foreach cria uma cópia (ou de alguma forma uma nova referência) para o objeto. Usando um loop for regular, você acessa o item diretamente.
fonte
Eu sugiro que você verifique o manual do php ou adicione a
gc_enable()
função para coletar o lixo ... Ou seja, os vazamentos de memória não afetam a forma como o código é executado.PS: php tem um coletor de lixo
gc_enable()
que não aceita argumentos.fonte
Recentemente, notei que as funções lambda do PHP 5.3 deixam memória extra usada quando são removidas.
Não sei por que, mas parece que cada lambda ocupa 250 bytes extras, mesmo depois que a função é removida.
fonte
Se o que você diz sobre o PHP fazer apenas GC após uma função for verdadeiro, você pode envolver o conteúdo do loop dentro de uma função como uma solução alternativa / experimento.
fonte
run()
que é chamado também é uma função, ao final da qual deve acontecer o CG.Um grande problema que tive foi usando create_function . Como nas funções lambda, ele deixa o nome temporário gerado na memória.
Outra causa de vazamentos de memória (no caso do Zend Framework) é o Zend_Db_Profiler. Certifique-se de que está desabilitado se você executar scripts no Zend Framework. Por exemplo, eu tinha em meu application.ini o seguinte:
Rodar aproximadamente 25.000 consultas + cargas de processamento antes disso, trouxe a memória para bons 128Mb (Meu limite máximo de memória).
Definindo apenas:
foi o suficiente para mantê-lo abaixo de 20 Mb
E este script estava rodando em CLI, mas estava instanciando o Zend_Application e rodando o Bootstrap, então ele usou a configuração "development".
Realmente ajudou a executar o script com a criação de perfil xDebug
fonte
Eu não vi isso explicitamente mencionado, mas xdebug faz um ótimo trabalho de perfil de tempo e memória (a partir de 2.6 ). Você pode pegar as informações que ele gera e passá-las para um front end gui de sua escolha: webgrind (somente tempo), kcachegrind , qcachegrind ou outros e gera árvores de chamadas e gráficos muito úteis para permitir que você encontre as fontes de seus vários problemas .
Exemplo (de qcachegrind):
fonte
Estou um pouco atrasado para esta conversa, mas compartilharei algo pertinente ao Zend Framework.
Tive um problema de vazamento de memória após instalar o php 5.3.8 (usando o phpfarm) para trabalhar com um aplicativo ZF que foi desenvolvido com o php 5.2.9. Descobri que o vazamento de memória estava sendo acionado no arquivo httpd.conf do Apache, na minha definição de host virtual, onde diz
SetEnv APPLICATION_ENV "development"
. Depois de comentar esta linha, os vazamentos de memória pararam. Estou tentando criar uma solução alternativa embutida no meu script php (principalmente definindo-o manualmente no arquivo index.php principal).fonte
"development"
ambiente geralmente tem um monte de registros e perfis que outros ambientes podem não ter. Comentar a linha apenas faz seu aplicativo usar o ambiente padrão, que geralmente é"production"
ou"prod"
. O vazamento de memória ainda existe; o código que o contém simplesmente não está sendo chamado nesse ambiente.Não vi isso mencionado aqui, mas uma coisa que pode ser útil é usar xdebug e xdebug_debug_zval ('variableName') para ver o refcount.
Também posso fornecer um exemplo de extensão php que atrapalha: Zend Server's Z-Ray. Se a coleta de dados estiver habilitada, o uso da memória aumentará em cada iteração, como se a coleta de lixo estivesse desativada.
fonte