Tuve el mismo problema. Este es un problema conocido con glibc>=2.10
La cura es establecer esta variable env
export MALLOC_ARENA_MAX=4
Artículo de IBM sobre la configuración de MALLOC_ARENA_MAXhttps://www.ibm.com/developerworks/community/blogs/kevgrig/entry/linux_glibc_2_10_rhel_6_malloc_may_show_excessive_virtual_memory_usage?lang=en
Busque MALLOC_ARENA_MAX en Google o búsquelo en SO para encontrar muchas referencias.
Es posible que desee ajustar también otras opciones de malloc para optimizar la fragmentación de la 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
También es posible que haya una pérdida de memoria nativa. Un problema común son las fugas de memoria nativa causadas por no cerrar un ZipInputStream
/GZIPInputStream
.
Una forma típica en que un ZipInputStream
se abre mediante una llamada a Class.getResource
/ClassLoader.getResource
y llamando openConnection().getInputStream()
en el java.net.URL
instancia o llamando a Class.getResourceAsStream
/ClassLoader.getResourceAsStream
. Uno debe asegurarse de que estas corrientes siempre se cierren.
Algunas bibliotecas de código abierto de uso común han tenido errores que filtran java.util.zip.Inflater
sin cerrar o java.util.zip.Deflater
instancias. Por ejemplo, la biblioteca Nimbus Jose JWT solucionó una pérdida de memoria relacionada en la versión 6.5.1. Java JWT (jjwt) tenía un error similar que se solucionó en la versión 0.10.7. El patrón de error en estos 2 casos fue el hecho de que las llamadas a DeflaterOutputStream.close()
y InflaterInputStream.close()
no llamar Deflater.end()
/Inflater.end()
cuando un Deflater
/Inflater
se proporciona una instancia. En esos casos, no es suficiente verificar el código de las transmisiones que se están cerrando. Cada Deflater
/Inflater
las instancias creadas en el código deben tener un manejo que .end()
recibe una llamada.
Una forma de verificar las fugas de Zip*Stream es obtener un volcado de pila y buscar instancias de cualquier clase con "zip", "Inflater" o "Deflater" en el nombre. Esto es posible en muchas herramientas de análisis de volcado de pila, como Yourkit Java Profiler, JProfiler o Eclipse MAT. También vale la pena verificar los objetos en estado de finalización ya que, en algunos casos, la memoria se libera solo después de la finalización. Es útil buscar clases que puedan usar bibliotecas nativas. Esto también se aplica a las bibliotecas TLS/ssl.
Hay una herramienta OSS llamada leakchecker de Elastic que es un agente Java que se puede usar para encontrar las fuentes de java.util.zip.Inflater
instancias que no han sido cerradas (.end()
no llamado).
Para fugas de memoria nativa en general (no solo para fugas de bibliotecas zip), puede usar jemalloc para depurar fugas de memoria nativa habilitando el perfilado de muestreo de malloc especificando la configuración en MALLOC_CONF
Variable ambiental. Las instrucciones detalladas están disponibles en esta entrada de blog:http://www.evanjones.ca/java-native-leak-bug.html. Esta publicación de blog también tiene información sobre el uso de jemalloc para depurar una fuga de memoria nativa en aplicaciones Java. También hay una publicación de blog de Elastic que presenta a jemalloc y menciona el verificador de fugas, la herramienta que Elastic ha abierto para rastrear los problemas causados por los recursos de inflador de zip sin cerrar.
También hay una publicación de blog sobre una pérdida de memoria nativa relacionada con ByteBuffers. Java 8u102 tiene una propiedad de sistema especial jdk.nio.maxCachedBufferSize
para limitar el problema de caché descrito en esa publicación de blog.
-Djdk.nio.maxCachedBufferSize=262144
También es bueno verificar siempre los identificadores de archivos abiertos para ver si la pérdida de memoria es causada por una gran cantidad de archivos mmap:ed. En Linux lsof
se puede usar para listar archivos abiertos y sockets abiertos:
lsof -Pan -p PID
El informe del mapa de memoria del proceso también podría ayudar a investigar las fugas de memoria nativa
pmap -x PID
Para los procesos de Java que se ejecutan en Docker, debería ser posible ejecutar el comando lsof o pmap en el "host". Puede encontrar el PID del proceso en contenedor con este comando
docker inspect --format '{{.State.Pid}}' container_id
También es útil obtener un volcado de subprocesos (o usar jconsole/JMX) para verificar la cantidad de subprocesos, ya que cada subproceso consume 1 MB de memoria nativa para su pila. Una gran cantidad de subprocesos usaría mucha memoria.
También hay seguimiento de memoria nativa (NMT) en la JVM. Eso podría ser útil para verificar si es la propia JVM la que está utilizando la memoria nativa.
La herramienta jattach también se puede usar en un entorno contenedorizado (docker) para desencadenar volcados de subprocesos o volcados de pila desde el host. También puede ejecutar comandos jcmd que se necesitan para controlar NMT.