vendredi 20 novembre 2009

Mais où va ma mémoire ?

Dans le cadre de développement Java, on se pose souvent cette question. En général, c'est parce que des objets sont référencés par des champs statiques, ce qui fait qu'ils ne sont jamais "Garbage Collectés". Comme un objet Java en référence souvent beaucoup d'autre, il suffit donc d'un seul objet dans un champ statique pour empêcher le "Grabage Collecting" d'une énorme grappe d'objet !

Dans mon cas, je sais que j'ai des fuites mémoire lors du passage de mes tests. Ces fuites mémoire sont localisées dans un framework que j'utilise : GWT, qui j'utilise de manière assez originale dans mes tests :-) (voir ici, ici et pour comprendre ce que je fais).

Mon problème est donc le suivant : la consommation de mémoire augmente au fur et à mesure des tests, probablement parce que des champs statiques ne sont pas réinitialisés entre chaque tests. Comment trouver ces champs statiques ?

J'utilise HProf. C'est assez simple (et fournit de base dans le JDK de Sun, rien à installer) :
  • j'ajoute -agentlib:hprof=format=b,file=/tmp/dump.hprof dans les arguments de la VM lorsque je lance un test
  • je choisit un test que je soupçonne de provoquer une perte de mémoire. J'ajoute dans la classe de test le code suivant :
        @AfterClass
        public static void tearDownClass() {
            System.out.println("Call GC");
            System.gc();
            System.gc();
            System.gc();
            System.gc();
            System.gc();
        }
  • je lance le test : il passe, mais au moins dix fois moins vite que normalement :-( A la fin du test : je voie dans la console :
        Call GC
        Dumping Java heap ... allocation sites ... done.
Le dump du Java Heap peut être très long (une demi heure ....)
On peux ensuite rechercher la fuite mémoire, en utilisant jhat. Cet outil permet de parcourir le Heap restant à la fin du test, et de rechercher les fuites. Pour le lancer, il suffit de taper la commande suivante :
jhat -J-Xmx1024M /tmp/dump.hprof

On peut ensuite parcourir le heap, en allant avec un navigateur sur l'url http://localhost:7000.

Je vous laisse découvrir toutes les possibilités de l'outil. Dans mon cas, pour trouver les champs statiques, je fait une requête OQL (dernier lien en bas), du type :
select heap.livepaths(s) from com.google.gwt.dom.client.AnchorElement s
Pourquoi AnchorElement ? Parce que je sais que des objets graphiques de GWT ne sont pas libérés, j'en ai donc choisi un au hasard. Après pas mal de calcul, une page (en général énorme) s'affiche et me donne quel est le champs statique qui référence mes objets :
[ Static reference from com.google.gwt.user.client.Event.handlers (from class com.google.gwt.user.client.Event)->com.google.gwt.event.shared.HandlerManager@0x50014a9c (field registry) ->com.google.gwt.event.shared.HandlerManager$HandlerRegistry@0x50014a9f (field map) ->java.util.HashMap@0x50014aa0 (field table) - ...
Le champs statique s'appelle donc handlers, et est dans la classe : com.google.gwt.user.client.Event. Il ne me reste plus qu'à aller vider ce champs par réflexion, et le tour est joué !

Par contre, cela fonctionne bien sur des petits tests unitaires, mais sur des gros tests, c'est beaucoup trop lent, et HProf et jhat ont tendance à planter. Mais bon, cela m'a quand même bien aidé.

Aucun commentaire:

Enregistrer un commentaire