Me he encontrado con el temido mensaje de error, posiblemente a través de un esfuerzo minucioso, PHP se ha quedado sin memoria:
Tamaño de memoria permitido de #### bytes agotados (intentó asignar #### bytes) en file.php en la línea 123
Aumentando el límite
Si sabe lo que está haciendo y desea aumentar el límite, consulte memory_limit :
ini_set('memory_limit', '16M');
ini_set('memory_limit', -1); // no limit
¡Tener cuidado! ¡Es posible que solo esté resolviendo el síntoma y no el problema!
Diagnosticando la fuga:
El mensaje de error apunta a una línea dentro de un bucle que creo que está perdiendo o acumulando memoria innecesariamente. Imprimí memory_get_usage()
declaraciones al final de cada iteración y puedo ver que el número crece lentamente hasta que alcanza el límite:
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 los propósitos de esta pregunta, supongamos que el peor código espagueti imaginable se esconde en algún lugar de alcance global en $user
o Task
.
¿Qué herramientas, trucos de PHP o depuración de vudú pueden ayudarme a encontrar y solucionar el problema?
fuente
Respuestas:
PHP no tiene un recolector de basura. Utiliza el recuento de referencias para administrar la memoria. Por tanto, la fuente más común de pérdidas de memoria son las referencias cíclicas y las variables globales. Si usa un marco, tendrá mucho código para rastrear para encontrarlo, me temo. El instrumento más simple es realizar llamadas selectivamente
memory_get_usage
y limitarlas a donde se filtra el código. También puede usar xdebug para crear un rastro del código. Ejecute el código con seguimientos de ejecución yshow_mem_delta
.fuente
Aquí hay un truco que hemos usado para identificar qué scripts están usando más memoria en nuestro servidor.
Guarde el siguiente fragmento en un archivo en, por ejemplo
/usr/local/lib/php/strangecode_log_memory_usage.inc.php
,:Úselo agregando lo siguiente a httpd.conf:
Luego analice el archivo de registro en
/var/log/httpd/php_memory_log
Puede que necesite hacerlo
touch /var/log/httpd/php_memory_log && chmod 666 /var/log/httpd/php_memory_log
antes de que su usuario web pueda escribir en el archivo de registro.fuente
Una vez me di cuenta de que en un antiguo script PHP mantendría la variable "as" como en el alcance incluso después de mi bucle foreach. Por ejemplo,
No estoy seguro de si las futuras versiones de PHP solucionaron esto o no desde que lo vi. Si este es el caso, puede
unset($user)
después de ladoSomething()
línea borrarlo de la memoria. YMMV.fuente
unset()
hacerlo, pero tenga en cuenta que para los objetos, todo lo que está haciendo es cambiar el lugar al que apunta su variable, en realidad no lo ha eliminado de la memoria. PHP liberará automáticamente la memoria una vez que esté fuera de alcance de todos modos, por lo que la mejor solución (en términos de esta respuesta, no la pregunta del OP) es usar funciones cortas para que no se aferren a esa variable del ciclo demasiado largo.Hay varios puntos posibles de pérdida de memoria en php:
Es bastante difícil encontrar y arreglar los primeros 3 sin un profundo conocimiento de la ingeniería inversa o del código fuente php. Para el último, puede usar la búsqueda binaria para el código con fugas de memoria con memory_get_usage
fuente
Recientemente me encontré con este problema en una aplicación, bajo circunstancias similares. Un script que se ejecuta en el cli de PHP que recorre muchas iteraciones. Mi script depende de varias bibliotecas subyacentes. Sospecho que una biblioteca en particular es la causa y pasé varias horas en vano tratando de agregar métodos de destrucción apropiados a sus clases en vano. Enfrentado con un largo proceso de conversión a una biblioteca diferente (que podría resultar tener los mismos problemas), se me ocurrió una solución burda para el problema en mi caso.
En mi situación, en un linux cli, estaba recorriendo un montón de registros de usuario y para cada uno de ellos creando una nueva instancia de varias clases que creé. Decidí intentar crear las nuevas instancias de las clases usando el método exec de PHP para que esos procesos se ejecutaran en un "nuevo hilo". Aquí hay una muestra realmente básica de lo que me refiero:
Obviamente, este enfoque tiene limitaciones, y uno debe ser consciente de los peligros de esto, ya que sería fácil crear un trabajo de conejo, sin embargo, en algunos casos raros, podría ayudar a superar un punto difícil, hasta que se pueda encontrar una mejor solución. , como en mi caso.
fuente
Me encontré con el mismo problema y mi solución fue reemplazar foreach con un for. No estoy seguro de los detalles, pero parece que foreach crea una copia (o de alguna manera una nueva referencia) al objeto. Usando un bucle for regular, accede al elemento directamente.
fuente
Le sugiero que consulte el manual de php o agregue la
gc_enable()
función para recolectar la basura ... Es decir, las pérdidas de memoria no afectan la forma en que se ejecuta su código.PD: php tiene un recolector de basura
gc_enable()
que no acepta argumentos.fuente
Recientemente me di cuenta de que las funciones lambda de PHP 5.3 dejan memoria adicional utilizada cuando se eliminan.
No estoy seguro de por qué, pero parece que se necesitan 250 bytes adicionales cada lambda incluso después de que se elimina la función.
fuente
Si lo que dice acerca de que PHP solo hace GC después de una función es verdadero, podría envolver el contenido del ciclo dentro de una función como una solución / experimento.
fuente
run()
que se llama también es una función, al final de la cual debería ocurrir la GC.Un gran problema que tuve fue al usar create_function . Como en las funciones lambda, deja el nombre temporal generado en la memoria.
Otra causa de pérdidas de memoria (en el caso de Zend Framework) es Zend_Db_Profiler. Asegúrese de que esté deshabilitado si ejecuta scripts en Zend Framework. Por ejemplo, tenía en mi application.ini lo siguiente:
Ejecutar aproximadamente 25.000 consultas + cargas de procesamiento antes de eso, llevó la memoria a un agradable 128Mb (Mi límite máximo de memoria).
Simplemente configurando:
fue suficiente para mantenerlo por debajo de 20 Mb
Y este script se estaba ejecutando en CLI, pero estaba instanciando Zend_Application y ejecutando Bootstrap, por lo que usó la configuración de "desarrollo".
Realmente ayudó a ejecutar el script con la creación de perfiles xDebug
fuente
No vi que se mencionara explícitamente, pero xdebug hace un gran trabajo al perfilar el tiempo y la memoria (a partir de 2.6 ). Puede tomar la información que genera y pasarla a una interfaz gráfica de usuario de su elección: webgrind (solo tiempo), kcachegrind , qcachegrind u otros y genera árboles de llamadas y gráficos muy útiles para que pueda encontrar las fuentes de sus diversos problemas. .
Ejemplo (de qcachegrind):
fuente
Llego un poco tarde a esta conversación, pero compartiré algo pertinente a Zend Framework.
Tuve un problema de pérdida de memoria después de instalar php 5.3.8 (usando phpfarm) para trabajar con una aplicación ZF que fue desarrollada con php 5.2.9. Descubrí que la pérdida de memoria se estaba activando en el archivo httpd.conf de Apache, en mi definición de host virtual, donde dice
SetEnv APPLICATION_ENV "development"
. Después de comentar esta línea, las pérdidas de memoria se detuvieron. Estoy tratando de encontrar una solución alternativa en línea en mi script php (principalmente definiéndolo manualmente en el archivo index.php principal).fuente
"development"
entorno suele tener un montón de registros y perfiles que otros entornos podrían no tener. Al comentar la salida de línea, su aplicación solo usó el entorno predeterminado, que generalmente es"production"
o"prod"
. La pérdida de memoria todavía existe; el código que lo contiene simplemente no se llama en ese entorno.No vi que se mencionara aquí, pero una cosa que podría ser útil es usar xdebug y xdebug_debug_zval ('variableName') para ver el refcount.
También puedo dar un ejemplo de una extensión php que se interpone en el camino: Z-Ray de Zend Server. Si la recopilación de datos está habilitada, el uso de la memoria aumentará en cada iteración como si la recolección de basura estuviera desactivada.
fuente