Optimisation de la mémoire

Jérôme Lafosse

Cet article est un extrait gratuit de l'ouvrage Java EE - Guide de développement d'applications web en Java, Chapitre 7 : Techniques avancées, section "Optimisation de la mémoire".

Commentez

- Titre : Optimisation de la mémoire

- Auteur : Jérôme Lafosse

- Parution : 20 juillet 2009

- Mise à jour : 20 juillet 2009

- Licence : Copyright ® 2009 Jérôme Lafosse / ENI. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- 1. Présentation

- 2. Gestion dynamique de la mémoire

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.

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.

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.

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.

<!-- 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.

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.

<%@ 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.

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");
}
}