Tous ceux qui ont rencontrés ce problème pour la première fois ont certainement dû s’arracher les cheveux. Pourquoi donc ce processus Java se plaint-il de ne plus avoir de mémoire alors qu’il n’utilise pas la moitié de ce qui lui est alloué ?
La première partie de la réponse est assez simple… quand on l’a déjà vu! La JVM de Sun définit la taille de la mémoire allouée en deux lots distincts.
- La mémoire classique (« heap ») configurée classiquement avec les options -Xms128m et -Xmx256m, respectivement pour la initiale et la taille maxime
- La mémoire de la « permanent generation », contenant notamment les déclarations des objets Class (mais pas les instances!) et Method. La taille de cet espace est définit par les options -XX:PermSize=96m et -XX:MaxPermSize=128m, respectivement pour la taille initiale et la taille maximale
Il est tout à fait possible que le réglage par défaut (64Mo de taille maximale) ne suffise pas et qu’une « OutOfMemoryError : PermGen space » apparaisse après quelques minutes d’utilisation de l’application, suite à la compilation d’un volume trop important de JSP. Dans ce cas, augmenter l’espace « permanent generation » à 128Mo devrait résoudre le problème. Attention cependant, comme toutes les options commençant par XX, il s’agit de paramètres spécifiques à l’implémentation de la JVM, en l’occurrence celle de Sun. Ainsi la JVM BEA par exemple ne sépare pas la définition de ces deux espaces dans ses options de JVM.
Donc, grâce à ces options nous voilà débarrassés de cette erreur de mémoire. Pas forcément…
En effet, il est possible que cette erreur réapparaisse par exemple après plusieurs re-déploiements d’une application web sous Tomcat. Dans ce cas, le réglage de la mémoire n’est pas en cause, mais il s’agit bel et bien d’une fuite mémoire. Plus précisément, les classes de l’application que l’on a déchargée ne sont pas recyclées par la JVM. En effet, tant qu’une référence subsiste vers une classe chargée par le classloader de l’application web, ce classloader et toutes les classes qu’il a définit ne pourront pas être recyclés.
Une multitude de causes peuvent aboutir à ce problème, parmi les plus classiques :
-
le chargement d’un driver JDBC dans le classloader d’une application web : le driver (et donc le classloader et toutes ses classes) reste référencé au niveau serveur par le DriverManager, on peut utiliser un ServletContextListener pour décharger le driver
-
une bibliothèque chargée au niveau serveur (type Hibernate) référence dans un cache des classes de l’application (comme par exemple des classes Proxy des classes du modèle)
-
l’application web a fixé des données dans un objet ThreadLocal, liées à un thread du serveur (le thread appartient à un pool du conteneur et n’est jamais recyclé)
Pour plus d’informations sur ce sujet et en particulier une liste d’outils permettant d’identifier la source du problème, je vous conseille cet article.