solsin1985
Guión:
Tengo una JVM ejecutándose en un contenedor docker. Hice un análisis de memoria usando dos herramientas: 1) arriba 2) Seguimiento de memoria nativa de Java. Los números parecen confusos y estoy tratando de encontrar la causa de las diferencias.
Pregunta:
El RSS se informa como 1272 MB para el proceso de Java y la memoria total de Java se informa como 790,55 MB. ¿Cómo puedo explicar dónde se fue el resto de la memoria 1272 – 790,55 = 481,44 MB?
Por qué quiero mantener este problema abierto incluso después de ver esta pregunta en SO:
Vi la respuesta y la explicación tiene sentido. Sin embargo, después de obtener el resultado de Java NMT y pmap -x, Todavía no puedo mapear concretamente qué direcciones de memoria java son realmente residentes y mapeadas físicamente. Necesito una explicación concreta (con pasos detallados) para encontrar la causa de esta diferencia entre RSS y la memoria asignada total de Java.
Salida superior
TNM de Java
Estadísticas de memoria de Docker
gráficos
Tengo un contenedor docker funcionando durante más de 48 horas. Ahora, cuando veo un gráfico que contiene:
- Memoria total otorgada al contenedor docker = 2 GB
- Montón máximo de Java = 1 GB
- Total comprometido (JVM) = siempre menos de 800 MB
- Montón usado (JVM) = siempre menos de 200 MB
- Non Heap Used (JVM) = siempre menos de 100 MB.
- RSS = alrededor de 1,1 GB.
Entonces, ¿qué está consumiendo la memoria entre 1,1 GB (RSS) y 800 MB (memoria comprometida total de Java)?
VonC
Tienes alguna pista en “
Análisis del uso de la memoria Java en un contenedor Docker” de Mijaíl Krestjaninoff:
(Y para ser claros, en mayo de 2019, tres años después, el la situación mejora con openJDK 8u212)
Rresidente Set Size es la cantidad de memoria física actualmente asignada y utilizada por un proceso (sin intercambiar páginas). Incluye el código, los datos y las bibliotecas compartidas (que se contabilizan en cada proceso que las utiliza)
¿Por qué la información de las estadísticas de la ventana acoplable difiere de los datos de ps?
La respuesta a la primera pregunta es muy simple: Docker tiene un error (o una característica, depende de su estado de ánimo): incluye cachés de archivos en la información de uso de memoria total. Entonces, podemos simplemente evitar esta métrica y usar
ps
información sobre RSS.Bueno, está bien, pero ¿Por qué RSS es más alto que Xmx?
Teóricamente, en el caso de una aplicación Java
RSS = Heap size + MetaSpace + OffHeap size
donde OffHeap consiste en pilas de subprocesos, búferes directos, archivos asignados (bibliotecas y jars) y código JVM en sí mismo
Desde JDK 1.8.40 tenemos Rastreador de memoria nativa!
Como puedes ver, ya he añadido
-XX:NativeMemoryTracking=summary
propiedad a la JVM, por lo que podemos invocarla desde la línea de comando:
docker exec my-app jcmd 1 VM.native_memory summary
(Esto es lo que hizo el OP)
No se preocupe por la sección “Desconocido”: parece que NMT es una herramienta inmadura y no puede manejar CMS GC (esta sección desaparece cuando usa otro GC).
Tenga en cuenta, que NMT muestra la memoria “comprometida”, no “residente” (que se obtiene a través del comando ps). En otras palabras, una página de memoria se puede comprometer sin considerarla como residente (hasta que se accede directamente).
Eso significa que los resultados de NMT para áreas que no son de almacenamiento dinámico (el almacenamiento dinámico siempre se preinicializa) pueden ser mayores que los valores de RSS.
(ahí es donde entra “¿Por qué una JVM informa más memoria comprometida que el tamaño del conjunto residente del proceso de Linux?”)
Como resultado, a pesar de que establecimos el límite de almacenamiento dinámico de jvm en 256 m, nuestra aplicación consume 367 M. Los “otros” 164M se utilizan principalmente para almacenar metadatos de clase, código compilado, subprocesos y datos de GC.
Los primeros tres puntos suelen ser constantes para una aplicación, por lo que lo único que aumenta con el tamaño del almacenamiento dinámico son los datos del GC.
Esta dependencia es lineal, pero el “k
” coeficiente (y = kx + b
) es mucho menor que 1.
Más generalmente, esto parece ser seguido por número 15020 que informa un problema similar desde docker 1.7
Estoy ejecutando una aplicación Scala (JVM) simple que carga una gran cantidad de datos dentro y fuera de la memoria.
Configuré la JVM en un montón de 8G (-Xmx8G
). Tengo una máquina con memoria de 132 G y no puede manejar más de 7 u 8 contenedores porque crecen mucho más allá del límite de 8 G que impuse en la JVM.
(docker stat
era reportado como engañoso antesya que aparentemente incluye cachés de archivos en la información de uso total de la memoria)
docker stat
muestra que cada contenedor en sí usa mucha más memoria de la que se supone que usa la JVM. Por ejemplo:
CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O
dave-1 3.55% 10.61 GB/135.3 GB 7.85% 7.132 MB/959.9 MB
perf-1 3.63% 16.51 GB/135.3 GB 12.21% 30.71 MB/5.115 GB
Casi parece que la JVM está solicitando memoria al sistema operativo, que se asigna dentro del contenedor, y la JVM está liberando memoria a medida que se ejecuta su GC, pero el contenedor no libera la memoria al sistema operativo principal. Así que… pérdida de memoria.
-
¡gracias por su respuesta! Acabo de adjuntar un gráfico a la pregunta original. La cuestión es que, según el resumen que ha proporcionado, si JVM estaba pidiendo memoria al sistema operativo, entonces cuando el montón utilizado crece, entonces RSS también debería haberse disparado. Y, cuando hubiera ocurrido la GC, el almacenamiento dinámico utilizado se habría caído, pero el RSS no. Pero, cuando veo los gráficos, veo que desde el primer momento, hay una brecha considerable entre la memoria comprometida total de RSS y Java. ¿Cómo puedo explicar esta brecha?
– sunsin1985
4 de agosto de 2016 a las 18:09
-
@sunsin1985 “¿Qué está consumiendo la memoria entre 1,1 GB (RSS) y 800 MB (memoria comprometida total de Java)?” Sospecho que esos son los “metadatos de clase de almacenamiento, código compilado, subprocesos y datos de GC” a los que se hace referencia en el artículo, además, es posible que GC no libere nada si el contenedor sigue sin liberarlo.
– VoC
4 de agosto de 2016 a las 18:13
-
gracias, pero ¿no será eso parte del número “total comprometido” obtenido de Java NMT?
– sunsin1985
04/08/2016 a las 18:42
-
@ sunsin1985 otra buena lectura: devcenter.heroku.com/articles/java-memory-issuesdonde la opción java completa se convierte en
JAVA_OPTS="-XX:NativeMemoryTracking=detail -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics"
– VoC
4 de agosto de 2016 a las 19:08
-
Creo que la respuesta aquí no es a la pregunta formulada. La pregunta era por qué java proceso RSS> JVM heap+non_heap. La respuesta es a la pregunta opuesta: ¿por qué JVM heap+non_heap podría ser > java proceso RSS? @VonC ¿Me estoy perdiendo algo?
– Artem Nakonechni
7 junio 2019 a las 19:06
Descargo de responsabilidad: no soy un experto
Recientemente tuve un incidente de producción cuando, bajo una carga pesada, los pods tuvieron un gran salto en RSS y Kubernetes destruyó los pods. No hubo una excepción de error OOM, pero Linux detuvo el proceso de la manera más dura.
Había una gran brecha entre RSS y el espacio total reservado por JVM. Memoria de pila, memoria nativa, subprocesos, todo parecía estar bien, sin embargo, RSS era grande.
Se descubrió que se debe al hecho de cómo funciona malloc internamente. Hay grandes lagunas en la memoria de donde malloc toma fragmentos de memoria. Si hay muchos núcleos en su máquina, malloc intenta adaptarse y darle a cada núcleo su propio espacio para liberar memoria y evitar la contención de recursos. configurando export MALLOC_ARENA_MAX=2
resolvió el problema. Puede encontrar más información sobre esta situación aquí:
- Creciente uso de memoria residente (RSS) de Java Process
- https://devcenter.heroku.com/articles/tuning-glibc-memory-behavior
- https://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html
- https://github.com/jeffgriffith/native-jvm-leaks
PD: no sé por qué hubo un salto en la memoria RSS. Los pods se basan en Spring Boot + Kafka.
Posible duplicado de ¿Por qué una JVM informa más memoria comprometida que el tamaño del conjunto residente del proceso de Linux? … bueno, a la inversa.
– the8472
27 de julio de 2016 a las 0:26
Tengo el mismo problema y encuentro las respuestas 🙁 ¿Qué tipo de aplicación tienes?
– Miguel
29 de julio de 2016 a las 11:10
¿Encontró satisfactoria la respuesta a su pregunta anterior (stackoverflow.com/a/38630406/6309)?
– VoC
29 de julio de 2016 a las 12:33
@ sunsin1985 puede intentar cambiar la implementación de malloc a jemalloc, he visto que RSS disminuyó significativamente después de eso. Ver gdstechnology.blog.gov.uk/2015/12/11/… Tengo una aplicación que se ejecuta con 600 MB de almacenamiento dinámico y 1,3 GB de RSS; aún no investigué eso de cerca, pero simplemente falta la memoria; no hay una memoria nativa significativa asignada. Sospecho que esto se debe a la fragmentación de la memoria (y por eso ayuda jemalloc)
– mamón
04/08/2016 a las 23:57
Si está rastreando fugas de memoria nativa de Java o desea minimizar el uso de RSS, hay esta pregunta con respuestas: stackoverflow.com/questions/26041117/…
– Lari Hotari
7 sep 2016 a las 9:40