Accueil Nos publications Blog Java Performances (1/3) – Configuration de la JVM

Java Performances (1/3) – Configuration de la JVM

Comme toute étape du processus de développement d’une application, l’étude des performances nécessite un plan d’action.

Cet article appartient à la suite d’article (Configuration de la JVM, Dumps mémoire et Optimisation de code) qui a pour objectif  de présenter les différentes étapes d’analyse d’un problème de performances d’une application en production sous un Système d’Exploitation de type Unix, en utilisant une approche bottom-up et ceci en nous focalisant uniquement au niveau de la JVM. La méthode bottom-up commence au niveau le plus bas d’une application, au niveau du CPU, à la recherche de cache misses, d’instructions CPU utilisées de manière inefficientes, pour ensuite remonter aux différents modules utilisés par l’application. Cette approche est souvent utilisée lorsqu’il n’est pas possible d’effectuer des modifications dans le code. Malheureusement, il arrive parfois qu’une modification de code soit inévitable.

Dans la suite de ces articles, nous considérerons que la JVM est la JVM officielle de Oracle : HotSpot, et que l’utilisateur est connecté à une machine ayant les différents outils en local, qu’il a les droits adéquats pour les utiliser et accéder aux processus système qui l’intéressent.

Java Virtual Machine Process Status Tool

La première étape est d’identifier la configuration de la JVM et de récupérer par la même occasion le LVMID (Local Virtual Machine ID)

Java Virtual Machine Process Status Tool (jps)[1] permet de lister tous les processus de type JVM actuellement actifs, et ceci quel que soit la version de la JVM, ainsi que les paramètres fournis à la JVM et à la méthode main.

Ordre des paramètres au lancement d’une JVM


    # javaw com.soat.perf.SoatPerf -Xms256m –Xmx1024m
    # jps -v
    3792 Jps -Dapplication.home=-Dapplication.home=/usr/lib/jvm/java-6-openjdk -Xms8m
    4484 SoatPerf
    # jps -m
    3572 Jps -m
    4484 SoatPerf -Xms256m –Xmx1024m
    
Fig. 1) Paramètres destinés à la méthode main.

        # javaw -Xms256m -Xmx1024m com.soat.perf.SoatPerf
        # jps -m
        2264 SoatPerf
        2176 Jps -m
        # jps -v
        2264 SoatPerf -Xms256m -Xmx1024m
        320 Jps -Dapplication.home=/usr/lib/jvm/java-6-openjdk -Xms8m
        
Fig. 2) Paramètres destinés à la JVM.

Configuration Info

Avec jps, nous avons les paramètres fournis à l’initialisation de la JVM, mais nous n’avons pas les propriétés systèmes de la JVM. Pour ce faire, nous pouvons utiliser Configuration Info ( jinfo ) [2].

A noter que la version de jinfo doit être identique à la JVM, dans le cas contraire une exception de type VMVersionMismatchException sera levée.


        # javaw com.soat.perf.SoatPerf -Xms256m –Xmx1024m
        # jps
        3792 Jps
        4484 SoatPerf
        # jinfo 4484
        Attaching to process ID 4484, please wait...
        Debugger attached successfully.
        Server compiler detected.
        JVM version is 20.5-b03
        Java System Properties:
        java.runtime.name = Java(TM) SE Runtime Environment
        sun.boot.library.path = /usr/lib/jvm/java-6-openjdk/bin
        java.vm.version = 20.5-b03
        java.vm.vendor = Sun Microsystems Inc.
        java.vendor.url = https://java.sun.com/
        path.separator = ;
        java.vm.name = Java HotSpot(TM) 64-Bit Server VM
        (...)
        VM Flags:

        -Xms256m -Xmx1024m
        
Fig. 3)  Propriétés système et flags d’une JVM depuis l’id de son processus

Memory Map

Jusqu’à présent nous avons uniquement évoqué de possible erreurs/problèmes issus d’un passage de paramètres à la JVM lors son initialisation. Si aucune option n’est fournie à la JVM, lors de son lancement, la heap est initialisée avec des valeurs par défaut. Memory Map [3] permet de visualiser ses valeurs.


        # javaw com.soat.perf.SoatPerf
        # jps
        3792 Jps
        4484 SoatPerf
        # jmap -heap 4484
        Attaching to process ID 4484, please wait...
        Debugger attached successfully.
        Server compiler detected.
        JVM version is 20.5-b03
        using thread-local object allocation.
        Parallel GC with 4 thread(s)
        Heap Configuration:
        MinHeapFreeRatio = 40
        MaxHeapFreeRatio = 70
        MaxHeapSize = 1342177280 (1280.0MB)
        NewSize = 1310720 (1.25MB)
        MaxNewSize = 17592186044415 MB
        OldSize = 5439488 (5.1875MB)
        NewRatio = 2
        SurvivorRatio = 8
        PermSize = 21757952 (20.75MB)
        MaxPermSize = 85983232 (82.0MB)
        (...)
        
Fig. 4)  Informations sur la heap.

A noter que la valeur de MaxNewSize est erronée. Pour plus de détails, voir https://bugs.sun.com/bugdatabase/view_bug.do;jsessionid=bc3bd27567a528d86286ee4991b?bug_id=6792386

Les valeurs les plus importantes sont la taille maximale de la heap et la taille maximum de l’espace mémoire « Permanent Generation ». Hormis dans des cas très particuliers, il est déconseillé de changer les différents ratios.

Options de la JVM liées aux performances

Outre des problèmes de performances, trois problèmes courants peuvent provoquer des crashes de la JVM (OutOfMemoryError: Java heap space, OutOfMemoryError: PermGen space et StackOverflowException). Si vous considérez que votre implémentation est correcte et que certains espaces mémoires aient besoin d’être augmentés, les options suivantes vous y aideront.

-Xms and -Xmx

Ces paramètres sont utilisés pour définir la taille de la heap. -Xms définit la taille initiale de la heap et -Xmx définit la taille maximale de la heap.

Dans le cadre de serveurs d’applications, il est recommandé d’avoir les mêmes valeurs pour ces deux paramètres. Cela indiquera à la JVM de créer une heap initialement égale à sa taille maximale, ce qui évitera de multiple « full garbage collections » avec de multiple redimensionnement de la heap.

-XX:PermSize et -XX:MaxPermSize

Ces paramètres sont utilisés pour définir la taille de l’espace mémoire « Permanent generation ». -XX:PermSize définit la valeur initiale et   -XX:MaxPermSize la valeur maximale.

De même que pour la taille de la heap, dans le cadre de serveurs d’applications, ces deux paramètres doivent être identiques et ceci pour les mêmes raisons.

A noter que si l’espace mémoire PermGen est rempli (quel que soit l’espace mémoire de la heap disponible), la JVM tentera d’effectuer un « full Garbage Collection » pour récupérer de la mémoire. Ceci peut être source de problèmes pour des applications créant ou chargeant de nombreuses classes.  Une taille adaptée de -XX:PermSize et -XX:MaxPermSize permettra d’éviter ce genre de problèmes.

-Xss

Ce paramètre détermine la taille de la stack de chaque thread de la JVM.

Bien que la valeur dépende des besoins, dans la plupart des cas, la valeur par défaut est trop grande. Généralement une taille de  128k est suffisante. Dans le cas contraire une exception de type StackOverFlow sera levée.

Diminuer la taille des stacks permet d’augmenter de façon conséquente le nombre de thread d’une application.

-server

Ce paramètre permet de sélectionner la JVM en mode serveur. Cela indiquera à la VM qu’elle s’exécute sur un environnement de type serveur, les différentes configurations par défaut seront alors adaptées en conséquence.

A noter que cette option est utile uniquement dans un environnement de Windows 32 bits, pour les autres environnements, il s’agit de la valeur par défaut. De plus, il n’existe pas de version -client pour les versions 64 bits.

Pour plus de détails sur les options de la JVM, vous pouvez consulter l’article Java HotSpot VM Options.

L’impact du Système d’Exploitation

Le dernier point à prendre en compte lors de l’analyse des performances à un bas niveau est l’impact du Système d’Exploitation.

Les Processus Système

La JVM est une application comme une autre, suivant les mêmes règles que toutes les autres.

De ce fait, plus il y a de processus actifs (d’applications en cours d’exécution), plus les performances de la JVM et donc de notre application est impactée.

Par conséquent, pour limiter les impacts externes, il est conseillé, tant que possible, de limiter le nombre d’applications lancées en parallèles.

Swap mémoire

Le swapping est le résultat deux événements plus ou moins distincts :

  • Bien que la taille de la RAM soit supérieure à l’espace mémoire demandée par la JVM lors de son initialisation, le fait qu’il y ait plusieurs processus actifs empêche le Système d’Exploitation de lui accorder la totalité de cet espace
  • La JVM demande un espace mémoire (au travers des paramètres d’initialisation) supérieur à la taille de la RAM

Pour une application Java, le swapping est une catastrophe en terme de performances, et ceci en raison du fonctionnement du Garbage Collector.

Un “Full Garbage Collection” passe sur tout l’espace mémoire utilisé par la heap. Si une partie de la heap est swappée, pour être Garbage collectée elle doit être rechargée en mémoire.

Par conséquent, plus la taille du swap est importante et moins l’espace mémoire physique l’est, plus les performances seront impactées, puisque pour effectuer un “full Garbage Collection” de multiple swap/rechargement mémoire seront nécessaires.

Tous les Systèmes d’Exploitation de type Unix ont divers outils pour monitorer les processus et l’utilisation de la mémoire. Les plus communément utilisés sont vmstat [4] et top [5].

Ressources

[1] https://docs.oracle.com/javase/7/docs/technotes/tools/share/jps.html

[2] https://docs.oracle.com/javase/7/docs/technotes/tools/share/jinfo.html

[3] https://docs.oracle.com/javase/7/docs/technotes/tools/share/jmap.html

[4] https://unixhelp.ed.ac.uk/CGI/man-cgi?vmstat

[5] https://unixhelp.ed.ac.uk/CGI/man-cgi?top