Question

L'un de nos programmes génère parfois une erreur OutOfMemory sur l'ordinateur d'un utilisateur, mais bien sûr, pas lorsque je le teste. Je viens de l'exécuter avec JProfiler (avec une licence d'évaluation de 10 jours parce que je ne l'avais jamais utilisée auparavant) et en filtrant sur notre préfixe de code, le plus gros bloc en taille totale et en nombre d'instances étant supérieur à 8 000 instances d'une classe simple particulière .

J'ai cliqué sur le bouton "Ramasse-miettes". bouton sur JProfiler, et la plupart des instances d’autres classes de la nôtre sont parties, mais pas celles-là. J’ai refait le test, toujours dans le même cas, et il a créé plus de 4 000 occurrences de la classe, mais lorsque j’ai cliqué sur "Ramasse-miettes", celles-ci sont parties, laissant les plus de 8 000 occurrences originales.

Ces instances sont bloquées dans diverses collections à différentes étapes. Je suppose que le fait qu’elles ne soient pas mises au rebut doit signifier que quelque chose retient une référence à l’une des collections, donc une référence aux objets.

Avez-vous des suggestions pour savoir ce qui tient la référence? Je cherche des suggestions sur ce qu'il faut rechercher dans le code, ainsi que sur les moyens de le savoir dans JProfiler, le cas échéant.

Était-ce utile?

La solution

Videz le tas et inspectez-le.

Je suis sûr qu'il y a plus d'une façon de faire cela, mais voici une solution simple. Cette description concerne MS Windows, mais des étapes similaires peuvent être suivies sur d’autres systèmes d’exploitation.

  1. Installez le JDK si vous ne l'avez pas déjà. Il est livré avec une panoplie d'outils utiles.
  2. Démarrer l'application.
  3. Ouvrez le gestionnaire de tâches et recherchez l'ID de processus (PID) de java.exe (ou le fichier exécutable que vous utilisez). Si les PID ne sont pas affichés par défaut, utilisez Afficher > Sélectionnez les colonnes ... pour les ajouter.
  4. Videz le tas à l'aide de jmap .
  5. Démarrez le serveur jhat sur le fichier que vous avez généré et ouvrez votre navigateur à http: // localhost: 7000 (le port par défaut est 7000). Vous pouvez maintenant parcourir le type qui vous intéresse et des informations telles que le nombre d’instances, leur référence, etc.
  6. .

Voici un exemple:

C:\dump>jmap -dump:format=b,file=heap.bin 3552

C:\dump>jhat heap.bin
Reading from heap.bin...
Dump file created Tue Sep 30 19:46:23 BST 2008
Snapshot read, resolving...
Resolving 35484 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

Pour interpréter cela, il est utile de comprendre certaines des nomenclature de type de tableau utilise Java, comme savoir que class [Ljava.lang.Object; désigne en réalité un objet de type Object [] .

Autres conseils

Essayez Eclipse Memory Analyzer. Il vous montrera pour chaque objet comment il est connecté à une racine du CPG - un objet qui n'est pas mis au rebut car il est détenu par la machine virtuelle.

Voir http://dev.eclipse.org/blogs/memoryanalyzer/2008/05/27/automated-heap-dump-analysis-finding-memory-leaks-with-one-click/ pour plus d'informations sur le fonctionnement d'Eclipse MAT.

Je voudrais examiner les collections (en particulier les collections statiques) dans vos classes (les HashMaps sont un bon point de départ). Prenez ce code par exemple:

Map<String, Object> map = new HashMap<String, Object>(); // 1 Object
String name = "test";             // 2 Objects
Object o = new Object();          // 3 Objects
map.put(name, o);                 // 3 Objects, 2 of which have 2 references to them

o = null;                         // The objects are still being
name = null;                      // referenced by the HashMap and won't be GC'd

System.gc();                      // Nothing is deleted.

Object test = map.get("test");    // Returns o
test = null;

map.remove("test");               // Now we're down to just the HashMap in memory
                                  // o, name and test can all be GC'd

Tant que HashMap ou une autre collection contient une référence à cet objet, il ne sera pas récupéré.

Pas de solution miracle, vous devez utiliser le profileur pour identifier les collections contenant ces objets inutiles et trouver la place dans le code où ils auraient dû être supprimés. Comme l'a dit JesperE, les collections statiques sont le premier endroit à regarder.

Les objets avec finaliseurs constituent un candidat évident. Ils peuvent s'attarder pendant que leur méthode de finalisation est appelée. Ils doivent être collectés, puis finalisés (généralement avec un seul thread de finaliseur), puis collectés à nouveau.

Sachez également que vous pouvez obtenir un serveur OOME car le gc n'a pas réussi à collecter suffisamment de mémoire, même s'il est suffisant pour que la demande d'objet soit créée. Sinon, les performances pourraient être détruites.

Gardez un œil sur les conteneurs statiques. Tous les objets d'un conteneur statique resteront tant que la classe est chargée.

Edition: suppression de la remarque incorrecte sur WeakReference.

Je viens de lire un article à ce sujet, mais je suis désolé de ne pouvoir me rappeler où. Je pense que cela aurait pu être dans le livre "Effective Java". Si je trouve la référence, je mettrai à jour ma réponse.

Les deux leçons importantes décrites sont les suivantes:

1) Les méthodes finales indiquent au gc ce qu'il faut faire quand il cue l'objet, mais il ne lui demande pas de le faire et il n'y a pas non plus moyen de l'exiger.

2) L'équivalent moderne de la "fuite de mémoire" dans les environnements de mémoire non gérés, ce sont les références oubliées. Si vous ne définissez pas toutes les références à un objet sur null lorsque vous avez terminé, l'objet ne sera jamais . Ceci est particulièrement important lorsque vous implémentez votre propre type de collection ou votre propre wrapper qui gère une collection. Si vous avez un pool, une pile ou une file d'attente et que vous ne définissez pas le compartiment sur null lorsque vous supprimez " un objet de la collection, le compartiment dans lequel se trouvait cet objet le maintiendra en vie jusqu’à ce que ce compartiment soit configuré pour faire référence à un autre objet.

disclaimer: je sais que d'autres réponses ont mentionné cela, mais j'essaie d'offrir plus de détails.

J'ai utilisé le profileur Java Yourkit ( http://www.yourkit.com ) pour améliorer les performances. optimisations sur java 1.5. Il contient une section sur la façon de travailler sur les fuites de mémoire. Je trouve cela utile.

http://www.yourkit.com/docs /75/help/performance_problems/memory_leaks/index.jsp

Vous pouvez obtenir une évaluation de 15 jours: http: //www.yourkit .com / download / yjp-7.5.7.exe

BR,
~ A

Les collections ont déjà été mentionnées. Si vous utilisez plusieurs ClassLoaders, il est également difficile de trouver l'emplacement, car l'ancien chargeur de classes risque de ne pas pouvoir être nettoyé jusqu'à ce que toutes les références aient disparu.

Vérifiez également les statistiques - elles sont mauvaises. Les infrastructures de journalisation peuvent garder des éléments ouverts, ainsi que des références dans des ajouts personnalisés.

Avez-vous résolu le problème?

Quelques suggestions:

  • Cartes illimitées utilisées en tant que caches, surtout lorsqu'elles sont statiques
  • ThreadLocals dans les applications serveur, car les threads ne meurent généralement pas, le ThreadLocal n'est donc pas libéré
  • Interning strings (Strings.intern ()), ce qui entraîne la création d'une pile de chaînes dans l'espace permSpace

Si vous rencontrez des erreurs de MOO dans un langage mal ordonné, cela signifie généralement qu'une partie de la mémoire n'est pas comptabilisée par le collecteur. Peut-être que vos objets contiennent des ressources non-java? Si tel est le cas, ils devraient avoir une sorte de méthode 'close' pour s'assurer que cette ressource est publiée même si l'objet Java n'est pas collecté suffisamment tôt.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top