Bonjour à tous! Nous voulons faire coïncider la traduction du matériel d'aujourd'hui avec le lancement d'un nouveau fil dans le cours
Java Developer , qui commence demain. Eh bien, commençons.
La JVM peut être une bête complexe. Heureusement, la majeure partie de cette complexité est cachée sous le capot, et nous, en tant que développeurs d'applications et responsables du déploiement, n'avons souvent pas à nous en préoccuper. Bien qu'en raison de la popularité croissante de la technologie pour le déploiement d'applications dans des conteneurs, il convient de prêter attention à l'allocation de mémoire dans la JVM.
Deux types de mémoireLa JVM divise la mémoire en deux catégories principales: tas et non-tas. Un tas est un morceau de mémoire JVM que les développeurs connaissent le mieux. Les objets créés par l'application sont stockés ici. Ils y restent jusqu'à ce qu'ils soient retirés par le ramasse-miettes. En règle générale, la taille de segment de mémoire que l'application utilise varie en fonction de la charge actuelle.
La mémoire hors segment est divisée en plusieurs zones. Dans HotSpot, vous pouvez utiliser le mécanisme de
suivi de la mémoire native (NMT) pour explorer les zones de cette mémoire. Veuillez noter que même si NMT ne suit pas l'utilisation de toute la mémoire native (
par exemple, il ne suit pas l'allocation de mémoire native par du code tiers ), ses capacités sont suffisantes pour la plupart des applications Spring typiques. Pour utiliser NMT, exécutez l'application avec l'
-XX:NativeMemoryTracking=summary
et en utilisant
jcmd VM.native_memory résumé voir les informations sur la mémoire utilisée.
Prenons l'exemple de la NMT de notre vieil ami Petclinic . Le diagramme ci-dessous montre l'utilisation de la mémoire JVM en fonction des données NMT (moins sa propre surcharge NMT) lors du démarrage de Petclinic avec une taille de segment de mémoire maximale de 48 Mo (
-Xmx48M
):

Comme vous pouvez le voir, la mémoire en dehors du segment représente la majeure partie de la mémoire JVM utilisée et la mémoire du segment ne représente qu'un sixième du total. Dans ce cas, il s'agit d'environ 44 Mo (dont 33 Mo ont été utilisés immédiatement après la collecte des ordures). L'utilisation de la mémoire hors segment a totalisé 223 Mo.
Zones de mémoire nativeEspace de classe compressé : utilisé pour stocker des informations sur les classes chargées. Limité au paramètre
MaxMetaspaceSize
. Une fonction du nombre de classes qui ont été chargées.
Note du traducteurPour une raison quelconque, l'auteur écrit sur l'espace de classe compressé, et non sur la zone de classe entière. La zone d'espace de classe compressé fait partie de la zone de classe et le paramètre MaxMetaspaceSize
limite la taille de la zone de classe entière, pas seulement l'espace de classe compressé. Pour limiter «l'espace de classe compressé», le paramètre CompressedClassSpaceSize
est utilisé.
D'ici :
Si UseCompressedOops
est activé et UseCompressedClassesPointers
est utilisé, deux zones logiquement différentes de mémoire native sont utilisées pour les métadonnées de classe ...
Une région est allouée à ces pointeurs de classe compressés (les décalages 32 bits). La taille de la région peut être définie avec CompressedClassSpaceSize
et est de 1 gigaoctet (Go) par défaut ...
MaxMetaspaceSize
s'applique à la somme de l'espace de classe compressé engagé et de l'espace pour les autres métadonnées de classe
Si le paramètre UseCompressedOops
est UseCompressedOops
et UseCompressedOops
utilisé, deux zones logiquement différentes de mémoire native sont utilisées pour les métadonnées de classe ...
Pour les pointeurs compressés, une zone mémoire est allouée (décalages 32 bits). La taille de cette zone peut être définie par CompressedClassSpaceSize
et par défaut elle est de 1 Go ...
Le paramètre MaxMetaspaceSize
fait référence à la somme de la zone du pointeur compressé et de la zone des autres métadonnées de classe.
- Thread: La mémoire utilisée par les threads de la JVM. La fonction du nombre de threads en cours d'exécution.
- Cache de code: la mémoire utilisée par JIT pour l'exécuter. Une fonction du nombre de classes qui ont été chargées. Limité à
ReservedCodeCacheSize
. Vous pouvez réduire le paramètre de JIT, par exemple, en désactivant la compilation à plusieurs niveaux. - GC (garbage collector): stocke les données utilisées par le garbage collector. Dépend du ramasse-miettes utilisé.
- Symbole: stocke des caractères tels que les noms de champ, les signatures de méthode et les chaînes internes. Une utilisation excessive de la mémoire de caractères peut indiquer que les lignes sont trop internes.
- Interne: stocke d'autres données internes qui ne sont incluses dans aucune des autres zones.
Les différencesPar rapport à un tas, la mémoire hors tas change moins sous charge. Dès que l'application charge toutes les classes qui seront utilisées et que le JIT est complètement réchauffé, tout passe dans un état stable. Pour voir une diminution de l'utilisation de l'
espace de classe compressé , le chargeur de classe qui a chargé les classes doit être supprimé par le garbage collector. Cela était courant dans le passé lorsque les applications étaient déployées dans des conteneurs de servlets ou des serveurs d'applications (le chargeur de classe d'application a été supprimé par le garbage collector lorsque l'application a été supprimée du serveur d'applications), mais cela se produit rarement avec les approches modernes du déploiement d'applications.
Configurer la JVMIl n'est pas facile de configurer la JVM pour utiliser efficacement la RAM disponible. Si vous exécutez la machine
-Xmx16M
avec le paramètre
-Xmx16M
et n'attendez pas plus de 16 Mo de mémoire à utiliser, vous obtiendrez une surprise désagréable.
Un domaine intéressant de la mémoire JVM est le cache de code JIT. Par défaut, HotSpot JVM utilise jusqu'à 240 Mo. Si le cache de code est trop petit, le JIT peut ne pas avoir suffisamment d'espace pour stocker ses données et, par conséquent, les performances seront réduites. Si le cache est trop volumineux, la mémoire peut être gaspillée. Lors de la détermination de la taille d'un cache, il est important de prendre en compte son effet sur l'utilisation de la mémoire et les performances.
Lors de l'exécution dans un conteneur Docker, les dernières versions de Java sont
désormais conscientes des limites de mémoire du conteneur et tentent de redimensionner la mémoire JVM en conséquence. Malheureusement, beaucoup de mémoire est souvent allouée en dehors du tas et pas assez dans le tas. Supposons que vous ayez une application exécutée dans un conteneur avec 2 processeurs et 512 Mo de mémoire disponible. Vous souhaitez gérer plus de charge de travail et augmenter le nombre de processeurs à 4 et la mémoire à 1 Go. Comme nous l'avons vu ci-dessus, la taille du tas varie généralement avec la charge et la mémoire en dehors du tas change beaucoup moins. Par conséquent, nous nous attendons à ce que la plupart des 512 Mo supplémentaires soient alloués au tas pour gérer la charge accrue. Malheureusement, par défaut, la JVM ne le fera pas et répartira la mémoire supplémentaire plus ou moins également entre la mémoire du tas et hors du tas.
Heureusement, l'équipe CloudFoundry possède une connaissance approfondie de l'allocation de mémoire dans la JVM. Si vous téléchargez des applications sur CloudFoundry, le pack de construction vous appliquera automatiquement ces connaissances. Si vous n'utilisez pas CloudFoudry ou souhaitez en savoir plus sur la configuration de JVM, il est recommandé de lire la
description de la troisième version du
calculateur de mémoire de
Java buildpack .
Qu'est-ce que cela signifie pour le printempsL'équipe de Spring passe beaucoup de temps à réfléchir aux performances et à l'utilisation de la mémoire, considérant la possibilité d'utiliser la mémoire sur le tas et hors du tas. Une façon de limiter l'utilisation de la mémoire en dehors du tas consiste à rendre les parties du cadre aussi polyvalentes que possible. Un exemple de cela est l'utilisation de Reflection pour créer et injecter des dépendances dans les beans de votre application. Grâce à l'utilisation de Reflection, la quantité de code d'infrastructure que vous utilisez reste constante, quel que soit le nombre de beans dans votre application. Pour optimiser le temps de démarrage, nous utilisons le cache sur le tas, effaçant ce cache une fois le lancement terminé. La mémoire de tas peut être facilement nettoyée par le garbage collector pour fournir plus de mémoire disponible à votre application.
Traditionnellement, nous attendons vos commentaires sur le matériel.