8 novembre 2013

Allocation mémoire des applications Java

Ci dessous un résumé de ce que j'ai glané sur les mécanismes d'allocation mémoire des applications Java. Une des difficulté est d'intégrer les variations du vocabulaire. Ce n'est pas la seule ...

Le Heap

C'est le "tas" de mémoire alloué à chaque processus Java (Dans le cas de HR Access on trouvera un processus Java pour OpenHR, un pour HRQuery, un pour Tomcat). La taille maximale allouable est fonction du système (Sun, Red Hat, Windows ... / 32, 64 bits). Des paramètres de la ligne de commande permettent notamment de préciser pour chaque instance Java une taille de Heap au démarrage et un maximum à ne pas dépasser.

Le Heap est segmenté en plusieurs zones, dites "Générations". Ceci permet de gérer les objets éphémères (iterateurs, variables locales) et de pérenniser des objets persistants :
 
- Zone dite "New" ou "Young Generation" ou "nursery", sous divisée en :
  • Eden (le paradis, là ou sont placés les objets nouvellement créés)
  • Survivor 1 & 2 (les objets survivant aux premiers passages du Garbage Collector - cf plus bas)
  • Virtually reserved (espace libre)
- Zone dite "Old Generation" ou "Tenured", sous divisée en :
  • Tenured (les objets "titularisés")
  • Virtually reserved (espace libre)
Pour être complet, s'ajoute au Heap une zone mémoire dite "Permanent Génération" pour les données de la machine virtuelle elle-même (classes et méthodes). 

http://www.jmdoudoux.fr/java/dej/chap-gestion_memoire.htm
 


Le Garbage Collector

Ou "ramasse miette". C'est le mécanisme de récupération de mémoire. Les "Garbage Collections" (déclenchements du Garbage Collector) partielles ("partial" ou "minor") ne traitent que la zone "New Generation". Les "GC" complètes ("full") traitent toutes les zones dès que la "Old" ou la "Permanent" sont remplies.

Par défaut, la machine virtuelle Java augmente ou diminue le "Heap" à chaque "GC" pour essayer de garder dans chaque zone la proportion prévue d'espace libre.



Le passage trop fréquent des "GC" (sous-dimensionnement mémoire ou mauvaise répartition) peu coûter cher en terme de performances.



Paramètres

En fonction de l'analyse de la consommation mémoire de l'application et des ressources disponibles sur la machine, certains de ces paramètres peuvent êtres utiles (il s'agit ici de paramètres de la JVM Sun - certains ne sont disponibles qu'au dela d'une certaine version. Pour une JVM IBM se reporter à la documentation de l'éditeur).

Sources :
http://docs.oracle.com/cd/E19900-01/819-4742/abeii/index.html
http://www.oracle.com/technetwork/java/tuning-139912.html#section4.1.2
http://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html

Pour analyser

-verbose:gc
Provoque l'affichage d'informations lors des GC (ici deux partielles et une complète) :
[GC 325407K->83000K(776768K), 0.2300771 secs]
[GC 325816K->83372K(776768K), 0.2454258 secs]
[Full GC 267628K->83769K(776768K), 1.8479984 secs]


-XX:+PrintGCDetails
sera plus détaillé (ici une GC partielles a réduit la taille de la "New"/"Young Generation" de 64 à 1Mo, le Heap de 196 à 133Mo en 45ms) : 
[GC [DefNew: 64575K->959K(64576K), 0.0457646 secs] 196016K->133633K(261184K), 0.0459067 secs]

-XX:+PrintGCTimeStamps
affichera en plus des "timestamps" (ici la GC est déclenchée 111 secondes après le démarrage de l'application)
111.042: [GC 111.042: [DefNew: 8128K->8128K(8128K), 0.0000505 secs]111.042: [Tenured: 18154K->2311K(24576K), 0.1290354 secs] 26282K->2311K(32704K), 0.1293306 secs]

-XX:+PrintTenuringDistribution
Pour observer la distribution des durées de vie des objets de l'application.

-Xloggc:filename [ -XX:-UseGCLogFileRotation -XX:NumberOfGClogFiles=5 -XX:GCLogFileSize=8K ]
Les statistiques du GC sont écrites dans le(s) fichier(s) indiqué(s).

A voir aussi :
  •  GC output examples
  • La commande jmap [-J-d64] {-heap|-histo|-permstat} Pid : Pour obtenir une cartographie de la mémoire utilisée par un processus Java.
  • La commande jstat {-gcutil|-gc|...} -t Pid interval(ms) [count] : Pour obtenir à chaud des statistiques de fonctionnement d'un processus Java.

Pour dimensionner

-Xms ... -Xmx ...
Tailles initiale et maximale du "Heap" (par défaut avec Java5, Xms=1/64e de la RAM, et Xmx=1/4 de la RAM plafonné à 1Go). L'espace défini par le Xmx est réservé. Si la valeur Xms est plus petite alors seule cette quantité est immédiatement disponible, le reste étant dit virtuel.
Si la taille initiale est trop petite, de nombreuses GC seront nécessaires pour agrandir le Heap et ralentiront le démarrage. Si la taille maximale est trop petite, de nombreuses GC ralentiront l'application. Une taille maximale trop grande au vu des ressources machine provoquera de la pagination.

-XX:NewRatio=  ... -XX:NewSize= ... -XX:MaxNewSize=
Ratio, tailles minimales et maximales de la "Young"/"New Generation". Plus la "Young Generation" est volumineuse, moins les GC partiels sont fréquents. Mais si cela se fait au dépend de la "Old Generation", des GC complètes seront plus fréquentes. NewRatio=3 signifie que le ratio entre "Young" et "Old" est de 1/3. -Xmn est un raccourci pour NewSize.

-XX:MaxPermSize
Taille maximale de la zone de "génération permanente". Une "Permanent Generation" trop petite provoquera des GC complètes.

-XX:MinHeapFreeRatio= ... -XX:MaxHeapFreeRatio
Proportions d'espace libre minimale et maximale dans les différentes zones. Quand c'est possible la JVM grossit ou réduit le Heap à chaque GC pour garder la proportion d'espace libre de chaque zone entre des valeurs et la taille totale (bornée par -Xms et -Xmx) - par défaut entre 40 et 70% (chiffres fonction du système, de la version 32/64bits).

-XX:SurvivorRatio
Pour gérer les deux Survivor (ce n'est en général pas nécessaire pour ce qui concerne les performances). Avec -XX:SurvivorRatio=6 la New Generation est découpée en (Eden=6/8)+2x(Survivor=1/8).
Si les  Survivor sont trop petits, les GC écriront directement dans la Tenured Generation. S'ils sont trop gros, de l'espace est gaspillé.

-Xss
Taille de la pile (stack) de chaque thread. Si celle-ci est trop petite, une exception de type StackOverFlowError est levée. Exemple : -Xss1024k


http://www.oracle.com/technetwork/java/javase/gc-tuning-6-140523.html
http://javadecodedquestions.blogspot.fr/2012/12/java-memory-management.html
 



Par défaut, la taille des zones du Heap augmentent de 20% ou se réduisent de 5% par GC. Ces valeurs peuvent être modifiées en utilisant :
  • -XX:YoungGenerationSizeIncrement : Pourcentage d'augmentation de la "Young Generation"
  • -XX:TenuredGenerationSizeIncrem:ent  Pourcentage d'augmentation de la "Tenured"/"Old Generation"
  • -XX:AdaptiveSizeDecrementScaleFactor : Pourcentage de décroissance des zones 

Pour agir sur le fonctionnement du GC

-XX:+UseSerialGC
Serial Collector : plutôt pour les applications clientes peu gourmandes en mémoire (100 Mo) ou machines mono-processeurs.

-XX:+UseParallelGC
Parallel Collector : mieux adapté pour les heap de plus de un Go et les machines multiprocesseurs

-XX:+UseParallelOldGC
Parallel Compacting Collector : limite la fragmentation de la "Tenured" et réduit les temps de pause de l'application. Pour les machines multiprocesseurs.

-XX:ParallelGCThreads=n
Nombre de threads pour les GC parallélisés

-XX:+UseConcMarkSweepGC
Concurrent Mark Sweep Collector : Pour des temps de pause le plus court possible acceptant d'avoir les GC en concurrence avec l'application. Il faut une "Old Generation" de taille importante - une machine multiprocesseurs (sinon se renseigner sur le mode "incrémental). En général des serveurs ou conteneurs web.

A noter : s'il n'est pas imposé - et si les options "-server" ou "-client" ne sont pas précisées - le mode de fonctionnement du Garbage Collecteur sera choisi par la JVM en fonction des caractéristiques de la machine (pour Java 5 : une machine mono/multi-processseurs 32/64b avec une mémoire < ou > 2 Go fera choisir entre Serial et Parallel).

-XX:+DisableExplicitGC
pour désactiver les éventuels GC explicitement demandés par les applications

-Dsun.rmi.dgc.client.gcInterval=3600000
Intervalle souhaité entre deux GC (ici de une fois par heure au lieu de l'intervalle par défaut de une fois par minute).

-XX:MaxGCPauseMillis=n en millisecondes.
Durée maximale souhaitée de pause provoquée par GC.

-XX:GCTimeRatio=n
Ratio souhaité entre le temps des GC et le temps dédié à l'application selon la formule 1 / ( 1 + n ), par défaut 99.


1 commentaire:

  1. Pour complèter l'article, il est possible de visualiser en temps réel l'utilisation de la mémoire de chacun des services Tomcat/OpenHR/Query.
    L'une des méthodes est de mettre en place des paramètres JMX au niveau des paramètres de lancement de ces services :
    -Dcom.sun.management.jmxremote //Pour mise en oeuvre de JMX sur J2SE
    -Dcom.sun.management.jmxremote.port=portNum //Pour ouvrir un port de connexion JMX
    -Dcom.sun.management.jmxremote.authenticate=false //Pour supprimer l'authentification
    -Dcom.sun.management.jmxremote.ssl=false //Pour désactiver l'autentification SSL

    CATALINA_OPTS=" -Dcom.sun.management.jmxremote.port=1234 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=12.123.123.123 "
    Puis de se connceter à cette adresse IP via ce port avec un outil comme VisualVM.

    Nous obtenons alors en temps réel les courbes d'utilisation, le déclanchement du Garbage Collector et les paramètres Xmx et Xms en cours d'utilisation.

    RépondreSupprimer