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

StackOverflow https://stackoverflow.com/questions/209875

Pregunta

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 antiguo Core 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 la memoria supera los 160 MB. Incluso durante la carga alta, nuestra aplicación nunca supera los 120 MB y, junto con otros procesos nativos que están activos, nos mantenemos dentro del límite OOM.

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

Actualmente, para solucionar este problema, tenemos un programa nativo independiente que ejecuta 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 sobre este problema en línea obtuve algunos comentarios que indican que esto no debería ocurrir en " más reciente " versiones de Linux, ya que implementan el método de bifurcación de posix utilizando copiar-en-escribir, lo que probablemente significa que solo copiará las páginas que necesite modificar cuando sea necesario 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 algún otro lugar completamente?
  • ¿De qué versión del kernel / libc / es la copia para escribir para fork () disponible?
¿Fue útil?

Solución

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

Para crear un nuevo proceso en * nixes que llamas 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 copia en escritura, por lo que (en casos óptimos) no se copia realmente la memoria, solo las asignaciones. Una siguiente llamada exec () reemplaza la asignación de memoria actual con la del nuevo ejecutable. Entonces, fork () / exec () es la forma en que creas un nuevo proceso y eso es lo que usa la JVM.

La advertencia es con procesos enormes en un sistema ocupado, el padre puede continuar ejecutándose por un tiempo antes de que el exec () del niño haga que se copie una gran cantidad de memoria para la copia en escritura. En las máquinas virtuales, la memoria se puede mover mucho para facilitar un recolector de basura que produce aún más copias.

La " solución temporal " es hacer lo que ya ha hecho, crear un proceso liviano externo que se encargue de generar nuevos procesos, o usar un enfoque más liviano que los procesos de bifurcación / ejecución (que Linux no tiene) y de todos modos requeriría un cambio en el propio jvm). 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 lo es.

Otros consejos

Bueno, personalmente dudo que esto sea cierto, ya que la bifurcación de Linux () se realiza a través de copiar-en-escribir ya que Dios sabe cuándo (al menos, los kernels 2.2.x lo tenían, y estaba en algún lugar en el 199x).

Dado que se cree que OOM killer es un instrumento bastante tosco que se sabe que falla (fe, no es necesario eliminar el proceso que realmente asignó la mayor parte de la memoria) y que debería usarse solo como un último informe, es no me queda claro por qué lo tiene configurado para disparar en 160M.

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

Mi consejo es dejar la OOM sola (o deshabilitarla por completo), configurar ulimits y olvidarse de este problema.

Sí, este es absolutamente el caso incluso con las nuevas versiones de Linux (estamos en Red Hat 5.2 de 64 bits). He tenido un problema con los 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 utilizando Runtime.exec () con 16 subprocesos, no podremos ejecutar nuestro proceso más rápido que alrededor de 20 llamadas de proceso por segundo.

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

Después de leer tu pregunta, decidí intentar reducir 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 chupando la memoria de tal manera que me quede sin memoria, incluso cuando la probé con 32 hilos. Es solo que la memoria adicional debe asignarse de alguna manera, lo que provoca un alto costo de inicio (y tal vez de apagado).

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

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

El asesino de OOM entra en acción cuando la memoria baja se ve amenazada en cajas de 32 bits. Nunca he tenido un problema con esto, pero hay maneras de mantener a raya a OOM. Este problema podría ser un problema de configuración de OOM.

Ya que 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 y cuando se instalen las bibliotecas relevantes.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top