Depuis peu de temps, je suis arrivé sur un projet J2EE pratiquement terminé. Mon but était de comprendre le fonctionnement de l’équipe, la structure du programme et du projet, d’en analyser les atouts et les défaillances. Pendant cette période, j’en ai profité pour regardé rapidement le code source. J’ai décidé de cataloguer les quelques petits problèmes que je voyais, tout en y apportant des solutions. Ce sont des petits reflexes à avoir lorsque l’on travaille en Java.
Attention au ramasse-miette
Demandez à n’importe quelle personne ayant travaillé en C/C++ et évolué sur du Java la chose qu’il a préféré, et cette personne vous répondra le ramasse-miette. C’est vrai qu’en C# comme en Java, le ramasse-miette est quelque chose d’exceptionnel, un aspect un peu magique qui rend la vie plus facile et le code plus souple. On n’a tout à coup plus à tenir compte de la gestion de la mémoire, ce qui peut représenter un énorme gain de temps, de production, et d’apétit le soir en rentrant chez soi.
Toutefois, le ramasse-miettes ne va pas s’occuper de tout. En effet, il existe des objets (pointeurs, poignées sur les fichiers, sémaphores, etc…) qui ont leur propres fonctions .close(), .destroy() ou .release() qu’il ne faut pas oublier d’appeller. Bien entendu, tout est automatiquement nettoyé par le dernier finalizer lorsque le programme se termine… Mais bien souvent, il vaut mieux s’en occuper de suite. (Imaginez un serveur multi-thread qui fait une fuite à chaque requête HTTP, la mémoire sera vite débordée…)
Ainsi, je suis tombé sur ce code:
-
-
-
-
oProps.load(sInputStream);
-
sInputStream.close();
-
return oProps;
-
}
Ce code compile parfaitement. De plus, l’application tournait aussi bien. Seulement, que se serait-il passé si la fonction load(sInputStream) avait relevé une exception? Le close() n’aurait pas eu lieu, et on aurait eu une fuite… jusqu’au dernier finalizer.
En fait, la solution consiste à utiliser un bloc try/finally pour vérifier que la fonction ne renvoie pas d’erreur :
-
-
-
try{
-
-
oProps.load(sInputStream);
-
return oProps;
-
} finally {
-
sInputStream.close();
-
}
-
}
Ainsi, le close() se fera toujours dans de bonnes conditions. J’avoue que cette pratique n’est pas très facile à mettre en oeuvre, surtout qu’elle risque facilement d’imposer la lourdeur des blocs try/finally imbriqués. On pourrait aussi revenir à une solution qui s’apparente au C/C++ et conserver quelque part une trace des différentes allocations mémoire pour les libérer manuellement, mais on perd l’intéret du ramasse-miettes… A méditer!