Surveillance de la mémoire utilisée par la JVM dans une application Java

publié le

Comment faire pour implémenter une surveillance d'un seuil d'utilisation de la mémoire sans compromettre la performance.

Nous pouvons obtenir à tout moment le niveau de mémoire utilisée par la JVM en utilisant la méthode Runtime.getRuntime().totalMemory(). Alors pour mesurer en permanence la consommation de mémoire, nous pourrions utiliser un processus (thread) séparé qui va appeler cette méthode à chaque seconde et ensuite, vérifier si la limite obtenue en appelanttotalMemory() est plus grande que le seuil. Mais le problème de cette méthode est qu'elle va dégrader les performances de l'application. En effet, chaque appel à la méthode totalMemoy() est coûteuse en terme de temps (de 70 à 100 ns).

Une meilleure alternative est d'utiliser un platform MXBean, qui est un MBean (managed bean) utilisé pour surveiller ou configurer la JVM.

Voici la liste de ce qui peut être surveiller en utilisant un MXBean:

- Le nombre de classes chargées et de processus (thread) en exécution.
- Le temps écoulé depuis le démarrage de la machine virtuelle, les propriétés système, et les arguments en entrée de la VM.
- Statut et pile d'exécution des processus actifs.
- Consommation mémoire.
- Statistiques sur le Garbage Collector.
- Détection de mémoire faible.
- Détection sur demande des deadlock.
- Information sur le système d'exploitation.

Donc dans notre cas nous voulons surveiller la consommation mémoire, pour cela nous allons utiliser un MemoryMXBean. MemoryMXBean a en effet une fonctionnalité bien pratique qui permet d'enregistrer un seuil d'utilisation de mémoire. Si ce seuil est atteint, un évènement sera lancé (plus besoin de surveiller périodiquement la mémoire utilisée.)

La première étape est de créer la classe qui va intercepter l'évènement envoyé par MemoryMXBean. Pour cela, il suffit d'implémenter l'interface NotificationListener et de surcharger la méthode handleNotification. Dans cette méthode, nous loguons un warning si une notification de type MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED est relevée.

package com.inoneo.util;

import java.lang.management.MemoryNotificationInfo;

import javax.management.Notification;
import javax.management.NotificationListener;


/**
 * Notification call in case of "collection Usage Threshold" reached.
 * 
 * The collection usage threshold is a manageable attribute of some garbage-collected memory pools.
 * After a Java VM has performed garbage collection on a memory pool, some memory in the pool will still be in use.
 * The collection usage threshold allows you to set a value for this memory.
 *
 */
public class LowMemoryListener implements NotificationListener {

    private static final Logger log = Logger.getLogger(LowMemoryListener.class);    

    public LowMemoryListener() {
        super();
    }

    @Override
    public void handleNotification(Notification notification, Object handback)  {
        String notifType = notification.getType();
        if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
            // potential low memory, log a warning
            log.warning("Memory usage threshold reached");
        }
    }


}

Maintenant, nous avons besoin d'enregistrer un seuil d'utilisation qui va déclencher l'évènement. La mémoire est divisée en différents pools. Dans le code ci-dessous, nous parcourrons chaque pool mémoire et enregistrons un seuil pour chacun d'entre eux qui supporte cette fonctionnalité.

Ce bout de code doit être placé à l'endroit ou vous désirez commencer la surveillance de l'utilisation de la mémoire, généralement au début du programme.

package com.inoneo;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;

import javax.management.NotificationEmitter;

import java.util.List;

import com.inoneo.util.LowMemoryListener;

public static void main(String[] args){

       //Start to monitor memory usage
       MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
       NotificationEmitter emitter = (NotificationEmitter) mbean;
       LowMemoryListener listener = new LowMemoryListener(this, maxHeapMemoryThreshold);
       emitter.addNotificationListener(listener, null, null);
       
       //set threshold
        List pools = ManagementFactory.getMemoryPoolMXBeans();
        for (MemoryPoolMXBean pool : pools) {
            if(pool.isCollectionUsageThresholdSupported()){
            // JVM Heap memory threshold in bytes (1.00 Gb = 1000000000), 0 to disable
                pool.setUsageThreshold(1000000000);
            }
        }
}

Dans cet exemple, nous avons établi un seuil de consommation mémoire de 1 Gb. Cela signifie que si la consommation mémoire atteint ou dépasse 1 Gb, un évènement sera émis. Faites attention d'entrer une limite inférieure au maximum de mémoire paramétré pour la JVM, sinon vous aurez une exception au démarrage:

java.lang.IllegalArgumentException: Usage threshold cannot exceed maximum amount of memory for pool.
	at com.ibm.lang.management.MemoryPoolMXBeanImpl.setUsageThreshold(MemoryPoolMXBeanImpl.java:437)

Si vous voulez désactiver un seuil, entrez une limite de 0.

Ressources:
Overview of Java SE Monitoring and Management
Using the Platform MBean Server and Platform MXBeans

comments powered by Disqus