2008-09-30 6 views
14

Un de nos programmes obtient parfois une erreur OutOfMemory sur la machine d'un utilisateur, mais bien sûr pas quand je la teste. Je l'ai juste couru avec JProfiler (sur une licence d'évaluation de 10 jours parce que je ne l'avais jamais utilisée), et en filtrant sur notre préfixe de code, le plus grand bloc en taille totale et nombre d'instances est de 8000+ instances d'une classe simple particulière .Comment puis-je savoir ce qui se passe sur des objets non-traités?

J'ai cliqué sur le bouton "Garbage Collect" sur JProfiler, et la plupart des instances de nos autres classes ont disparu, mais pas celles-ci en particulier. J'ai recommencé le test, toujours dans le même cas, et cela a créé plus de 4000 instances de la classe, mais quand j'ai cliqué sur "Garbage Collect", ceux-ci sont partis laissant les plus de 8000 originaux.

Ces instances sont bloquées dans diverses collections à différentes étapes. Je suppose que le fait qu'ils ne soient pas collectés doit signifier que quelque chose garde une référence à l'une des collections afin de conserver une référence aux objets.

Des suggestions comment je peux comprendre ce qui tient sur la référence? Je suis à la recherche de suggestions sur ce qu'il faut rechercher dans le code, ainsi que sur les moyens de le trouver dans JProfiler, le cas échéant.

+0

Si vous recherchez un profileur gratuit, je vous suggère de regarder http://jiprof.sourceforge.net/. Peut-être un peu démodé, pas de gui de fantaisie et ainsi de suite, mais travaillant pour la plupart des cas. – dhiller

Répondre

18

Vider le tas et l'inspecter.

Je suis sûr qu'il y a plus d'une façon de le faire, mais en voici une simple. Cette description est pour MS Windows, mais des mesures similaires peuvent être prises sur d'autres systèmes d'exploitation.

  1. Installez le JDK si vous ne l'avez pas déjà. It comes with a bunch of neat tools.
  2. Démarrez l'application.
  3. Ouvrez le gestionnaire de tâches et recherchez l'ID de processus (PID) pour java.exe (ou quel que soit l'exécutable que vous utilisez). Si les PID ne sont pas affichés par défaut, utilisez Affichage> Sélectionner les colonnes ... pour les ajouter. Videz le tas à l'aide de jmap.
  4. Démarrez le serveur jhat sur le fichier que vous avez généré et ouvrez votre navigateur pour 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, les références à celles-ci, etc.

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 array type nomenclature Java utilise - comme en sachant que classe [Ljava.lang.Object; signifie vraiment un objet de type Objet [].

+1

Attention lors d'un vidage avec 'jmap': vous devrez peut-être lancer' jmap' en tant qu'utilisateur possédant le jvm auquel vous vous connectez. Voir [this] (http://stackoverflow.com/a/2943651/580412). – phs

2

Gardez un œil sur les conteneurs statiques. Tous les objets dans un conteneur statique resteront tant que la classe est chargée. Edit: correction d'une remarque incorrecte sur WeakReference.

0

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

2

Un candidat évident est les objets avec des finaliseurs. 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 fil finaliser) puis collectés à nouveau.

Sachez également que vous pouvez obtenir un OOME car le gc n'a pas réussi à collecter suffisamment de mémoire, bien qu'il y ait suffisamment de place pour que la demande d'objet soit créée. Sinon, la performance serait broyer dans le sol.

3

Aucune balle d'argent là-bas, vous devez utiliser le profileur pour identifier les collections qui contiennent ces objets inutiles et trouver l'endroit dans le code où ils auraient dû être supprimés. Comme l'a dit JesperE, les collections statiques sont le premier endroit à regarder.

5

Je regarderais les collections (en particulier celles 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 le HashMap ou d'une autre collection a une référence à cet objet, il ne sera pas le ramasse-miettes.

+0

S'il n'y avait pas d'autre référence à "map" après le premier System.gc(), ne serait-il pas collecté? –

+0

Oui, si rien d'autre ne fait référence à la HashMap, elle sera éligible à la GC'd. – 18Rabbit

+0

Cela a peut-être changé, mais la machine virtuelle Java ignore-t-elle l'appel system.gc et la fait-elle quand elle en a envie? – tloach

1

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

Les deux leçons importantes qu'il dégageons:

1) Les méthodes finales disent gc quoi faire quand il cueille l'objet, mais il ne demande pas de le faire, ni est-il un moyen d'exiger que c'est le cas.

2) L'équivalent moderne de la «fuite de mémoire» dans les environnements de mémoire non gérés est les références oubliées. Si vous ne définissez pas toutes les références à un objet à null lorsque vous avez terminé, l'objet sera jamais être éliminé. Ceci est particulièrement important lors de l'implémentation de votre propre collection ou de votre propre wrapper qui gère une collection. Si vous avez un pool ou une pile ou une file d'attente et que vous ne définissez pas le compartiment null lorsque vous "supprimez" un objet de la collection, le compartiment dans lequel cet objet se trouvait conservera cet objet actif jusqu'à ce que ce compartiment défini pour se référer à un autre objet. Je sais que d'autres réponses ont mentionné cela, mais j'essaie d'offrir plus de détails.

+0

Je pense que vous ne devez pas annuler la référence à un objet si elle sort de la portée. –

+0

En p. 24 de Bloch "Effective Java" Point 6: Éliminer les références d'objets obsolètes, il existe un exemple de pile mal implémentée où ses éléments sont mis en cache dans les éléments Object [], mais ils ne sont jamais annulés du tableau des éléments quand pop() est appelée. –

1

Collections a déjà été mentionné.Un autre emplacement difficile à trouver est si vous utilisez plusieurs ClassLoaders, car l'ancien classloader peut ne pas être récupéré avant que toutes les références aient disparu.

Vérifiez également les statistiques - elles sont désagréables. Les cadres de journalisation peuvent garder les choses ouvertes, ce qui peut conserver des références dans les appenders personnalisés.

Avez-vous résolu le problème?

+1

Non, j'ai été licencié. Donc ce n'est plus mon problème. –

1

Quelques suggestions:

  • cartes illimitées utilisés comme caches, surtout quand statique
  • ThreadLocals dans les applications de serveur, parce que les fils ne meurent pas, de sorte que le ThreadLocal n'est pas libéré
  • cordes interner (Strings.intern()), ce qui entraîne une pile de Strings dans le PermSpace
Questions connexes