Pregunta

Tengo un problema con una aplicación Java que se ejecuta en Linux.

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

La memoria virtual reservada no se utilizará de todos modos, según tengo entendido, porque una vez que alcanzamos el límite del montón, OutOfMemoryError es aventado.Ejecuté la misma aplicación en Windows y veo que el tamaño de la memoria virtual y el tamaño del montón son similares.

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

Editar 1:El problema no es el montón.El problema es que si configuro un montón de 128 MB, por ejemplo, Linux aún asigna 210 MB de memoria virtual, que nunca es necesaria.**

Editar 2:Usando 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á integrado.Y el sistema sí tiene un límite de memoria virtual (según comentarios, detalle importante).

¿Fue útil?

Solución

Esta ha sido una queja de larga data con Java, pero en gran medida no tiene sentido y, por lo general, se basa en observar información incorrecta.La frase habitual es algo así como "¡Hola mundo en Java ocupa 10 megabytes!¿Por qué necesita eso?" Bueno, aquí hay una manera de hacer que Hello World en una JVM de 64 bits afirme ocupar más de 4 gigabytes...al menos mediante una forma de medición.

java -Xms1024m -Xmx4096m com.example.Hello

Diferentes formas de medir la memoria

En Linux, el arriba El comando le proporciona varios números diferentes para la memoria.Esto es lo que dice sobre el ejemplo de Hola Mundo:

  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 más abajo).
  • RES es el tamaño del conjunto residente:el número de páginas que residen actualmente en la RAM.En casi todos los casos, este es el único número que debe usar al decir "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 Java, esto normalmente se limita a bibliotecas compartidas y archivos JAR asignados en memoria.En este ejemplo, solo tenía un proceso Java ejecutándose, por lo que sospecho que 7k es el resultado de las bibliotecas utilizadas por el sistema operativo.
  • SWAP no está activado de forma predeterminada y no se muestra aquí.Indica la cantidad de memoria virtual que reside actualmente en el disco, si está o no realmente 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 la cantidad 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 "Uso de memoria" y "Tamaño de memoria virtual", pero las documentación oficial guarda silencio sobre lo que quieren decir.Windows Vista y Windows 7 agregan más columnas y, de hecho, son documentado.De éstas, la medición del "Conjunto de Trabajo" es la más útil;corresponde aproximadamente a la suma de RES y SHR en Linux.

Comprender el mapa de memoria virtual

La memoria virtual consumida por un proceso es el total de todo lo que está en el mapa de memoria del proceso.Esto incluye datos (por ejemplo, el montón de Java), pero también todas las bibliotecas compartidas y archivos mapeados en memoria utilizados por el programa.En Linux, puedes usar el mapap comando para ver todas las cosas asignadas al espacio de proceso (de ahora en adelante solo me referiré a Linux, porque es lo que uso;Estoy seguro de que existen 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.A esto le sigue 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 de mapa mm.

Empezando desde arriba, tenemos

  • El cargador JVM (es decir, el programa que se ejecuta cuando escribes 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ónimos que contienen el montón de Java y los datos internos.Se trata de una JVM de Sun, por lo que el montón 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 según la -Xmx valor;esto le permite tener un montón contiguo.El -Xms El valor se usa internamente para indicar qué parte del montón está "en uso" cuando se inicia el programa y para activar la recolección de basura a medida que se acerca a ese límite.
  • Un archivo JAR asignado a la memoria, en este caso el archivo que contiene las "clases JDK". Cuando asigna un mapa de memoria de un JAR, puede acceder a los archivos que contiene de manera muy eficiente (en lugar de leerlo desde el principio cada vez).Sun JVM asignará en memoria todos los JAR en el classpath;Si el código de su aplicación necesita acceder a un JAR, también puede asignarlo en memoria.
  • Datos por subproceso para dos subprocesos.El bloque de 1M es una pila de subprocesos;No sé qué incluye el bloque 4K.Para una aplicación real, verá docenas, si no cientos, de estas entradas repetidas en el 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 sólo una de las muchas cosas que carga la JVM y 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 la biblioteca y un segmento de lectura y escritura que contiene datos globales por proceso para la biblioteca (no sé cuál es el segmento sin permisos;Sólo 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,5 M 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.Parte es de solo lectura, parte se comparte y parte se asigna pero nunca se toca (por ejemplo, casi todos los 4 Gb del montón en este ejemplo).Pero el sistema operativo es lo suficientemente inteligente como para cargar sólo 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 un sistema operativo de 32 bits, donde solo puede asignar 2 Gb (o, en algunos casos, 3 Gb) de espacio de direcciones de proceso.En ese caso, se trata de un recurso escaso y es posible que tenga que hacer concesiones, como reducir el tamaño del montón 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 la memoria virtual sea una estadística completamente irrelevante.

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

El tamaño del conjunto residente es la porción del espacio de memoria virtual que realmente se encuentra en la RAM.Si su RSS crece hasta convertirse en una parte importante de su memoria física total, puede que sea hora de empezar a preocuparse.Si su RSS crece hasta ocupar toda su memoria física y su sistema comienza a intercambiar, ya es hora de comenzar a preocuparse.

Pero RSS también es engañoso, especialmente en una máquina con poca carga.El sistema operativo no dedica mucho esfuerzo a recuperar las páginas utilizadas por un proceso.Se obtendrán pocos beneficios al hacerlo y existe la posibilidad de que se produzca un costoso error en la página si el proceso afecta a 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 fondo

A menos que esté intercambiando, no se preocupe demasiado por lo que le dicen las diversas estadísticas de memoria.Con la advertencia de que un RSS en constante crecimiento 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 sucede en el montón.La cantidad total de espacio consumido es importante y existen algunas medidas que puede seguir para reducirla.Más importante es la cantidad de tiempo que dedica a la recolección de basura y qué partes del montón se recolectan.

Acceder al disco (es decir, a una base de datos) es caro y la memoria es barata.Si puedes cambiar uno por el otro, hazlo.

Otros consejos

Hay un problema conocido con Java y glibc> = 2,10 (incluye Ubuntu> = 10,04, RHEL> = 6).

La cura es fijar este env. Variable:

export MALLOC_ARENA_MAX=4

Si está ejecutando Tomcat, se puede añadir esto a TOMCAT_HOME/bin/setenv.sh archivo.

Para estibador, añadir esto a Dockerfile

ENV MALLOC_ARENA_MAX=4

Hay un artículo acerca de la configuración de IBM MALLOC_ARENA_MAX https://www.ibm.com/developerworks/ comunidad / blogs / kevgrig / entrada / linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage? lang = es

Esta entrada de blog dice

  

memoria residente se ha conocido a la fluencia de una manera similar a una   pérdida de memoria o la fragmentación de memoria.

También hay un fallo abierta JDK JDK-8193521 "memoria desechos glibc con la configuración por defecto "

búsqueda de MALLOC_ARENA_MAX en Google o SO para 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

La cantidad de memoria asignada para el proceso de Java es más o menos a la par con lo que cabe esperar. He tenido problemas similares en ejecución Java en sistemas limitados incrustado / memoria. Correr cualquier aplicación con límites arbitrarios o VM en sistemas que no tienen cantidades adecuadas de intercambio tienden a romper. Parece que es la naturaleza de muchas de las aplicaciones modernas que no se diseño para su uso en sistemas de recursos limitados.

Usted tiene algunas opciones más se puede tratar de limitar su consumo de memoria de JVM. Esto podría reducir el consumo de memoria virtual:

  

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

     

-XX: MaxPermSize = 64m Tamaño de la Generación Permanente. [5.0 y más reciente:   64 VM bits se escalan 30% más grande; 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 a la pico real de uso de la memoria de su aplicación. Creo que el comportamiento por defecto de la JVM está todavía a doble el tamaño de la pila cada vez que lo expande hasta el máximo. Si usted comienza con el montón 32M y su aplicación alcanzó su punto máximo a 65M, entonces el montón terminaría crecimiento 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 de montón libre después de GC a   evitar la expansión.

Además, de lo que recuerdo de experimentar con esto hace unos años, el número de bibliotecas nativas cargado tuvo un enorme impacto en la huella mínima. Cargando java.net.Socket añadido más de 15 millones si no recuerdo mal (y probablemente no lo hacen).

The Sun JVM requiere una gran cantidad de memoria para HotSpot y mapas en las bibliotecas de ejecución en la memoria compartida.

Si la memoria no es un problema considere el uso de otra JVM adecuada para incrustar. IBM tiene J9, y existe la "jamvm" código abierto que utiliza las bibliotecas de ruta de clases de GNU. También tiene el sol Squeak JVM se ejecuta en las manchas solares por lo que hay alternativas.

Es sólo una idea, pero es posible comprobar la influencia de la una opción ulimit -v .

Esto no es una solución real, ya que limitaría el espacio de direcciones disponible para todos proceso, pero que permitirá comprobar el comportamiento de su aplicación con una memoria virtual limitada.

Una forma de reducir el SICE montón de un sistema con recursos limitados pueden ser jugar un poco con el -XX: variable de MaxHeapFreeRatio. Esto normalmente se establece en 70, y es el porcentaje máximo de la pila que está libre antes de la GC se encoge él. Si se establece en un valor más bajo, y verá por ejemplo, en el generador de perfiles jvisualvm que un montón más pequeño SICE se utiliza generalmente para su programa.

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

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

Edit2: Añadido un ejemplo de una aplicación real que se inicia y hace la misma tarea, uno con parámetros por defecto y una con 10 y 25 como parámetros. No me di cuenta cualquier diferencia de velocidad real, aunque java en teoría debería utilizar más tiempo para aumentar el montón en el último ejemplo.

parámetros predeterminados

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 tiene alguna inpact, como nuestra aplicación se ejecuta en un servidor de escritorio remoto, y muchos usuarios pueden ejecutar a la vez.

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 por defecto   valor es de 2 MB. Ejemplos:

           -Xms6291456
           -Xms6144k
           -Xms6m
     

-Xmxn       Especificar el tamaño máximo, en bytes, de la memoria reservada.   Este valor debe un múltiplo de 1024   mayores de 2 MB. Agregue la letra k   o K para indicar kilobytes, o m o M   para indicar megabytes. El valor por defecto   valor es de 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

No, no se puede configurar la cantidad de memoria requerida por VM. Sin embargo, tenga en cuenta que esta es la memoria virtual, no residente, por lo que sólo queda allí sin daño si no se utiliza realmente.

Alernatively, puede intentar alguna otra JVM Sun entonces uno, con menor consumo de memoria, pero no puede aconsejar aquí.

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