una - Problemas de consumo de memoria de un programa Java
proyectos en java netbeans ejemplos (8)
¿Dices que estás creando archivos de imagen? ¿Estás creando objetos de imagen? Si es así, ¿está llamando dispose () en estos objetos cuando haya terminado?
Si recuerdo correctamente, los objetos java awt imagine asignan recursos nativos que deben eliminarse explícitamente.
Tengo un programa Java que se ejecuta en mi máquina Ubuntu 10.04 y, sin ninguna interacción del usuario, consulta repetidamente una base de datos MySQL y luego construye archivos img y txt de acuerdo con los datos leídos en la base de datos. Hace decenas de miles de consultas y crea decenas de miles de archivos.
Después de algunas horas de funcionamiento, la memoria disponible en mi máquina, incluido el espacio de intercambio, está totalmente agotada. No he iniciado otros programas y los procesos que se ejecutan en segundo plano no consumen mucha memoria y realmente no crecen en consumo.
Para averiguar qué está asignando tanta memoria, quería analizar un volcado de pila, así que comencé el proceso con -Xms64m -Xmx128m -XX: + HeapDumpOnOutOfMemoryError.
Para mi sorpresa, la situación era la misma que antes, después de algunas horas, el programa estaba asignando todo el intercambio, que está muy por encima del máximo dado de 128m.
Otra ejecución depurada con VisualVM mostró que la asignación del montón nunca está más allá del máximo de 128 m: cuando la memoria asignada se aproxima al máximo, una gran parte se libera nuevamente (supongo que el recolector de basura).
Por lo tanto, no puede ser un problema un montón en constante crecimiento.
Cuando la memoria está agotada:
Muestra gratis lo siguiente:
total used free shared buffers cached
Mem: 2060180 2004860 55320 0 848 1042908
-/+ buffers/cache: 961104 1099076
Swap: 3227640 3227640 0
La parte superior muestra lo siguiente:
USER VIRT RES SHR COMMAND
[my_id] 504m 171m 4520 java
[my_id] 371m 162m 4368 java
(de lejos, los dos procesos "más grandes" y los únicos procesos Java que se ejecutan)
Mi primera pregunta es:
- ¿Cómo puedo saber en el nivel del sistema operativo (por ejemplo, con las herramientas de la línea de comandos) qué es lo que está asignando tanta memoria? top / htop no me ha ayudado. En el caso de muchos, muchos procesos pequeños del mismo tipo se comen la memoria: ¿hay una manera de resumir de manera inteligente procesos similares? (Sé que probablemente no sea un tema, ya que es una pregunta de Linux / Ubuntu, pero mi problema principal aún puede estar relacionado con Java)
Mis viejas preguntas eran:
- ¿Por qué el consumo de memoria de mi programa no se encuentra en la salida superior?
- ¿Cómo puedo saber qué es la asignación de tanta memoria?
- Si la pila no es el problema, ¿es el único "factor de asignación" la pila? (la pila no debería ser un problema ya que no hay una "profundidad de llamada de método" profunda)
- ¿Qué pasa con los recursos externos como conexiones DB?
¿Estás creando hilos separados para ejecutar tus "tareas"? La memoria utilizada para crear subprocesos está separada del montón de Java.
Esto significa que incluso si especifica -Xmx128m
la memoria utilizada por el proceso Java podría ser mucho mayor, dependiendo de la cantidad de subprocesos que esté utilizando y del tamaño de pila de subprocesos (cada subproceso obtiene una pila asignada, del tamaño especificado por -Xss
) .
Ejemplo del trabajo recientemente: teníamos un montón de Java de 4 GB ( -Xmx4G
), pero el proceso del sistema operativo consumía más de 6 GB, también usaba el espacio de intercambio. Cuando verifiqué el estado del proceso con cat /proc/<PID>/status
, noté que teníamos 11000 subprocesos en ejecución. Desde que configuramos -Xss256K
, esto se explica fácilmente: 10000 hilos significa 2,5GB.
Como no hubo actividad después del día, hice la pregunta (hasta el 23 de marzo) y como todavía no podía encontrar la causa del consumo de memoria, "resolví" el problema de manera pragmática.
El programa que causa el problema es básicamente una repetición de una "tarea" (es decir, consultar una base de datos y luego crear archivos). Es relativamente fácil parametrizar el programa para que se ejecute un cierto subconjunto de tareas y no todas.
Así que ahora ejecuto repetidamente mi programa desde un script de shell, en cada proceso ejecutando solo un conjunto de tareas (parametrizadas a través de argumentos). Al final, todas las tareas se están ejecutando, pero como un solo proceso solo procesa un subconjunto de tareas, ya no hay problemas de memoria.
Para mí esa es una solución suficiente. Si tiene un problema similar y su programa tiene una estructura de ejecución similar a un lote, este puede ser un enfoque pragmático.
Cuando encuentre el tiempo, examinaré las nuevas sugerencias, con la esperanza de identificar la causa raíz (¡gracias por la ayuda!).
Como señalan @maximdim y @JamesBranigan, el culpable probable es una interacción nativa de su código. Pero como no ha podido rastrear exactamente dónde está la interacción problemática utilizando las herramientas disponibles, ¿por qué no intenta un enfoque de fuerza bruta?
Usted ha descrito un proceso de dos partes: consultar MySQL y escribir archivos. Cualquiera de estas cosas podría ser excluida del proceso como una prueba. Prueba uno: elimine la consulta y codifique el contenido que se habría devuelto. Prueba dos: haz la consulta, pero no te molestes en escribir los archivos. ¿Todavía tienes fugas?
También puede haber otros casos verificables, dependiendo de lo que haga su aplicación.
El almacenamiento en caché del sistema de archivos probablemente esté causando esto, la memoria caché del sistema de archivos consumirá toda la memoria disponible cuando se realice una gran cantidad de IO. El rendimiento de los sistemas no debe verse afectado negativamente por este comportamiento, el kernel lanzará inmediatamente la memoria caché del sistema de archivos cuando un proceso solicite memoria.
Hmm ... use ipcs para verificar que los segmentos de la memoria compartida no estén abiertos. Verifique los descriptores de archivo abiertos de su JVM ( /proc/<jvm proccess id>/fd/*
). En la parte superior, escriba fpFp
para mostrar intercambio y ordenar por intercambio usado de la lista de tareas.
Eso es todo lo que puedo hacer por ahora, espero que ayude al menos un poco.
La respuesta de @ maximdim es un gran consejo general para este tipo de situación. Lo que probablemente está sucediendo aquí es que se retiene un objeto Java muy pequeño que causa una mayor cantidad de memoria nativa (a nivel del sistema operativo). Esta memoria nativa no se contabiliza en el montón de Java. El objeto Java es probablemente tan pequeño que alcanzará el límite de memoria del sistema mucho antes de que la retención del objeto Java abrume el montón.
Entonces, el truco para encontrar esto es usar sucesivos volcados de pilas, lo suficientemente separados para que haya notado un crecimiento de la memoria durante todo el proceso, pero no tan lejos como para que se haya realizado una tonelada de trabajo. Lo que está buscando son recuentos de objetos Java en el montón que siguen aumentando y tienen memoria nativa adjunta.
Estos pueden ser identificadores de archivos, sockets, conexiones de base de datos o identificadores de imagen solo para nombrar algunos que probablemente sean directamente aplicables para usted.
En ocasiones más raras, hay un recurso nativo que se filtra por la implementación de Java, incluso después de que el objeto de Java se recolecta como basura. Una vez me topé con un error de WinCE 5 donde se filtraron 4k con cada socket cerca. Por lo tanto, no hubo crecimiento de objetos en Java, pero sí hubo crecimiento en el uso de la memoria de proceso. En estos casos, es útil hacer algunos contadores y realizar un seguimiento de las asignaciones java de objetos con memoria nativa frente al crecimiento real. Luego, en una ventana lo suficientemente corta, puede buscar las correlaciones y usarlas para hacer pequeños casos de prueba.
Otro consejo, asegúrese de que todas sus operaciones de cierre estén finalmente en bloques, en caso de que una excepción lo saque de su flujo de control normal. Se sabe que esto también causa este tipo de problema.
Si efectivamente su proceso de Java es el que se lleva la memoria y no hay nada sospechoso en VisualVM o volcado de memoria, entonces debe estar en algún lugar en el código nativo, ya sea en JVM o en algunas de las bibliotecas que está utilizando. En el nivel de JVM, podría ser, por ejemplo, si está utilizando NIO o archivos asignados a la memoria. Si algunas de sus bibliotecas utilizan llamadas nativas o si no usa el controlador JDBC tipo 4 para su base de datos, entonces podría haber una fuga.
Algunas sugerencias:
- Hay algunos detalles sobre cómo encontrar fugas de memoria en el código nativo here . Buena read también.
- Como de costumbre, asegúrese de estar cerrando correctamente todos los recursos (archivos, transmisiones, conexiones, subprocesos, etc.). La mayoría de estos están llamando a la implementación nativa en algún momento, por lo que la memoria consumida puede no ser directamente visible en JVM
- Compruebe los recursos consumidos en el nivel del sistema operativo: número de archivos abiertos, descriptores de archivos, conexiones de red, etc.