Avant-propos

La particularité du soft développé est qu'il utilise JNI, et on a donc une quantité de mémoire non négligeable qui est allouée côté C/C++ (et donc dont la JVM ne prend pas vraiment compte, a priori). Tout ce qui est alloué côté C doit y être libéré (logique), et donc les objets qui contiennent des "pointeurs" (stockés dans des entiers, je sais, c'est mal ^^) ont tous une méthode finalize qui va appeler les routines de nettoyage en C (libération d'une image, etc...).

Méthode finalize, oui mais quand ?

En théorie la méthode finalize est appelée quand le GC a vu que cet objet n'est plus référencé et qu'il doit être libéré... Sauf que voilà, quand est-ce sensé se produire ? Ca dépend de l'implémentation de la JVM... D'après la doc de Sun (donc concernant uniquement leur JVM), la JVM a en fait un thread qui se charge d'appeler les méthodes pour les objets qui sont dans une queue d'objets à finaliser ; du coup, si on génère beaucoup d'objets à finalizer dans un thread à haute priorité, on se retrouve avec une perte de mémoire car le thread du finalize n'a pas le temps de tout dégager. C'est intéressant, mais a priori ce n'est pas notre cas :/ (pour ceux que ça intéresse, plus d'info ici : http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/memleaks.html#gbywm)

En fait ce qui semble se passer est encore plus "con" : les objets qui contiennent des pointeurs C sont très petits, car en fait ils ne contiennent que des entiers... Du coup, pourquoi est-ce que la JVM irait récupérer de la mémoire alors que l'utilisation globale apparait comme minime ? Car l'objet peut, aux yeux de la JVM, ne faire que quelques octets, alors qu'en pratique elle contient un pointeur sur une image qui fait 1Mo, et là, ce n'est plus du tout la même chose (surtout quand on commence à en cumuler).

Destructeurs en Java ??

D'après ce que j'ai pu lire à droite à gauche, il semblerait que le fait de passer une référence à null motiverait le GC à libérer l'objet qui était auparavant référencé. Ca parait assez logique, puisque si on imagine qu'on arrête de référencer un objet, avant qu'il ne soit libéré il faut que le GC se rende compte que plus personne ne le référence. Alors que si on met la référence à null et que c'était la seule, on "facilite" (a priori) le travail du GC, qui peut se rendre compte immédiatement que cet objet ne sert plus à rien.

Sauf qu'on ne sait toujours pas quand la méthode finalize sera appelée ^^ Une solution consiste à reprendre ce qui fait la "force" du C++ (façon de parler, hein ^^) : les destructeurs. En C++, on n'aurait tout simplement pas ce problème (mais on en aurait d'autres, ne vous en faites pas !), puisque quand on a fini d'utiliser une image on va la libérer. On a donc commencé à rajouter des méthodes "close" sur nos objets, qui bien qu'elles ne fassent pas de libération de l'objet à proprement parlé (contraitement aux destructeurs), s'occupent au moins de virer la mémoire allouée côté C. Ca peut paraitre paradoxal de faire du Java et de se retrouver à gérer soi-même la mémoire, mais au final ça a quand même du sens puisque le problème vient en majeure partie du fait que cette mémoire a été allouée par du code C, donc non géré par le GC... L'autre chose qui va dans ce sens et que j'ai vu à droite à gauche, mais

J'ai bon espoir qu'avec ça les problèmes de mémoire qu'on a seront réglés, car actuellement l'application est pour le moins instable à l'heure actuelle...

Java pure : on n'est pas à l'abris...

Même si tout cela peut paraître un poil négatif (après on va dire que je peste tout le temps contre Java), je pense que ça reste intéressant. Du coup je vais donner un exemple d'utilisation mémoire non optimale en Java qui peut être facilement améliorée, car non, tout n'est pas la faute de JNI et du code C ^^ :

Imaginons qu'on fasse une classe qui gère une pile en utilisant un tableau. Quand on ajoute un objet à la pile (push), on l'ajoute à la fin de notre tableau, et on incrémente le nombre d'éléments que la pile contient. Quand on retire l'élément du dessus de la pile (pop), il suffit de décrémenter le nombre d'éléments et de renvoyer celui du haut. Tout ce la est particulièrement simple, mais en fait il y a une faille à la con : que se passe-t-il si on push 1000 objets, puis qu'on les pop tous ? Notre tableau contient, aux yeux de la JVM (et donc du GC), 1000 références à ces objets. Du coup, même s'ils ne sont plus référencés nul part ailleurs, on a une référence dessus, et il ne seront pas libérés.

La solution à ce problème est celle évoquée précédemment : mettre la référence à null... Donc quand on pop, il suffit de mettre dans notre tableau l'ancienne référence à null. Et là on peut faire autant de pop qu'on veut, on ne gardera jamais de "fausse" référence sur nos objets, et l'utilisation de la RAM redevient correcte.

Alors oui, on n'a pas vraiment de raison d'aller faire une pile en Java alors qu'on doit avoir 40 classes "natives" pour le faire, mais ça reste juste un exemple pour illustrer les potentiels problèmes.

Mot de la fin

Ce qu'il ressort de tout ça, c'est qu'un GC c'est génial, mais ça ne doit pas rendre paresseux pour autant. Car si on fait l'effort de passer les références à null lorsque c'est possible, on va gagner énormément en efficacité. Et contrairement au C++ où quand on fait un delete il vaut mieux être sûr qu'on ait la seule référence (ou pointeur :p) sur l'objet sous peine de pourir le programme, ici mettre à null n'engage finalement à rien, car s'il y a d'autres références ailleurs rien ne sera libéré.

Un GC aidé est un GC heureux et rapide, il font donc en prendre soin et bien s'occuper de lui, je peux vous assurer qu'il vous le rendra ^^ (d'ailleurs il vous rendra la mémoire, déjà...).

Je vous invite à vous documenter sur le site de Sun si vous voulez en apprendre plus : http://java.sun.com/javase/6/webnotes/trouble/TSG-VM/html/memleaks.html

Et si vous avez des astuces pour améliorer la gestion de la mémoire, je suis toujours preneur !