Comment trouver une fuite de mémoire Java

142

Comment trouvez-vous une fuite de mémoire en Java (en utilisant, par exemple, JHat)? J'ai essayé de charger le vidage de tas dans JHat pour jeter un coup d'œil de base. Cependant, je ne comprends pas comment je suis censé pouvoir trouver la référence racine ( ref ) ou quel que soit son nom. Fondamentalement, je peux dire qu'il existe plusieurs centaines de mégaoctets d'entrées de table de hachage ([java.util.HashMap $ Entry ou quelque chose comme ça), mais les cartes sont utilisées partout ... Y a-t-il un moyen de rechercher de grandes cartes , ou peut-être trouver des racines générales de grands arbres d'objets?

[Edit] Ok, j'ai lu les réponses jusqu'à présent, mais disons simplement que je suis un salaud bon marché (ce qui signifie que je suis plus intéressé à apprendre à utiliser JHat qu'à payer JProfiler). De plus, JHat est toujours disponible car il fait partie du JDK. À moins bien sûr qu'il n'y ait aucun moyen avec JHat mais la force brute, mais je ne peux pas croire que cela puisse être le cas.

De plus, je ne pense pas que je pourrai réellement modifier (en ajoutant la journalisation de toutes les tailles de carte) et l'exécuter assez longtemps pour que je remarque la fuite.

jwiklund
la source
Ceci est un autre "vote" pour JProfiler. Cela fonctionne plutôt bien pour l'analyse de tas, a une interface utilisateur décente et fonctionne plutôt bien. Comme le dit McKenzieG1, 500 $ sont moins chers que le temps que vous brûleriez autrement à la recherche de la source de ces fuites. En ce qui concerne le prix des outils, ce n'est pas mal.
joev le
Oracle a une page pertinente ici: docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/…
Laurel

Réponses:

126

J'utilise l'approche suivante pour trouver des fuites de mémoire en Java. J'ai utilisé jProfiler avec beaucoup de succès, mais je pense que tout outil spécialisé avec des capacités graphiques (les différences sont plus faciles à analyser sous forme graphique) fonctionnera.

  1. Démarrez l'application et attendez qu'elle passe à l'état "stable", lorsque toute l'initialisation est terminée et que l'application est inactive.
  2. Exécutez l'opération soupçonnée de produire une fuite de mémoire à plusieurs reprises pour permettre à toute initialisation liée à la base de données du cache.
  3. Exécutez GC et prenez un instantané de la mémoire.
  4. Exécutez à nouveau l'opération. En fonction de la complexité de l'opération et de la taille des données traitées, l'opération peut devoir être exécutée plusieurs à plusieurs fois.
  5. Exécutez GC et prenez un instantané de la mémoire.
  6. Exécutez un diff pour 2 instantanés et analysez-le.

Fondamentalement, l'analyse doit commencer à partir de la plus grande différence positive par, par exemple, des types d'objets et trouver ce qui fait que ces objets supplémentaires restent en mémoire.

Pour les applications Web qui traitent les requêtes dans plusieurs threads, l'analyse devient plus compliquée, mais l'approche générale s'applique néanmoins.

J'ai réalisé un certain nombre de projets visant spécifiquement à réduire l'empreinte mémoire des applications et cette approche générale avec quelques ajustements et astuces spécifiques à l'application a toujours bien fonctionné.

Dima Malenko
la source
7
La plupart des profileurs Java (sinon tous) vous offrent la possibilité d'appeler GC en un clic. Ou vous pouvez appeler System.gc () à partir de l'endroit approprié dans votre code.
Dima Malenko le
3
Même si nous appelons System.gc (), JVM peut choisir de négliger l'appel. AFAIK ceci est spécifique à JVM. +1 à la réponse.
Aniket Thakur
4
Qu'est-ce qu'un "instantané de la mémoire"? Y a-t-il quelque chose qui me dira le nombre de chaque type d'objet que mon code exécute?
gnomed
2
Comment puis-je passer de «partir du plus grand diff positif par type d'objet» à «trouver ce qui fait que ces objets supplémentaires restent en mémoire»? Je vois des choses très générales comme int [], Object [], String, etc. Comment puis-je trouver d'où elles viennent?
Vituel
48

Interlocuteur ici, je dois dire que l'obtention d'un outil qui ne prend pas 5 minutes pour répondre à n'importe quel clic facilite beaucoup la recherche de fuites de mémoire potentielles.

Étant donné que les gens suggèrent plusieurs outils (j'ai seulement essayé visual wm depuis que je l'ai eu dans l'essai JDK et JProbe), je pense que je devrais suggérer un outil gratuit / open source construit sur la plate-forme Eclipse, l'analyseur de mémoire (parfois appelé mémoire SAP analyseur) disponible sur http://www.eclipse.org/mat/ .

Ce qui est vraiment cool à propos de cet outil, c'est qu'il a indexé le vidage du tas lorsque je l'ai ouvert pour la première fois, ce qui lui a permis d'afficher des données comme le tas conservé sans attendre 5 minutes pour chaque objet (à peu près toutes les opérations étaient beaucoup plus rapides que les autres outils que j'ai essayés) .

Lorsque vous ouvrez la décharge, le premier écran vous montre un diagramme à secteurs avec les plus gros objets (en comptant le tas conservé) et on peut rapidement naviguer vers les objets qui sont trop gros pour le confort. Il a également une recherche de suspects de fuite probable que je reconnais peut être utile, mais comme la navigation était suffisante pour moi, je n'y suis pas vraiment entré.

jwiklund
la source
1
A noter: apparemment dans Java 5 et supérieur, le HeapDumpOnCtrlBreakparamètre VM n'est pas disponible . La solution que j'ai trouvée (jusqu'à présent, toujours à la recherche) est d'utiliser JMap pour vider le .hproffichier, que je place ensuite dans Eclipse et que j'utilise MAT pour l'examiner.
Ben
1
En ce qui concerne l'obtention d'un vidage de tas, la plupart des profileurs (y compris JVisualVM) incluent une option permettant de vider le tas et les threads dans un fichier.
bbaja42
13

Un outil est d'une grande aide.

Cependant, il y a des moments où vous ne pouvez pas utiliser un outil: le vidage du tas est si énorme qu'il plante l'outil, vous essayez de dépanner une machine dans un environnement de production auquel vous n'avez qu'un accès shell, etc.

Dans ce cas, il est utile de connaître votre chemin dans le fichier de vidage hprof.

Recherchez SITES BEGIN. Cela vous montre quels objets utilisent le plus de mémoire. Mais les objets ne sont pas regroupés uniquement par type: chaque entrée comprend également un ID "trace". Vous pouvez ensuite rechercher ce "TRACE nnnn" pour voir les quelques cadres supérieurs de la pile où l'objet a été alloué. Souvent, une fois que je vois où l'objet est alloué, je trouve un bug et j'ai terminé. Notez également que vous pouvez contrôler le nombre d'images enregistrées dans la pile avec les options -Xrunhprof.

Si vous consultez le site d'allocation et que vous ne voyez rien de mal, vous devez commencer le chaînage arrière de certains de ces objets en direct vers des objets racine, pour trouver la chaîne de référence inattendue. C'est là qu'un outil aide vraiment, mais vous pouvez faire la même chose à la main (enfin, avec grep). Il n'y a pas qu'un seul objet racine (c'est-à-dire un objet non soumis au garbage collection). Les threads, classes et cadres de pile agissent comme des objets racine, et tout ce qu'ils référencent fortement n'est pas collectable.

Pour effectuer le chaînage, recherchez dans la section HEAP DUMP les entrées avec l'ID de trace incorrect. Cela vous mènera à une entrée OBJ ou ARR, qui affiche un identifiant d'objet unique en hexadécimal. Recherchez toutes les occurrences de cet identifiant pour trouver qui a une référence forte à l'objet. Suivez chacun de ces chemins vers l'arrière pendant qu'ils se branchent jusqu'à ce que vous trouviez où se trouve la fuite. Voyez pourquoi un outil est si pratique?

Les membres statiques sont des récidivistes pour les fuites de mémoire. En fait, même sans outil, cela vaudrait la peine de passer quelques minutes à rechercher dans votre code les membres statiques de la carte. Une carte peut-elle s'agrandir? Est-ce que quelque chose nettoie jamais ses entrées?

Erickson
la source
«Le vidage du tas est tellement énorme qu'il plante l'outil» - j'ai vérifié la dernière fois, jhatet j'ai MATapparemment essayé de charger le vidage du tas entier en mémoire, et donc typiquement planter avec un vidage de OutOfMemoryErrorgrande taille (c'est-à-dire des applications qui avaient le plus besoin d'une analyse de tas! ). Le NetBeans Profiler semble utiliser un algorithme différent pour l'indexation des références, ce qui peut devenir lent sur les gros vidages mais au moins ne consomme pas de mémoire illimitée dans l'outil et plante.
Jesse Glick
10

La plupart du temps, dans les applications d'entreprise, le tas Java donné est plus grand que la taille idéale de 12 à 16 Go maximum. J'ai eu du mal à faire fonctionner le profileur NetBeans directement sur ces grandes applications java.

Mais ce n'est généralement pas nécessaire. Vous pouvez utiliser l'utilitaire jmap fourni avec le jdk pour effectuer un vidage de tas "en direct", c'est-à-dire que jmap videra le tas après avoir exécuté GC. Effectuez une opération sur l'application, attendez que l'opération soit terminée, puis effectuez un autre vidage de tas "en direct". Utilisez des outils comme Eclipse MAT pour charger les tas de tas, trier sur l'histogramme, voir quels objets ont augmenté, ou lesquels sont les plus élevés, cela donnerait un indice.

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

Il n'y a qu'un seul problème avec cette approche; D'énormes vidages de tas, même avec l'option live, peuvent être trop volumineux pour être transférés vers le tour de développement, et peuvent nécessiter une machine avec suffisamment de mémoire / RAM pour s'ouvrir.

C'est là que l'histogramme de classe entre en scène. Vous pouvez vider un histogramme de classe en direct avec l'outil jmap. Cela ne donnera que l'histogramme de classe de l'utilisation de la mémoire. Fondamentalement, il n'aura pas les informations pour enchaîner la référence. Par exemple, il peut mettre un tableau de caractères en haut. Et la classe String quelque part en dessous. Vous devez établir le lien vous-même.

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

Au lieu de prendre deux vidages de tas, prenez deux histogrammes de classe, comme décrit ci-dessus; Ensuite, comparez les histogrammes de classe et voyez les classes qui augmentent. Voyez si vous pouvez associer les classes Java à vos classes d'application. Cela donnera un assez bon indice. Voici un script pythons qui peut vous aider à comparer deux vidages d'histogramme jmap. histogramparser.py

Enfin, des outils comme JConolse et VisualVm sont essentiels pour voir la croissance de la mémoire au fil du temps, et voir s'il y a une fuite de mémoire. Enfin, parfois, votre problème peut ne pas être une fuite de mémoire, mais une utilisation élevée de la mémoire. Pour cela, activez la journalisation GC, utilisez un GC de compactage plus avancé et nouveau comme G1GC; et vous pouvez utiliser des outils jdk comme jstat pour voir le comportement du GC en direct

jstat -gccause pid <optional time interval>

Autres références à google pour -jhat, jmap, Full GC, Humongous allocation, G1GC

Alex Punnen
la source
1
a ajouté un article de blog avec plus de détails ici - alexpunnen.blogspot.in/2015/06/…
Alex Punnen
5

Il existe des outils qui devraient vous aider à trouver votre fuite, comme JProbe, YourKit, AD4J ou JRockit Mission Control. Le dernier est celui que je connais le mieux personnellement. Tout bon outil devrait vous permettre de descendre à un niveau où vous pouvez facilement identifier les fuites et où les objets qui fuient sont alloués.

L'utilisation de HashTables, Hashmaps ou similaires est l'un des rares moyens de perdre réellement de la mémoire en Java. Si je devais trouver la fuite à la main, j'imprimerais péridiquement la taille de mes HashMaps, et à partir de là, je trouverais celui où j'ajoutais des éléments et j'oublierais de les supprimer.

Tnilsson
la source
4

Eh bien, il y a toujours la solution de faible technologie d'ajouter une journalisation de la taille de vos cartes lorsque vous les modifiez, puis recherchez les journaux pour lesquels les cartes augmentent au-delà d'une taille raisonnable.

Mike Stone
la source
1

NetBeans a un profileur intégré.

wbkang
la source
0

Vous devez vraiment utiliser un profileur de mémoire qui suit les allocations. Jetez un oeil à JProfiler - leur fonctionnalité "heap walker" est excellente, et ils ont une intégration avec tous les principaux IDE Java. Ce n'est pas gratuit, mais ce n'est pas si cher non plus (499 $ pour une seule licence) - vous brûlerez 500 $ de temps assez rapidement en luttant pour trouver une fuite avec des outils moins sophistiqués.

McKenzieG1
la source
0

Vous pouvez le savoir en mesurant la taille d'utilisation de la mémoire après avoir appelé plusieurs fois garbage collector:

Runtime runtime = Runtime.getRuntime();

while(true) {
    ...
    if(System.currentTimeMillis() % 4000 == 0){
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    }

}

Si les nombres de sortie étaient égaux, il n'y a pas de fuite de mémoire dans votre application, mais si vous constatez une différence entre les nombres d'utilisation de la mémoire (nombres croissants), il y a une fuite de mémoire dans votre projet. Par exemple:

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

Notez que parfois, il faut un certain temps pour libérer de la mémoire par certaines actions comme les flux et les sockets. Vous ne devez pas juger par les premières sorties, vous devez le tester dans un laps de temps spécifique.

Amir Fo
la source
0

Consultez cet écran sur la recherche de fuites de mémoire avec JProfiler. C'est une explication visuelle de @Dima Malenko Answer.

Remarque: Bien que JProfiler ne soit pas un logiciel gratuit, la version d'essai peut gérer la situation actuelle.

Vaibhav Jain
la source
0

Comme la plupart d'entre nous utilisent déjà Eclipse pour écrire du code, pourquoi ne pas utiliser l'outil d'analyse de la mémoire (MAT) dans Eclipse. Cela fonctionne très bien.

L' Eclipse MAT est un ensemble de plug-ins pour Eclipse IDE qui offre des outils d'analyse heap dumpsde l' application Java et d'identifier memory problemsdans l'application.

Cela aide le développeur à trouver des fuites de mémoire avec les fonctionnalités suivantes

  1. Acquisition d'un instantané de la mémoire (Heap Dump)
  2. Histogramme
  3. Tas conservé
  4. Arbre dominateur
  5. Explorer les chemins vers les racines du GC
  6. Inspecteur
  7. Anti-motifs de mémoire communs
  8. Langage de requête d'objet

entrez la description de l'image ici

Sreeram Nair
la source