2009-02-18 20 views
215

Tengo un problema con una aplicación Java que se ejecuta en Linux.Uso de memoria virtual de Java en Linux, demasiada memoria utilizada

Cuando lance la aplicación, utilizando el tamaño de almacenamiento dinámico máximo predeterminado (64 MB), veo utilizando la aplicación tops que 240 MB de memoria virtual se asignan a la aplicación. Esto crea algunos problemas con algún otro software en la computadora, que tiene relativamente pocos recursos.

La memoria virtual reservada no se utilizará de todos modos, por lo que yo entiendo, porque una vez que alcanzamos el límite de montón se lanza un OutOfMemoryError. Ejecuté la misma aplicación en Windows y veo que el tamaño de la memoria virtual y el tamaño de Heap son similares.

¿Hay alguna forma de que pueda configurar la memoria virtual en uso para un proceso de Java en Linux?

Editar 1: El problema no es el Heap. El problema es que si me puse un montón de 128 MB, por ejemplo, todavía Linux asigna 210 MB de memoria virtual, que no es necesario, siempre **

Editar 2:. El uso de ulimit -v permite limitar la cantidad de memoria virtual. Si el tamaño establecido es inferior a 204 MB, la aplicación no se ejecutará aunque no necesite 204 MB, solo 64 MB. Entonces quiero entender por qué Java requiere tanta memoria virtual. ¿Se puede cambiar esto?

Editar 3: Hay varias otras aplicaciones ejecutándose en el sistema, que está incrustado. Y el sistema tiene un límite de memoria virtual (a partir de comentarios, detalles importantes).

+0

¿Por qué estás preocupado por el uso de memoria virtual? Si realmente quieres preocuparte, mira el uso de la memoria residente y lee los siguientes comandos: free, ps, top. – basszero

+2

Hay varias otras aplicaciones ejecutándose en el sistema, que está incrustado. Y el sistema tiene un límite de memoria virtual. –

+0

ahhhh, diablo está en los detalles – basszero

Respuesta

532

Esto ha sido una queja de larga data con Java, pero en gran medida no tiene sentido, y generalmente se basa en buscar la información incorrecta. El fraseo habitual es algo así como "¡Hello World on Java toma 10 megabytes! ¿Por qué necesita eso?" Bueno, aquí hay una manera de hacer que Hello World en una afirmación JVM de 64 bits tome más de 4 gigabytes ... al menos en una forma de medición.

 
java -Xms1024m -Xmx4096m com.example.Hello 

Diferentes formas de medir la memoria

En Linux, el comando top le da varios números diferentes para la memoria. Aquí está lo que dice sobre el ejemplo Hello World:

 
    PID USER  PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 
2120 kgregory 20 0 4373m 15m 7152 S 0 0.2 0:00.10 java 
  • VIRT es el espacio de memoria virtual: la suma de todo en el mapa de memoria virtual (ver más abajo). En gran medida no tiene sentido, excepto cuando no lo es (ver abajo).
  • RES es el tamaño del conjunto residente: el número de páginas que actualmente residen en la memoria RAM. En casi todos los casos, este es el único número que debe usar cuando dice "demasiado grande". Pero todavía no es un número muy bueno, especialmente cuando se habla de Java.
  • SHR es la cantidad de memoria residente que se comparte con otros procesos. Para un proceso de Java, esto generalmente se limita a bibliotecas compartidas y archivos JAR mapeados en memoria. En este ejemplo, solo tenía un proceso en ejecución Java, así que sospecho que el 7k es el resultado de las bibliotecas utilizadas por el sistema operativo.
  • SWAP no está activado de manera predeterminada, y no se muestra aquí. Indica la cantidad de memoria virtual que reside actualmente en el disco, ya sea que esté o no en el espacio de intercambio. El sistema operativo es muy bueno para mantener páginas activas en la RAM, y las únicas soluciones para el intercambio son (1) comprar más memoria, o (2) reducir el número de procesos, por lo que es mejor ignorar este número.

La situación del Administrador de tareas de Windows es un poco más complicada. En Windows XP, hay columnas de "Uso de la memoria" y "Tamaño de la memoria virtual", pero el official documentation no dice nada sobre lo que significan. Windows Vista y Windows 7 agregan más columnas, y en realidad son documented. De estos, la medida "Working Set" es la más útil; corresponde aproximadamente a la suma de RES y SHR en Linux.

La comprensión de la memoria virtual Mapa

La memoria virtual consumida por un proceso es el total de todo lo que está en el proceso de asignación de memoria. Esto incluye datos (por ejemplo, el montón de Java), pero también todas las bibliotecas compartidas y los archivos mapeados en memoria utilizados por el programa. En Linux, puede usar el comando pmap para ver todas las cosas mapeadas en el espacio de proceso (a partir de ahora solo me voy a referir a Linux, porque es lo que uso, estoy seguro de que hay herramientas equivalentes para Windows). Aquí hay un extracto del mapa de memoria del programa "Hello World"; todo el mapa de memoria tiene más de 100 líneas, y no es inusual tener una lista de mil líneas.

 
0000000040000000  36K r-x-- /usr/local/java/jdk-1.6-x64/bin/java 
0000000040108000  8K rwx-- /usr/local/java/jdk-1.6-x64/bin/java 
0000000040eba000 676K rwx-- [ anon ] 
00000006fae00000 21248K rwx-- [ anon ] 
00000006fc2c0000 62720K rwx-- [ anon ] 
0000000700000000 699072K rwx-- [ anon ] 
000000072aab0000 2097152K rwx-- [ anon ] 
00000007aaab0000 349504K rwx-- [ anon ] 
00000007c0000000 1048576K rwx-- [ anon ] 
... 
00007fa1ed00d000 1652K r-xs- /usr/local/java/jdk-1.6-x64/jre/lib/rt.jar 
... 
00007fa1ed1d3000 1024K rwx-- [ anon ] 
00007fa1ed2d3000  4K ----- [ anon ] 
00007fa1ed2d4000 1024K rwx-- [ anon ] 
00007fa1ed3d4000  4K ----- [ anon ] 
... 
00007fa1f20d3000 164K r-x-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 
00007fa1f20fc000 1020K ----- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 
00007fa1f21fb000  28K rwx-- /usr/local/java/jdk-1.6-x64/jre/lib/amd64/libjava.so 
... 
00007fa1f34aa000 1576K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so 
00007fa1f3634000 2044K ----- /lib/x86_64-linux-gnu/libc-2.13.so 
00007fa1f3833000  16K r-x-- /lib/x86_64-linux-gnu/libc-2.13.so 
00007fa1f3837000  4K rwx-- /lib/x86_64-linux-gnu/libc-2.13.so 
... 

Una explicación rápida del formato: cada fila comienza con la dirección de memoria virtual del segmento. Esto es seguido por el tamaño del segmento, los permisos y el origen del segmento. Este último elemento es un archivo o "anon", que indica un bloque de memoria asignado a través del mmap.

A partir de la parte superior, que tienen

  • El cargador de JVM (es decir, el programa que se ejecuta cuando se escribe java). Esto es muy pequeño; todo lo que hace es cargar en las bibliotecas compartidas donde se almacena el código JVM real.
  • Un montón de bloques anón que contienen el montón de Java y los datos internos. Esta es una JVM de Sun, por lo que la pila se divide en varias generaciones, cada una de las cuales es su propio bloque de memoria. Tenga en cuenta que la JVM asigna espacio de memoria virtual basado en el valor -Xmx; esto le permite tener un montón contiguo. El valor -Xms se usa internamente para indicar qué parte del montón está "en uso" cuando se inicia el programa y para desencadenar la recolección de elementos no utilizados cuando se llega a ese límite.
  • Un archivo JAR mapeado en memoria, en este caso el archivo que contiene las "clases JDK". Cuando asigna un mapa de memoria a un JAR, puede acceder a los archivos dentro de él de manera muy eficiente (en lugar de leerlo desde el principio cada vez). Sun JVM mapeará en memoria todos los JAR en classpath; si su código de aplicación necesita acceder a un JAR, también puede mapearlo en la memoria.
  • Per-thread data for two threads. El bloque de 1M es una pila de hilos; No sé lo que entra en el bloque 4K. Para una aplicación real, verá docenas o cientos de estas entradas repetidas a través del mapa de memoria.
  • Una de las bibliotecas compartidas que contiene el código JVM real. Hay varios de estos.
  • La biblioteca compartida para la biblioteca estándar C. Esta es solo una de las muchas cosas que carga la JVM que no son estrictamente parte de Java.

Las bibliotecas compartidas son particularmente interesantes: cada biblioteca compartida tiene al menos dos segmentos: un segmento de solo lectura que contiene el código de biblioteca y un segmento de lectura y escritura que contiene datos globales por proceso para la biblioteca (I no sé cuál es el segmento sin permisos, solo lo he visto en Linux x64). La parte de solo lectura de la biblioteca se puede compartir entre todos los procesos que usan la biblioteca; por ejemplo, libc tiene 1.5M de espacio de memoria virtual que se puede compartir.

¿Cuándo es importante el tamaño de la memoria virtual?

El mapa de memoria virtual contiene muchas cosas. Algunos de ellos son de solo lectura, algunos de ellos son compartidos, y algunos de ellos se asignan pero nunca se tocan (por ejemplo, casi todos los 4 Gb de Heap en este ejemplo). Pero el sistema operativo es lo suficientemente inteligente como para cargar solo lo que necesita, por lo que el tamaño de la memoria virtual es en gran medida irrelevante.

Donde el tamaño de la memoria virtual es importante es si está ejecutando en un sistema operativo de 32 bits, donde solo puede asignar 2Gb (o, en algunos casos, 3Gb) de espacio de direcciones de proceso. En ese caso, se trata de un recurso escaso y podría tener que hacer concesiones, como reducir el tamaño de almacenamiento dinámico para mapear en memoria un archivo grande o crear muchos subprocesos.

Pero, dado que las máquinas de 64 bits son omnipresentes, no creo que pase mucho tiempo antes de que el Tamaño de memoria virtual sea una estadística completamente irrelevante.

¿Cuándo es importante el tamaño del conjunto de residentes?

Tamaño de conjunto de residentes es la parte del espacio de memoria virtual que está realmente en la memoria RAM. Si su RSS se convierte en una parte importante de su memoria física total, podría ser hora de comenzar a preocuparse. Si su RSS crece para ocupar toda su memoria física y su sistema comienza a intercambiarse, ya es hora de que empiece a preocuparse.

Pero RSS también es engañoso, especialmente en una máquina con poca carga. El sistema operativo no se esfuerza demasiado para reclamar las páginas utilizadas por un proceso. Se puede obtener poco beneficio al hacerlo, y la posibilidad de un costoso error de página si el proceso toca la página en el futuro. Como resultado, la estadística RSS puede incluir muchas páginas que no están en uso activo.

línea de base

A menos que estás cambiando, no se preocupa demasiado por lo que las diferentes estadísticas de memoria que le están diciendo. Con la advertencia de que un RSS cada vez mayor puede indicar algún tipo de pérdida de memoria.

Con un programa Java, es mucho más importante prestar atención a lo que está sucediendo en el montón. La cantidad total de espacio consumido es importante, y hay algunos pasos que puede seguir para reducir eso. Más importante es la cantidad de tiempo que pasas en la recolección de basura, y qué partes del montón se están recolectando.

El acceso al disco (es decir, una base de datos) es costoso y la memoria es barata. Si puede intercambiar uno por el otro, hágalo.

+6

Debe tener en cuenta que las partes de la memoria que se intercambian actualmente faltan en la medida RES. Por lo tanto, es posible que tenga un valor RES bajo, pero solo porque la aplicación estaba inactiva y gran parte del almacenamiento dinámico se intercambió en el disco. Java hace un trabajo muy malo para intercambiar: en cada GC completo, la mayor parte del montón se recorre y se copia, por lo que si gran parte de tu montón estaba en intercambio, el GC tiene que cargarlo todo de nuevo en la memoria principal. – jrudolph

+1

¡Gran respuesta kdgregory! Me estoy ejecutando en un entorno incrustado usando un CF que no tiene espacio de intercambio. Por lo tanto, según su respuesta, todos mis valores VIRT, SWAP y nFLT provienen de archivos mapeados en memoria ... lo cual ahora tiene sentido para maullar. ¿Sabe si el valor SWAP representa páginas que aún no se han cargado en la memoria o páginas que se han intercambiado de memoria, o ambas? ¿Cómo podemos tener una idea de una posible golpiza (mapa continuo en luego cambiar)? – Jeach

+2

@Jeach - Me sorprendió que se haya informado sobre algún intercambio, así que inicié mi "Linux viajero" (un disco USB con Ubuntu 10.04 y sin intercambio). Cuando habilité la columna "SWAP" en * top *, vi que Eclipse tenía 509m. Cuando lo miré con * pmap *, el espacio virtual total era de 650 m. Así que sospecho que la figura "SWAP" representa todas las páginas en disco, no solo aquellas que no están en la memoria. – kdgregory

0

No, no puede configurar la cantidad de memoria que necesita VM. Sin embargo, tenga en cuenta que se trata de memoria virtual, no residente, por lo que simplemente permanece allí sin daños si no se utiliza realmente.

Alernativamente, puedes probar con otra JVM y luego con Sun, con una huella de memoria más pequeña, pero no puedo asesorar aquí.

1

Java de Sun 1.4 tiene los siguientes argumentos para controlar el tamaño de la memoria:

-Xmsn Especificar el tamaño inicial, en bytes, de la memoria reservada. Este valor debe ser un múltiplo de 1024 mayor que 1 MB. Agregue la letra k o K para indicar kilobytes, o m o M para indicar megabytes. El valor predeterminado es 2MB. Ejemplos:

  -Xms6291456 
      -Xms6144k 
      -Xms6m 

-Xmxn especificar el tamaño máximo, en bytes, de la memoria reservada. Este valor debe ser un múltiplo de 1024 mayor que 2MB. Agregue la letra k o K para indicar kilobytes, o m o M para indicar megabytes. El valor predeterminado es 64 MB. Ejemplos:

  -Xmx83886080 
      -Xmx81920k 
      -Xmx80m 

http://java.sun.com/j2se/1.4.2/docs/tooldocs/windows/java.html

Java 5 y 6 tienen un poco más.Ver http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp

+0

El problema que tengo no está en el tamaño del montón, sino en la cantidad de memoria virtual asignada por Linux –

+0

Lea la explicación de kdgregory. La reducción del tamaño del almacenamiento dinámico, el "Nuevo tamaño" y los demás parámetros configurables reducirán la cantidad de memoria REAL que toma jvm. –

+0

Puede tener un problema legítimo. Algunas aplicaciones (como una que escribí) mapean un archivo de 1 GB y algunos sistemas solo tienen 2 GB de memoria virtual, algunos de los cuales se llenan con bibliotecas compartidas. Y si este es el problema, definitivamente debería deshabilitar la aleatorización de DSO. Hay una opción en/proc. –

2

Solo un pensamiento, pero puede verificar la influencia de a ulimit -v option.

No es una solución real, ya que limitaría el espacio de direcciones disponible para todo el proceso, pero eso le permitiría verificar el comportamiento de su aplicación con una memoria virtual limitada.

+0

Eso es exactamente lo que es mi problema. My Heap está configurado en 64M, pero Linux reserva 204MB. Si configuro el ulimit debajo de 204, la aplicación no se ejecuta en absoluto. –

+0

Interesante: configurar el ulimit podría tener un efecto secundario involuntario para otros procesos, explicando por qué la aplicación no se puede ejecutar. – VonC

+0

El problema parece ser que Java requiere reservar esta gran cantidad de memoria virtual aunque no la use. En Windows, la memoria virtual utilizada y la configuración de Xmx son bastante más cercanas. –

9

La cantidad de memoria asignada para el proceso de Java es casi igual a la que yo esperaría. He tenido problemas similares al ejecutar Java en sistemas incrustados/memoria limitada. Al ejecutar cualquier aplicación con límites de máquina virtual arbitrarios o en sistemas que no tienen cantidades adecuadas de intercambio, tienden a romperse. Parece ser la naturaleza de muchas aplicaciones modernas que no están diseñadas para su uso en sistemas de recursos limitados.

Tiene algunas opciones más que puede probar y limitar la memoria de su JVM. Esto podría reducir el consumo de memoria virtual:

-XX: ReservedCodeCacheSize = 32m Reservados tamaño de la caché de código (en bytes) - tamaño máximo de caché código. [Solaris de 64 bits, amd64 y -server x86: 48m; en 1.5.0_06 y anteriores, Solaris de 64 bits yy64: 1024m.]

-XX: MaxPermSize = 64m Tamaño de la generación permanente. [5.0 y más reciente: Las máquinas virtuales de 64 bits se escalaron un 30% más; 1.4 amd64: 96m; 1.3.1 -client:. 32m]

Además, también debe configurar su -Xmx (máximo tamaño de la pila) a un valor lo más cercano posible al pico real de uso de la memoria de su aplicación. Creo que el comportamiento predeterminado de la JVM sigue siendo double el tamaño del montón cada vez que lo expande hasta el máximo. Si comienzas con un montón de 32M y tu aplicación alcanzó un máximo de 65M, entonces el montón terminaría creciendo 32M -> 64M -> 128M.

También puede intentar esto para hacer la máquina virtual menos agresivo sobre el crecimiento de la pila:

-XX: MinHeapFreeRatio = 40 Porcentaje mínimo del montón libre después de GC para evitar la expansión .

Además, por lo que recuerdo de experimentar con esto hace unos años, el número de bibliotecas nativas cargadas tuvo un gran impacto en la huella mínima. Cargando java.net.Socket agregó más de 15M si recuerdo correctamente (y probablemente no).

7

Sun JVM requiere mucha memoria para HotSpot y se asigna en las bibliotecas de tiempo de ejecución de la memoria compartida.

Si la memoria es un problema, considere utilizar otra JVM adecuada para la incrustación. IBM tiene j9, y existe el código abierto "jamvm" que utiliza las bibliotecas GNU classpath. También Sun tiene Squeak JVM ejecutándose en SunSPOTS, por lo que hay alternativas.

+0

¿Es una opción para desactivar la zona activa? –

+0

Quizás. Verifique las opciones de línea de comando para la JVM que usa. –

2

Una forma de reducir el montón de un sistema con recursos limitados puede ser jugar con la variable -XX: MaxHeapFreeRatio. Esto generalmente se establece en 70, y es el porcentaje máximo del montón que está libre antes de que el GC lo reduzca. Estableciéndolo en un valor inferior, y verá en, por ejemplo, el perfil jvisualvm que un montón de montón más pequeño se usa generalmente para su programa.

EDIT: Para establecer los valores pequeños para -XX: MaxHeapFreeRatio También debe establecer -XX: MinHeapFreeRatio Ej

java -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=25 HelloWorld 

Edit2: Se ha añadido un ejemplo de una aplicación real que se inicia y hace la misma tarea, uno con parámetros por defecto y uno con 10 y 25 como parámetros. No noté ninguna diferencia de velocidad real, aunque Java en teoría debería usar más tiempo para aumentar el montón en el último ejemplo.

Default parameters

Al final, montón max es 905, montón utilizado es 378

MinHeap 10, MaxHeap 25

Al final, montón max es 722, montón utilizado es 378

En realidad, esto tener algún impacto, ya que nuestra aplicación se ejecuta en un servidor de escritorio remoto, y muchos usuarios pueden ejecutarlo a la vez.

30

Existe un problema conocido con Java y glibc> = 2.10 (incluye Ubuntu> = 10.04, RHEL> = 6).

La cura es establecer este env. variable: export MALLOC_ARENA_MAX=4 Si está ejecutando Tomcat, puede agregar esto al archivo TOMCAT_HOME/bin/setenv.sh.

hay un artículo acerca de la configuración de IBM MALLOC_ARENA_MAX https://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en

This blog post says

memoria residente se ha sabido para deslizarse de una manera similar a una fuga memoria o la fragmentación de memoria.

busque MALLOC_ARENA_MAX en Google o SO para obtener más referencias.

Es posible que desee ajustar también otras opciones para optimizar malloc de baja fragmentación de memoria asignada:

# tune glibc memory allocation, optimize for low fragmentation 
# limit the number of arenas 
export MALLOC_ARENA_MAX=2 
# disable dynamic mmap threshold, see M_MMAP_THRESHOLD in "man mallopt" 
export MALLOC_MMAP_THRESHOLD_=131072 
export MALLOC_TRIM_THRESHOLD_=131072 
export MALLOC_TOP_PAD_=131072 
export MALLOC_MMAP_MAX_=65536 
+0

Esta respuesta realmente me ayudó en un servidor Ubuntu de 64 bits con un servidor TomEE que tardó un poco en "consumir mem". El enlace al artículo de IBM realmente es una explicación profunda. Gracias de nuevo por esta buena pista! – MWiesner

+1

La JVM puede perder una memoria nativa que produce síntomas similares. Ver http://stackoverflow.com/a/35610063/166062. Las instancias GZIPInputStream y GZIPOutputStream no cerradas también pueden ser una fuente de fuga. –

+2

Hay un error de JVM en Java 8, que da como resultado un crecimiento ilimitado de la memoria nativa: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8164293 - Si esto te está afectando, usa 'MALLOC_ARENA_MAX' puede ralentizar el crecimiento de su memoria, pero no resolver el problema por completo. – outofcoffee

Cuestiones relacionadas