J'ai rencontré le même problème. C'est un problème connu avec la glibc>=2.10
Le remède consiste à définir cette variable d'environnement
export MALLOC_ARENA_MAX=4
Article IBM sur la configuration 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
Recherchez MALLOC_ARENA_MAX sur Google ou recherchez-le sur SO pour trouver de nombreuses références.
Vous voudrez peut-être également régler d'autres options de malloc pour optimiser une faible fragmentation de la mémoire allouée :
# 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
Il est également possible qu'il y ait une fuite de mémoire native. Un problème courant est les fuites de mémoire native causées par la non fermeture d'un ZipInputStream
/GZIPInputStream
.
Une façon typique qu'un ZipInputStream
est ouvert par un appel à Class.getResource
/ClassLoader.getResource
et appeler openConnection().getInputStream()
sur le java.net.URL
instance ou en appelant Class.getResourceAsStream
/ClassLoader.getResourceAsStream
. Il faut s'assurer que ces flux sont toujours fermés.
Certaines bibliothèques open source couramment utilisées ont eu des bogues qui fuient java.util.zip.Inflater
non fermés ou java.util.zip.Deflater
instances. Par exemple, la bibliothèque Nimbus Jose JWT a corrigé une fuite de mémoire associée dans la version 6.5.1. Java JWT (jjwt) avait un bogue similaire qui a été corrigé dans la version 0.10.7. Le modèle de bogue dans ces 2 cas était le fait que les appels à DeflaterOutputStream.close()
et InflaterInputStream.close()
ne pas appeler le Deflater.end()
/Inflater.end()
quand un Deflater
/Inflater
instance est fournie. Dans ces cas, il ne suffit pas de vérifier le code pour les flux en cours de fermeture. Tous les Deflater
/Inflater
les instances créées dans le code doivent gérer ce .end()
est appelé.
Une façon de vérifier les fuites de Zip*Stream est d'obtenir un vidage de tas et de rechercher des instances de n'importe quelle classe avec « zip », « Inflater » ou « Deflater » dans le nom. Ceci est possible dans de nombreux outils d'analyse de vidage de tas tels que Yourkit Java Profiler, JProfiler ou Eclipse MAT. Cela vaut également la peine de vérifier les objets en état de finalisation car, dans certains cas, la mémoire n'est libérée qu'après la finalisation. Il est utile de vérifier les classes susceptibles d'utiliser des bibliothèques natives. Cela s'applique également aux bibliothèques TLS/ssl.
Il existe un outil OSS appelé leakchecker d'Elastic qui est un agent Java qui peut être utilisé pour trouver les sources de java.util.zip.Inflater
instances qui n'ont pas été fermées (.end()
pas appelé).
Pour les fuites de mémoire native en général (pas seulement pour les fuites de bibliothèque zip), vous pouvez utiliser jemalloc pour déboguer les fuites de mémoire native en activant le profilage d'échantillonnage malloc en spécifiant les paramètres dans MALLOC_CONF
variables d'environnement. Des instructions détaillées sont disponibles dans cet article de blog :http://www.evanjones.ca/java-native-leak-bug.html . Ce billet de blog contient également des informations sur l'utilisation de jemalloc pour déboguer une fuite de mémoire native dans les applications Java. Il existe également un article de blog d'Elastic mettant en vedette jemalloc et mentionnant leakchecker, l'outil qu'Elastic a mis en open source pour détecter les problèmes causés par les ressources de gonflage zip non fermées.
Il existe également un article de blog sur une fuite de mémoire native liée à ByteBuffers. Java 8u102 a une propriété système spéciale jdk.nio.maxCachedBufferSize
pour limiter le problème de cache décrit dans ce billet de blog.
-Djdk.nio.maxCachedBufferSize=262144
Il est également bon de toujours vérifier les descripteurs de fichiers ouverts pour voir si la fuite de mémoire est causée par une grande quantité de fichiers mmap:ed. Sous Linux lsof
peut être utilisé pour lister les fichiers ouverts et les sockets ouverts :
lsof -Pan -p PID
Le rapport de la carte mémoire du processus pourrait également aider à enquêter sur les fuites de mémoire native
pmap -x PID
Pour les processus Java exécutés dans Docker, il devrait être possible d'exécuter la commande lsof ou pmap sur "l'hôte". Vous pouvez trouver le PID du processus conteneurisé avec cette commande
docker inspect --format '{{.State.Pid}}' container_id
Il est également utile d'obtenir un vidage de thread (ou d'utiliser jconsole/JMX) pour vérifier le nombre de threads puisque chaque thread consomme 1 Mo de mémoire native pour sa pile. Un grand nombre de threads utiliserait beaucoup de mémoire.
Il existe également un suivi de la mémoire native (NMT) dans la JVM. Cela peut être utile pour vérifier si c'est la JVM elle-même qui utilise la mémoire native.
L'outil jattach peut également être utilisé dans un environnement conteneurisé (docker) pour déclencher des threaddumps ou des heapdumps à partir de l'hôte. Il est également capable d'exécuter des commandes jcmd nécessaires au contrôle de NMT.