Les erreurs liées à un manque de mémoire sont une des hantise du développeur Java. De deux choses l’une, soit une augmentation du volume de mémoire allouée résout le problème définitivement, soit cela ne fait que retarder l’échéance et c’est là que les choses se corsent!
Le bon vieux débogueur ne peut pas nous aider pour ce genre de diganostic, il ne produit pas d’informations utiles concernant l’utilisation réelle de la mémoire. Il est bien sûr possible de générer un dump mémoire (avec un ‘kill -3’ sous Linux), néanmoins le résultat peut être assez lourd à analyser. Confronté récemment à un tel problème, je me suis rendu compte que Sun fournit avec son JDK un ensemble d’outils permettant de résoudre bon nombre de problème. Ces outils sont disponible depuis la version 1.5 du JDK, ils ne sont pas suportés par Sun et ne fonctionnent que sur certaines plate-formes : Linux, Solaris, Mac OS, mais pas Windows… Voici donc une présentation succincte des possibilités offertes pour diagnostiquer un problème de fuite mémoire.
Tous d’abord la commande ‘jps‘ (JVM Process Status Tool), permet le lister les différentes JVM s’exécutant sur une machine. On peut récupérer la classe ou le JAR principal, les paramètres de la méthode main ainsi que les paramètres de JVM. C’est à peu près l’équivalent d’un ‘ps aux | grep java‘ avec deux différences :
- le formatage des résultats est plus clair
- certains processus Java (par exemple Eclipse) n’apparaissent pas comme tels avec ‘ps‘ mais sont visibles avec ‘jps‘
Ce premier outil permet donc d’identifier le processus qui nous intéresse et de vérifier que les paramètres de mémoire (Xmx, Xms et autres XX:MaxPermSize) sont bien pris en compte. On ne sait jamais… Au même niveau d’information, on peut également utiliser la commande ‘jinfo
Si ce n’est pas le script de lancement qui est en cause, il peut être judicieux de mesurer l’utilisation mémoire réelle du processus au fur et à mesure de son exécution. C’est une mesure très simple qui n’est pas possible avec les outils systèmes de base. Pour cela, on pourra utiliser la commande ‘jmap‘ (JVM Memory Map) avec l’option ‘-heap‘. Cette commande permet de visualiser pour une JVM, l’ensemble de la configuration mémoire ainsi que l’espace réellement alloué et utilisé pour les différents espaces de mémoire (Eden, From, To, Old Generatoin, Perm Generation).
Si la fuite mémoire est avérée, on peut encore aller plus loin avec la commande ‘jmap‘. L’option ‘-histo‘ permet d’obtenir pour chaque classe, le nombre d’objets instanciés et le volume de mémoire utilisée. Ces informations peuvent fournir quelques bonnes pistes pour identifier la fuite mémoire. Enfin pour les problèmes liés aux ‘Perm Generation Space’ (un article a été consacré récemment à ce sujet sur ce même blog), l’option ‘-permstat‘ permet d’obtenir pour chaque ClassLoader de la JVM, le nombre d’objets instanciés et la taille totale utilisée. Grâce à cela, on peut par exemple constater qu’un ClassLoader qui aurait dû être collecté par le GC (par exemple lors du redéploiement d’un WAR) est encore vivant et sature la mémoire.
Au delà, pour identifier précisément le code fautif, une analyse du code ou d’un dump mémoire restera probablement un passage obligé. Si on en arrive là, ces outils nous auront quand même permis de circonscrire rapidement la zone de recherche.