Partage de technologie

Lié à la JVM

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

Architecture JDK

Fonctionnalités multiplateformes du langage Java

Structure globale de la JVM et modèle de mémoire

Paramètres de mémoire JVM

Le format de paramétrage JVM du programme Spring Boot (le démarrage de Tomcat est directement ajouté au fichier catalina.sh dans le répertoire bin) :

java -Xms2048M -Xmx2048M -Xmn1024M -Xss512K -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -jar microservice-eureka-server.jar

en conclusion:

Plus le paramètre -Xss est petit, plus la valeur du nombre est petite, ce qui signifie que moins de frames de pile peuvent être alloués dans une pile de threads, mais le nombre de threads pouvant être ouverts sera plus élevé pour la JVM dans son ensemble.

-Xss : taille de pile de chaque thread, par défaut 1M

-Xms : définit la taille initiale disponible du tas, la valeur par défaut est 1/64 de la mémoire physique

-Xmx : définit la taille maximale disponible du tas, la valeur par défaut est 1/4 de la mémoire physique

-Xmn : taille nouvelle génération

-XX:NewRatio : La valeur par défaut 2 signifie que la nouvelle génération représente la moitié de l'ancienne génération et 1/3 de la totalité de la mémoire du tas.

-XX:SurvivorRatio : La valeur par défaut 8 signifie qu'une zone survivant occupe 1/8 de la mémoire Eden, soit 1/10 de la mémoire nouvelle génération.

Il existe deux paramètres JVM concernant le métaespace : -XX:MetaspaceSize=N et -XX:MaxMetaspaceSize=N

-XX : MaxMetaspaceSize : définit la taille maximale du métaespace. La valeur par défaut est -1, ce qui signifie qu'il n'y a pas de limite ou qu'elle est uniquement limitée par la taille de la mémoire locale.

-XX : MetaspaceSize : spécifiez le seuil initial pour que le métaespace déclenche Fullgc (il n'y a pas de taille initiale fixe pour le métaespace), en octets. La valeur par défaut est d'environ 21 Mo. Lorsque cette valeur est atteinte, le gc complet sera déclenché pour le déchargement de type, et. le collecteur ajustera cette valeur : si une grande quantité d'espace est libérée, réduisez la valeur de manière appropriée ; si une petite quantité d'espace est libérée, augmentez la valeur de manière appropriée sans dépasser -XX:MaxMetaspaceSize (si défini). Cela a une signification différente de celle du paramètre -XX:PermSize dans les versions antérieures de jdk. -XX:PermSize représente la capacité initiale de la génération permanente.

Étant donné que le redimensionnement du métaespace nécessite un Full GC, il s'agit d'une opération très coûteuse. Si un grand nombre de Full GC se produit au démarrage de l'application, cela est généralement dû au redimensionnement de la génération permanente ou du métaespace. Il est généralement recommandé de définir dans les paramètres JVM MetaspaceSize et MaxMetaspaceSize sur la même valeur et de les définir à une valeur supérieure à la valeur initiale. Pour une machine avec 8 Go de mémoire physique, je définis généralement les deux valeurs sur 256 Mo.

Création d'objets

En-tête d'objet

Dans la machine virtuelle HotSpot, la disposition des objets stockés en mémoire peut être divisée en trois zones : l'en-tête de l'objet (Header), les données d'instance (Instance Data) et le remplissage d'alignement (Padding). L'en-tête de l'objet de la machine virtuelle HotSpot comprend deux parties d'informations. La première partie est utilisée pour stocker les données d'exécution de l'objet lui-même, telles que le code de hachage (HashCode), l'âge de génération du GC, l'indicateur d'état du verrouillage, le verrou détenu par le thread. , ID de fil de discussion biaisé, horodatages privilégiés, etc. L'autre partie de l'en-tête de l'objet est le pointeur de type, qui est le pointeur de l'objet vers ses métadonnées de classe. La machine virtuelle utilise ce pointeur pour déterminer de quelle classe l'objet est une instance.

En-tête d'objet 32 ​​bits

En-tête d'objet 64 bits

compression du pointeur

1. L'utilisation de pointeurs 32 bits (le stockage réel utilise 64 bits) dans HotSpot sur une plate-forme 64 bits utilisera environ 1,5 fois plus de mémoire. L'utilisation de pointeurs plus grands pour déplacer des données entre la mémoire principale et le cache occupe une plus grande bande passante. dans le même temps, GC sera également soumis à une plus grande pression

2. Afin de réduire la consommation de mémoire sur les plates-formes 64 bits, activez la fonction de compression du pointeur

3. Dans JVM, les adresses 32 bits prennent en charge jusqu'à 4 Go de mémoire (2 puissance 32), qui peut être optimisée en compressant et en codant le pointeur d'objet lorsqu'il est stocké dans la mémoire tas, et en le décodant après l'avoir retiré. au registre du processeur (le pointeur d'objet est dans le tas. Il est de 32 bits, 35 bits dans le registre, 2 puissance 35 = 32 Go), permettant à la JVM de prendre en charge des configurations de mémoire plus grandes (inférieures ou égales à 32 Go) en utilisant uniquement adresses 32 bits

4. Lorsque la mémoire du tas est inférieure à 4G, il n'est pas nécessaire d'activer la compression du pointeur. JVM supprimera directement l'adresse haute de 32 bits, c'est-à-dire utilisera l'espace d'adressage virtuel faible.

5. Lorsque la mémoire du tas est supérieure à 32 Go, le pointeur de compression deviendra invalide et 64 bits (c'est-à-dire 8 octets) seront forcés d'adresser l'objet Java. Cela provoquera le problème de 1, il est donc préférable de ne pas en avoir. la mémoire tas supérieure à 32G.

Analyse d'échappement et remplacement du titre de l'allocation sur la pile d'objets

Dans ce cas, la JVM peut optimiser l'emplacement d'allocation de mémoire de l'objet en activant le paramètre d'analyse d'échappement (-XX:+DoEscapeAnalysis), de sorte qu'il soit d'abord alloué sur la pile via un remplacement scalaire (l'allocation sur la pile est une analyse d'échappement). activé par défaut après JDK7. Si vous souhaitez désactiver l'utilisation des paramètres (-XX:-DoEscapeAnalysis)

Remplacement scalaire : lorsqu'il est déterminé par l'analyse d'échappement que l'objet ne sera pas accessible de l'extérieur et que l'objet peut être décomposé davantage, la JVM ne créera pas l'objet, mais décomposera les variables membres de l'objet en plusieurs variables membres utilisées par cette méthode et remplacez-les. Ces variables membres de remplacement allouent de l'espace sur le cadre de pile ou le registre, de sorte qu'il n'y ait pas d'allocation de mémoire insuffisante pour l'objet car il n'y a pas un grand espace contigu. Activez les paramètres de remplacement scalaire (-XX:+EliminateAllocations), qui sont activés par défaut après JDK7.

Quantités scalaires et agrégées : un scalaire est une quantité qui ne peut pas être décomposée davantage, et le type de données de base de JAVA est un scalaire (comme int, long et autres types de données de base et types de référence, etc.). est une quantité qui peut être davantage décomposée, et cette quantité est appelée quantité de polymérisation. En JAVA, les objets sont des agrégats qui peuvent être décomposés davantage.

Les gros objets entrent directement dans l’ancienne génération

Les objets volumineux sont des objets qui nécessitent une grande quantité d'espace mémoire continu (tels que des chaînes et des tableaux). Le paramètre JVM -XX:PretenureSizeThreshold peut définir la taille des objets volumineux. Si l'objet dépasse la taille définie, il entrera directement dans l'ancienne génération et n'entrera pas dans la jeune génération. Ce paramètre n'est valable que sous les deux collecteurs Serial et ParNew.

Par exemple, définissez les paramètres JVM : -XX:PretenureSizeThreshold=1000000 (l'unité est en octets) -XX:+UseSerialGC Si vous exécutez le premier programme ci-dessus, vous constaterez que le gros objet entre directement dans l'ancienne génération.

Pourquoi faut-il qu'il en soit ainsi ?

Afin d'éviter les opérations de copie lors de l'allocation de mémoire pour des objets volumineux et de réduire l'efficacité.

Jugement d'âge dynamique d'objet

Dans la zone Survivant où est actuellement placé l'objet (une des zones, la zone s où est placé l'objet), la taille totale d'un lot d'objets est supérieure à 50% de la taille mémoire de cette zone Survivant (-XX :TargetSurvivorRatio peut être spécifié), alors à ce moment il est supérieur à Les objets égaux à l'âge maximum de ce lot d'objets peuvent entrer directement dans l'âge ancien. Par exemple, il y a un lot d'objets dans la zone Survivant. la somme de plusieurs objets d'âge avec l'âge 1 + l'âge 2 + l'âge n dépasse 50 % de la zone Survivant. À ce moment, tous les objets avec l'âge n (inclus) et plus seront placés dans l'ancienne génération. Cette règle espère en fait que les objets qui peuvent survivre longtemps entreront dans la vieillesse le plus tôt possible. Le mécanisme de jugement dynamique de l'âge de l'objet est généralement déclenché après un gc mineur.

Mécanisme de garantie d’attribution des espaces ancienne génération

Avant chaque gc mineur dans la jeune génération, la JVM calculera l'espace libre restant dans l'ancienne génération.

Si l'espace disponible est inférieur à la somme des tailles de tous les objets existants dans la jeune génération (y compris les objets poubelles)

Il vérifiera si le paramètre "-XX:-HandlePromotionFailure" (défini par défaut dans jdk1.8) est défini.

Si ce paramètre est présent, il vérifiera si la taille de la mémoire disponible dans l'ancienne génération est supérieure à la taille moyenne des objets entrés dans l'ancienne génération après chaque gc mineur précédent.

Si le résultat de l'étape précédente est inférieur ou si les paramètres mentionnés précédemment ne sont pas définis, alors un gc complet sera déclenché et l'ancienne génération et la jeune génération seront collectées ensemble s'il n'y a toujours pas assez d'espace pour stocker la nouvelle. objets après le recyclage, "MOO" se produira.

Bien entendu, si la taille des objets survivants restants qui doivent être déplacés vers l'ancienne génération après le gc mineur est toujours supérieure à l'espace disponible dans l'ancienne génération, le gc complet sera également déclenché après le gc complet, le cas échéant. il n'y a toujours pas d'espace pour les objets survivants après le gc mineur, un "MOO" se produira également

Algorithme d'analyse d'accessibilité

Analyser le mécanisme de chargement des classes JVM à partir du niveau du code source du JDK

Processus de chargement de classe

Chargeurs de classes et mécanisme de délégation parentale

Pourquoi concevoir une délégation parentale ?

Explication détaillée du chargeur personnalisé Tomcat

Plusieurs chargeurs de classes principales de Tomcat :

commonLoader : le chargeur de classes le plus basique de Tomcat. Les classes du chemin de chargement sont accessibles par le conteneur Tomcat lui-même et par chaque application Web ;

catalinaLoader : Le chargeur de classes privé du conteneur Tomcat. Les classes dans le chemin de chargement ne sont pas visibles par la Webapp ;

sharedLoader : un chargeur de classe partagé par chaque application Web. Les classes dans le chemin de chargement sont visibles par toutes les applications Web, mais pas par le conteneur Tomcat ;

WebappClassLoader : chargeur de classe privé de chaque application Web. Les classes dans le chemin de chargement ne sont visibles que par l'application Web actuelle, comme le chargement de classes associées dans un package war. Chaque application de package war possède son propre WebappClassLoader pour obtenir une isolation mutuelle, comme un package war différent. applications. Différentes versions de ressort sont introduites afin que l'implémentation puisse charger leurs versions de ressort respectives ;

modèle de mémoire

Algorithme de collecte des déchets

Éboueur

Implémentation d'un algorithme sous-jacent pour le garbage collection