• Aucun résultat trouvé

lisateur conserve toutefois la possibilité d’afficher toutes les chaînes de références inverses en se servant de l’inspecteur graphique.

Comme pour les références entrantes, les chaînes de références inverses contiennent uni- quement des pointeurs physiquement présents dans le tas. Il n’est pas possible de masquer la compilation de structures de données des langages de haut niveau.

5.4 Exemple de débogage mémoire

Cette section présente un exemple typique d’utilisation des outils de débogage mémoire précédemment décrit, afin de montrer comment l’utilisateur peut détecter les causes de bogues liés à des problèmes d’allocation.

Le programme présenté dans la figure5.5est une démonstration inclue dans la distribu-

tion de la bibliothèque graphique Biglook. Son but est d’illustrer les possibilités d’animation de la bibliothèque. Le programme définit un type d’objet graphique bonhomme, qui étend le type canvas-image. Il commence son exécution en chargeant six images (ligne 9) pour animer le bonhomme. Puis, la fonction go crée une fenêtre (ligne 15) composée d’un canevas graphique et d’un bouton. Lorsque l’utilisateur clique sur ce bouton, trente bonshommes sont disposés aléatoirement sur le sol et s’animent. Leur comportement est dicté par la fonction new-runner : un bonhomme se déplace plus ou moins vite vers la droite (lignes 32 à 34) et puis recommence quelques milli-secondes plus tard (ligne 35). Dès qu’il sort de la fenêtre, il est détruit (ligne 37) et un nouveau bonhomme est créé à gauche de la fenêtre (ligne 38).

Le programme affiche constamment trente bonshommes en mouvement. Toutefois, si le programme s’exécute pendant une longue période de temps, il se met à ralentir, puis finit par planter en indiquant que la mémoire est pleine. Cela indique clairement la présence d’une fuite mémoire.

Afin de détecter la cause du bogue, l’utilisateur doit inspecter le tas pour surveiller les allocations effectuées durant l’animation. Pour cela, il suffit de relancer le programme, d’attendre que la fenêtre graphique apparaisse et de suspendre l’exécution par un CTRL+C. À partir de ce moment, l’utilisateur pose une marque temporelle pour ne plus s’occuper des objets alloués avant l’animation, reprend l’exécution et clique sur le bouton de la fenêtre. Au bout d’un certain temps, il suspend l’exécution, force le GC à ramasser les objets morts et inspecte les objets encore vivants alloués depuis la marque temporelle.

La figure5.6présente les statistiques renvoyées par le débogueur. Plusieurs informations

peuvent en être extraites. Tout d’abord, les six images sont stockées en mémoire par des objets du package sun.java2d. D’autre part, les trente bonshommes sont associés à trente

CHAPITRE 5. DÉBOGAGE DE L’ALLOCATION MÉMOIRE

1 (module anim2 2 (library biglook)

3 (static (class bonhomme::canvas-image

4 (images (default *bonhomme-image-list*))

5 (speed (default (+ 10 (random 30))) read-only))) 6 (main go))

7

8 (define *bonhomme-image-list*

9 (let ((images (map (lambda (x) (file->image (format "runner s.xpm" x)))

10 ’(1 2 3 4 5 6))))

11 (set-cdr! (last-pair images) images) 12 images))

13

14 (define (go args)

15 (let* ((w (instantiate::window (title "runner demo")))

16 (c (instantiate::canvas (parent w) (height 30) (width 150)))) 17 (instantiate::canvas-line (canvas c) (points ’(10 20 140 20))) 18 (instantiate::button

19 (text "Go!") (parent w) 20 (command (lambda (_) 21 (for-each (lambda (_) 22 (new-runner c (+ 10 (random 140)))) 23 (iota 30))))) 24 (widget-visible-set! w #t) )) 25 26 (define (new-runner c x0)

27 (let ((runner (instantiate::bonhomme (x x0) (y 15) (canvas c) ))) 28 (define (run)

29 (with-access::bonhomme runner (image x images speed) 30 (if (< x 140)

31 (begin

32 (set! x (+fx 1 x))

33 (set! image (car images)) 34 (set! images (cdr images))

35 (after speed run))

36 (begin

37 (destroy runner)

38 (new-runner c 10) ))))

39 (run)))

Fig. 5.5: Débogage d’un programme de démonstration de la bibliothèque Biglook objets javax.swing.Timer pour gérer les animations. Parmi les 35 pairs allouées, on peut inclure les trente nécessaires au canevas pour stocker la liste de bonshommes affichés.

Dans la trace, une valeur ne semble pas conforme aux attentes : il y a 230 objets bonhomme (ainsi que leur implantation nommée %canvas-image) au lieu des 30 attendus. De manière aussi inattendue, on compte 230 objets anim2, qui représentent les fermetures créées lors de la création d’un bonhomme (ligne 28). Le grand nombre d’objets java.lang.Object[] inclut les 230 objets nécessaires à chacune de ces fermetures pour stocker les variables capturées

cet runner1.

L’information la plus étonnante est la présence de 25606 instances d’objets de classe

BJTimerAdapter et LinkedList$Entry, qui à eux seuls occupent la moitié de la mémoire.

1

L’implantation des fermetures Bigloo est décrit en détail dans le chapitre6 76

5.4. EXEMPLE DE DÉBOGAGE MÉMOIRE (bugloo) (info heap mark)

java.util.LinkedList$Entry => 25607 instances (614568 bytes)

bigloo.biglook.peer.Jlib.BJTimerAdapter => 25606 instances (409696 bytes) java.lang.Object[] => 256 instances (7416 bytes)

::bigloo.biglook.peer.Lwidget.%canvas-image => 230 instances (12880 bytes) ::bonhomme => 230 instances (7360 bytes)

anim2 => 230 instances (5520 bytes) ::pair => 35 instances (560 bytes)

javax.swing.Timer => 30 instances (1440 bytes)

javax.swing.Timer$DoPostEvent => 30 instances (480 bytes) javax.swing.event.EventListenerList => 30 instances (480 bytes) java.lang.Class => 17 instances (7952 bytes)

java.util.Hashtable$Entry => 6 instances (144 bytes)

sun.java2d.x11.X11CachingSurfaceManager => 6 instances (288 bytes) sun.java2d.DefaultDisposerRecord => 6 instances (144 bytes)

::(array-of-char) => 3 instances (240 bytes) ::string => 2 instances (1424 bytes)

java.lang.InterruptedException => 1 instances (24 bytes) java.util.LinkedList => 1 instances (24 bytes)

javax.swing.TimerQueue => 1 instances (16 bytes) 52401 instances in 44 classes (total 1073744 bytes) 2307936 bytes used in heap (total 5177344 bytes) stats took 0.138s

Fig. 5.6: Exploration du tas du programme Biglook

Le programme n’a pas directement alloué ces objets. Il faut donc demander au débogueur de retrouver le responsable de ces allocations :

(bugloo) (info alloc ,(heap get "biglook.peer.Jlib.BJTimerAdapter" 0))

#0 (%after ::int ::proc) in class biglook.peer.Llib._after #1 (after ::obj) in class biglook.Llib.after

Le débogueur nous indique que la fonction Biglook after est responsable de l’allocation des objets BJTimerAdapter. Cette fonction est appelée à peu près tous les dixièmes de seconde, ce qui explique le grand nombre d’objet dans le tas, mais n’explique toujours pas la fuite mémoire (on s’attendait à avoir 30 objets BJTimerAdapter). La fonction after doit aussi insérer ces objets dans une liste chaînée, étant donné le nombre quasi-identique d’objets LinkedList$Entry dans le tas. La liste est sûrement référencée par une racine du GC. Pour le vérifier, il faut demander au débogueur de calculer une chaîne de référence inverse :

(bugloo) (backref ,(heap get "::runner" 0))

#0 biglook.peer.Jlib.BJTimerAdapter | field data #1 java.util.HashMap$Entry | field next #2 java.util.HashMap$Entry | field next #3 java.util.HashMap$Entry | field next

CHAPITRE 5. DÉBOGAGE DE L’ALLOCATION MÉMOIRE

La chaîne indique que la liste est stockée dans la variable statique timer_list de la classe BJTimerAdapter et que les instances de cette classe contiennent un pointeur vers une fermeture Scheme à exécuter. Cela explique pourquoi les fermetures anim2 sont encore vivantes et pourquoi les objets bonhomme qu’elles ont capturés le sont aussi.

Pour supprimer la fuite mémoire, il faut maintenant chercher la fonction qui est censée retirer les objets de la liste chaînée timer_list. En examinant le code source, on trouve la fonction suivante :

public void actionPerformed( ActionEvent e ) { Object timer = e.getSource();

if( thunk.funcall0() == foreign.BFALSE ) {

((javax.swing.Timer)timer).stop(); timer_list.remove( this );

} }

Le déclencheur d’action asynchrone n’est retiré de la liste que si la fonction utilisateur renvoie la valeur Scheme #f. Ce comportement s’explique par la définition de after : lors- qu’on utilise cette fonction en remplaçant le temps en milli-secondes par le symbole ’idle, la fermeture utilisateur est appelée à chaque fois que l’interface graphique est au repos. Les appels cessent lorsque la fermeture renvoie #f.

En conclusion, le débogueur nous a permis de localiser la cause de la fuite mémoire d’un programme d’exemple de la bibliothèque graphique Biglook. Pour la faire disparaître, il suffit de modifier la fonction new-runner pour qu’elle renvoie la valeur #f après la ligne 38. Sans un outil de débogage mémoire adapté, la localisation et la correction de ce bogue aurait sans doute été beaucoup plus compliquée, voir impossible.