java linux memory out-of-memory

¿De qué versión de Linux kernel/libc es Java Runtime.exec() seguro con respecto a la memoria?



memory out-of-memory (4)

1: Sí. 2: Esto se divide en dos pasos: Cualquier llamada al sistema como fork () está envuelta por glibc en el kernel. La parte del kernel de la llamada del sistema está en kernel / fork.c 3: No sé. Pero apostaría que tu kernel lo tiene.

El asesino de OOM se activa cuando la memoria baja está amenazada en cajas de 32 bits. Nunca tuve un problema con esto, pero hay formas de mantener a OOM a raya. Este problema podría ser un problema de configuración de OOM.

Como está utilizando una aplicación Java, debería considerar cambiarse a Linux de 64 bits. Eso definitivamente debería arreglarlo. La mayoría de las aplicaciones de 32 bits se pueden ejecutar en un kernel de 64 bits sin problemas, siempre que se instalen bibliotecas relevantes.

También puedes probar el kernel PAE para Fedora de 32 bits.

En el trabajo, una de nuestras plataformas de destino es un mini servidor con recursos limitados que ejecuta Linux (kernel 2.6.13, distribución personalizada basada en un viejo núcleo de Fedora). La aplicación está escrita en Java (Sun JDK 1.6_04). El asesino OOM de Linux está configurado para matar procesos cuando el uso de memoria excede los 160MB. Incluso durante cargas elevadas, nuestra aplicación nunca supera los 120 MB y junto con algunos otros procesos nativos que están activos, nos mantenemos dentro del límite de OOM.

Sin embargo, resulta que el método Java Runtime.getRuntime (). Exec (), la forma canónica de ejecutar procesos externos desde Java, tiene una implementación particularmente desafortunada en Linux que hace que los procesos secundarios generados (temporalmente) requieran la misma cantidad de memoria como el proceso principal ya que el espacio de direcciones se copia. El resultado neto es que nuestra aplicación es asesinada por el asesino OOM en cuanto hacemos Runtime.getRuntime (). Exec ().

Actualmente trabajamos en esto al tener un programa nativo separado para ejecutar todos los comandos externos y nos comunicamos con ese programa a través de un socket. Esto es menos que óptimo.

Después de publicar este problema en línea , obtuve algunos comentarios que indican que esto no debería ocurrir en versiones "más nuevas" de Linux ya que implementan el método posix fork () usando copy-on-write, lo que significa que solo copiará las páginas que necesita para modifique cuando se requiera en lugar de todo el espacio de direcciones inmediatamente.

Mis preguntas son:

  • ¿Es esto cierto?
  • ¿Es esto algo en el kernel, la implementación de libc o en otro lugar completamente?
  • ¿De qué versión de kernel / libc / whatever está disponible copy-on-write para fork ()?

Bueno, yo personalmente dudo que esto sea cierto, dado que el fork () de Linux se realiza mediante copy-on-write, ya que Dios sabe cuándo (al menos, los núcleos 2.2.x lo tenían, y estaba en alguna parte en los 199x).

Dado que se cree que el OOM killer es un instrumento bastante tosco que se sabe que falla (es decir, no es necesario que mate el proceso que realmente asignó la mayor parte de la memoria) y que debe usarse solo como último recuerdo, no está claro por qué lo tienes configurado para disparar en 160M.

Si desea imponer un límite a la asignación de memoria, entonces ulimit es su amigo, no OOM.

Mi consejo es dejar OOM solo (o deshabilitarlo por completo), configurar ulimits y olvidarse de este problema.


Esta es más o menos la forma en que * nix (y linux) han funcionado desde el comienzo de los tiempos (o hasta el amanecer de mmus).

Para crear un nuevo proceso en * nixes llame a fork (). fork () crea una copia del proceso de llamada con todas sus asignaciones de memoria, descriptores de archivos, etc. Las asignaciones de memoria se realizan con copia-en-escritura para que (en casos óptimos) no se copie la memoria, solo las asignaciones. Una llamada siguiente de exec () reemplaza la asignación de memoria actual con la del nuevo ejecutable. Entonces, fork () / exec () es la forma de crear un nuevo proceso y eso es lo que usa la JVM.

La advertencia es que con los procesos grandes en un sistema ocupado, el padre puede continuar ejecutándose por un tiempo antes de que el niño exec () ''s cause una gran cantidad de memoria para copiar debido a la copia en escritura. En las VM, la memoria se puede mover mucho para facilitar un recolector de basura que produce aún más copia.

La "solución alternativa" es hacer lo que ya ha hecho, crear un proceso ligero externo que se encargue de generar nuevos procesos, o utilizar un enfoque más ligero que fork / exec para generar procesos (lo que Linux no tiene) y de todos modos requiere un cambio en el jvm mismo). Posix especifica la función posix_spawn (), que en teoría se puede implementar sin copiar la asignación de memoria del proceso de llamada, pero en Linux no.


Sí, este es absolutamente el caso incluso con nuevas versiones de Linux (estamos en Red Hat 5.2 de 64 bits). He estado teniendo problemas con subprocesos de ejecución lenta durante aproximadamente 18 meses, y nunca pude resolver el problema hasta que leí su pregunta y realicé una prueba para verificarlo.

Tenemos una caja de 32 GB con 16 núcleos, y si ejecutamos la JVM con configuraciones como -Xms4g y -Xmx8g y ejecutamos subprocesos usando Runtime.exec () con 16 hilos, no podremos ejecutar nuestro proceso más rápido que aproximadamente 20 procesar llamadas por segundo.

Pruebe esto con el simple comando "fecha" en Linux unas 10,000 veces. Si agrega código de perfil para ver lo que está sucediendo, comienza rápidamente pero se ralentiza con el tiempo.

Después de leer su pregunta, decidí intentar bajar la configuración de mi memoria a -Xms128m y -Xmx128m. Ahora nuestro proceso se ejecuta en aproximadamente 80 llamadas de proceso por segundo. La configuración de la memoria JVM fue todo lo que cambié.

No parece estar absorbiendo memoria de tal manera que alguna vez me quede sin memoria, incluso cuando lo intenté con 32 hilos. Es solo que la memoria adicional tiene que ser asignada de alguna manera, lo que causa un fuerte inicio (y tal vez cierre) de costos.

De todos modos, parece que debería haber una configuración para desactivar este comportamiento Linux o tal vez incluso en la JVM.