1. Présentation

Les applications Java et plus particulièrement le serveur Tomcat sont gourmands en terme de mémoire vive (RAM). La version Tomcat 6.X améliore considérablement la gestion de la mémoire et l'utilisation du Garbage Collector ou ramassemiettes afin d'éviter les fuites de mémoire et la persistance d'objets non utilisés dans la mémoire.

Avant d'allouer inutilement de la ressource mémoire à notre serveur, il est nécessaire de tester la montée en charge de celui-ci avec des outils de monitoring comme ceux présentés dans le chapitre consacré à Tomcat. Il n'est pas rare de constater que la montée en charge de la RAM est liée à une mauvaise fermeture de connexion au SGBD, à un pointeur de lecture d'un fichier ou un accès à des ressources diverses.

Le chapitre consacré à Tomcat explique en détail comment allouer et gérer la mémoire du serveur Java. Pour rappel, la page status/état du serveur du manager Tomcat permet d'afficher les informations sur l'utilisation de la mémoire.

Image non disponible

Sous Windows, il est possible d'augmenter la mémoire allouée au serveur Tomcat en utilisant l'outil Apache Tomcat Properties et les options Java ou avec la fenêtre de préférences d'Eclipse.

La mémoire initiale réservée au serveur est paramétrée avec l'option -Xms et la mémoire maximale avec l'attribut -Xmx.

Image non disponible

Sous Linux la mémoire allouée à Tomcat est paramétrée dans le fichier catalina.sh. Le code suivant permet d'allouer 2 Go de RAM au serveur.

 
Sélectionnez

JAVA_OPTS="-Xms2048m -Xmx2048m"

Ce principe d'allocation mémoire associé à la configuration de la JVM est très utilisé mais il est également parfois nécessaire de gérer la mémoire en dynamique du point de vue de la programmation. En effet, le code peut parfois être gourmand en terme de mémoire lors d'instanciations de nombreux objets, d'accès à des ressources importantes, de transformations d'images et d'utilisation de bibliothèques graphiques.

2. Gestion dynamique de la mémoire

La gestion dynamique de la mémoire et du Garbage Collector peut être réalisée en programmation. Dans notre projet chatbetaboutique, nous allons ajouter un attribut au fichier de configuration de l'application web.xml.

 
Sélectionnez

<!-- charge mémoire utilisée pour la limite (ex :
Total-free=15-6=9Mo de limite maxi à atteindre) -->
<context-param>
	<param-name>chargememoire</param-name>
	<param-value>6</param-value>
</context-param>

D'après la configuration affichée avec le manager de Tomcat, nous disposons dans notre exemple de 16 Mo de mémoire avec un maximum autorisé de 64 Mo. Cette charge mémoire correspond à un calcul mathématique qui est le maximum de charge mémoire utilisée sur la machine (Total libre). Dans notre cas, la charge maximale autorisée est de 9 Mo de RAM. Si cette limite est atteinte, le Garbage Collector sera déclenché de manière forcée. Sur un serveur en production nous trouvons par exemple le calcul suivant : Total - libre = 1290 - 600 = 690 Mo qui indique que pour 1 Go de RAM au total, le ramasse-miettes sera déclenché lorsque la mémoire aura atteint 690 Mo de charge.

Nous pouvons ensuite créer une classe statique nommée OutilsGarbageCollector placée dans le paquetage boiteoutils et qui permet de déclencher le Garbage Collector en fonction de la limite atteinte.

 
Sélectionnez

package boiteoutils;

public class OutilsGarbageCollector
{
	//fixer la mémoire maxi à atteindre en méga octets (500Mo soit une charge de Total-free=6-5=1Mo)
	private static final int LIMITEMEMOIRE=5;
	/****************************************************************************
	* fonction qui permet de vider le garbage collector en fonction d'une taille
	****************************************************************************/
	public static void verifierChargeGarbageCollector(double chargememoire)
	{
		//récupérer les informations du système
		Runtime r=Runtime.getRuntime();
		//mémoire
		double memoirelibre=(r.freeMemory() / 1000000d);
		//limite
		double limitememoire;
		//utiliser notre limite ou celle par défaut
		if(chargememoire!=0) limitememoire=chargememoire;
		else limitememoire=LIMITEMEMOIRE;
		//si moins de mémoire libre que notre limite, alors vider la mémoire
		if(memoirelibre<limitememoire)
		{
			try
			{
				//appeler les méthodes finalize() des objets
				r.runFinalization();
				//vider la mémoire
				r.gc();
				System.gc();
			}
			catch(Exception e)
			{
				//logger
				System.out.println("Erreur lors du vidage de
				la mémoire en mode automatique");
			}
		}
	}
	/****************************************************************************
	* fonction qui permet de vider le garbage collector de manière forcée
	****************************************************************************/
	public static void viderGarbageCollector()
	{
		//récupérer les informations du système
		Runtime r=Runtime.getRuntime();
		try
		{
			//appeler les méthodes finalize() des objets
			try
			{
			//appeler les méthodes finalize() des objets
			r.runFinalization();
			//vider la mémoire
			r.gc();
			System.gc();
			}
			catch(Exception e)
			{
			//logger
			System.out.println("Erreur lors du vidage de
			la mémoire en mode forcé runFinalization");
			}
		}
		catch(Exception e)
		{
			//logger
			System.out.println("Erreur lors du vidage de
			la mémoire en mode forcé viderGarbageCollector");
		}
	}
	//fin de la classe
}

La première méthode permet de vider la RAM à l'aide du GarbageCollector à partir d'un maximum atteint correspondant à la valeur précisée dans le fichier de configuration web.xml. Pour cela nous vérifions la taille actuelle de la mémoire et en cas de besoin nous détruisons les objets non utilisés et nous lançons le Garbage Collector. La seconde méthode permet de toujours vider la RAM de manière forcée.

Maintenant nous pourrons vérifier cette mémoire et la vider au besoin dans une page JSP qui réalise un traitement complexe, une classe qui manipule d'importantes ressources ou alors un filtre qui sera déclenché sur toutes les URL.

 
Sélectionnez

<%@ page import="boiteoutils.OutilsGarbageCollector" %>
<%
	//charge maxi de la mémoire autorisée
	String chargememoire=getServletContext().getInitParameter("chargememoire");
	//gérer la mémoire
	OutilsGarbageCollector.verifierChargeGarbageCollector(Double.parseDouble(chargememoire));
%>

Cette technique bien que radicale est très utilisée en production afin d'éviter les ralentissements des traitements voire même des saturations de mémoire. Cette solution nous permet en effet de bénéficier d'une plate-forme Internet conséquente, qui manipule d'importantes ressources sans risquer de saturer le serveur.

 
Sélectionnez

package chatbetaboutique;

import modele.UtilisateurModele;
import org.apache.struts.action.*;
import org.apache.struts.actions.MappingDispatchAction;
import boiteoutils.OutilsGarbageCollector;
import boiteoutils.Utilisateur;
import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.*;
import javax.sql.DataSource;

public class GestionUtilisateurAction extends MappingDispatchAction{
	//variables de la classe
	DataSource ds=null;
	//afficher la liste des utilisateurs
	public ActionForward listeutilisateur(ActionMapping mapping, ActionForm form, 
			HttpServletRequest request, HttpServletResponse	response)
		throws IOException, ServletException
	{
		//récupérer la datasource du plugin dans un attribut
		présent dans le context de la servlet
		ds=(DataSource)servlet.getServletContext().getAttribute("datasource");
		//créer le modèle
		UtilisateurModele utilisateurmodele=new UtilisateurModele(ds);
		//fermer la datasource
		this.ds=null;
		//retourner la liste des utilisateur
		ArrayList listeutilisateur=
		(ArrayList)utilisateurmodele.getListeUtilisateur();
		//retourner la liste des utilisateurs
		request.setAttribute("listeutilisateur",listeutilisateur);
		//vider par sécurité
		listeutilisateur=null;
		//vider le garbage collector
		OutilsGarbageCollector.viderGarbageCollector();
		//retourner sur la page d'affichage des utilisateurs
		return mapping.findForward("listeutilisateur");
	}
}