5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

Préface

Cet article se concentrera sur l'analyse de jvm, et le contenu impliqué comprend le modèle de mémoire jvm, le chargeur de classe, l'algorithme de récupération GC, le dispositif de récupération GC et le biais global est théorique. Cet article ne convient pas aux débutants. En raison de l'espace limité, l'éditeur triera plus de 400 pages de notes d'étude sur le réglage des performances de la JVM, et fera attention aux espèces publiques Hao: Kylin change les bogues, partagez-le avec tout le monde, adapté à plus de 3 ans d'expérience en développement Le staff technique de, accueille tout le monde pour échanger et partager, s'il y a des lacunes dans l'article, accueille les lecteurs et amis à signaler, merci d'avance.

Une relation claire entre jdk, jre et jvm

L'image suivante est le site officiel du diagramme d'architecture jdk, jre et jvm, à partir du diagramme d'architecture, il est facile de voir la relation entre les trois:

(1) jdk contient jre et jre contient jvm

(2) JDK est principalement utilisé dans l'environnement de développement, et jre est principalement utilisé dans l'environnement de publication. Bien sûr, il est acceptable d'utiliser JDK dans l'environnement de publication, mais les performances peuvent être un peu affectées. La relation entre jdk et jre est quelque peu similaire à la relation entre la version de débogage et la version de publication du programme

(3) En termes de taille de fichier, jdk est plus grand que jre. Comme on peut le voir sur la figure, jdk a une boîte à outils de plus que jre, comme javac couramment utilisé, les commandes java, etc.

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

Chargeur de seconde classe

À propos du chargeur de classe jvm, il peut être résumé comme suit:

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

1. Pourquoi y a-t-il un chargeur de classe?

(1) Chargez le fichier bytecode dans la zone de données d'exécution. Le fichier bytecode (.class) formé en compilant le code source .java via la commande Javac est chargé dans le jvm via le chargeur de classe.

(2) Déterminez l'unicité de la zone de données du fichier de code d'octet au moment de l'exécution. Le même fichier bytecode peut former différents fichiers via différents chargeurs de classes. Par conséquent, l'unicité de la zone de données du fichier bytecode au moment de l'exécution est déterminée par le fichier bytecode et le chargeur de classe qui le charge.

2. Types de chargeurs de classe

Divisés de la catégorie, les chargeurs de classe sont principalement divisés en quatre catégories

(1) Démarrez le chargeur de classe (le chargeur de classe racine Bootstrap ClassLoader): Ce chargeur de classe est situé au niveau supérieur du chargeur de classe, et charge principalement les packages jar liés au noyau JRE, tels que /jre/lib/rt.jar

(2) Extension ClassLoader: ce chargeur de classe est situé au deuxième niveau de la hiérarchie du chargeur de classe, et il charge principalement les packages jar liés à l'extension JRE, tels que /jre/lib/ext/*.jar

(3) Application ClassLoader App: ce chargeur de classe est situé dans la troisième couche du chargeur de classe, et il charge principalement les packages jar associés sous le classpath (classpaht)

(4) Chargeur de classe défini par l'utilisateur (User ClassLoader): Ce chargeur de classe est un chargeur de classe défini par l'utilisateur, qui charge principalement les packages JAR associés sous le chemin spécifié par l'utilisateur

3. Le mécanisme du chargeur de classe (délégation parentale)

Pour le chargement du bytecode, le mécanisme de chargement de classe est la délégation parent. Qu'est-ce que la délégation parent?

Une fois que le chargeur de classe a obtenu le fichier bytecode, il ne le charge pas directement, mais transmet le fichier bytecode à son chargeur de classe parent direct, et son chargeur parent direct continue de passer au chargement parent direct de son chargeur parent direct Loader, et ainsi de suite sur le chargeur parent racine, si le chargeur parent racine

S'il peut être chargé, puis chargez, sinon il sera chargé par son chargeur enfant direct. Si le chargeur enfant direct peut se charger, il sera chargé. Si ce n'est pas le cas, le chargeur de classe enfant direct sera suivi d'une analogie. Chargeur.

4. Comment implémenter le chargeur de classe dans jdk 1.8?

Ce qui suit est l'implémentation du chargeur de classe jdk 1.8, en utilisant le mode récursif

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

5. Détruisez le modèle de délégation parent

Dans certains cas, en raison de la limitation de la plage de chargement, le chargeur de classe parent ne peut pas charger le fichier requis, donc le chargeur de classe parent doit déléguer son chargeur de sous-classe pour charger le fichier bytecode correspondant.

Par exemple, l'interface du pilote de base de données Driver défini dans jdk, mais l'implémentation de cette interface est implémentée par différents fournisseurs de base de données, ce qui pose un tel problème: par la classe de démarrage (Bootstrap ClassLoader)

Le DriverManager exécuté doit charger les classes d'implémentation pertinentes qui implémentent l'interface Driver pour obtenir une gestion unifiée, mais Bootstrap ClassLoader ne peut charger que les fichiers correspondants sous jre / lib, pas

Classes d'implémentation liées à l'interface Dirver implémentées par divers fournisseurs (les classes d'implémentation Dirver sont chargées par Application ClassLoader). À l'heure actuelle, Bootstrap ClassLoader doit confier à son chargeur de sous-classes le chargement de Driver

Pour y parvenir, détruisant ainsi le modèle de délégation parentale.

Trois types de cycles de vie

Le cycle de vie des classes en java et jvm est à peu près divisé en cinq étapes:

1. Étape de chargement: obtenez le flux binaire bytecode, convertissez la structure de stockage statique en structure de données d'exécution de la zone de méthode et générez l'objet de classe correspondant (objet java.lang.Class) dans la zone de méthode en tant que données de la classe Accédez à l'entrée.

2. Phase de connexion: Cette phase comprend trois petites phases, à savoir la vérification, la préparation et l'analyse

(1) Vérification: assurez-vous que le fichier bytecode répond aux exigences de la spécification de la machine virtuelle, telles que la vérification des métadonnées, la vérification du format de fichier, la vérification du bytecode et la vérification des symboles, etc.

(2) Préparation: allouez de la mémoire à la table statique interne et définissez la valeur par défaut de jvm. Pour les variables non statiques, il n'est pas nécessaire d'allouer de la mémoire à ce stade.

(3) Analyse: convertir les références de symboles dans le pool de constantes en références directes

3. Phase d'initialisation: quelques travaux d'initialisation nécessaires avant l'utilisation de l'objet de classe

Ce qui suit, cité du point de vue d'un blogueur, pense personnellement que l'explication est très bonne.

En code Java, si nous voulons initialiser un champ statique, nous pouvons l'affecter directement lors de la déclaration, ou l'affecter dans un bloc de code statique.

À l'exception des constantes modifiées statiques finales, les opérations d'assignation directe et tous les codes des blocs de code statique seront placés dans la même méthode par le compilateur Java et nommés <clinit>. Le but de l'initialisation est de marquer comme

Affectation de champ de valeur constante et processus d'exécution de la méthode <clinit>. La machine virtuelle Java garantit que la méthode <clinit> de la classe n'est exécutée qu'une seule fois par verrouillage.

Dans quelles conditions l'initialisation de classe se produira-t-elle?

(1) Lorsque la machine virtuelle démarre, initialisez la classe principale (fonction principale) spécifiée par l'utilisateur;

(2) Lorsque vous rencontrez la nouvelle instruction pour créer une nouvelle instance de la classe cible, initialisez la classe cible de la nouvelle instruction;

(3) Lorsqu'une instruction pour appeler une méthode statique est rencontrée, initialisez la classe où se trouve la méthode statique;

(4) L'initialisation de la sous-classe déclenchera l'initialisation de la classe parente;

(5) Si une interface définit une méthode par défaut, l'initialisation de la classe qui implémente directement ou implémente indirectement l'interface déclenchera l'initialisation de l'interface;

(6) Lorsque vous utilisez l'API de réflexion pour faire un appel de réflexion à une classe, initialisez cette classe;

(7) Lorsque l'instance MethodHandle est appelée pour la première fois, initialisez la classe de la méthode pointée par MethodHandle.

4. Utiliser l'étape: utiliser des objets dans jvm

5. Étape de déchargement: Déchargement de l'objet du jvm (déchargement), quelles conditions feront le déchargement de la classe jvm?

(1) Le chargeur de classe qui a chargé la classe est recyclé

(2) Toutes les instances de cette classe ont été recyclées

(3) L'objet java.lang.Class correspondant à cette classe n'est référencé nulle part

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

Quatre modèles de mémoire jvm

1. Quel est le modèle de mémoire JVM?

Ce qui suit est un diagramme de l'architecture du modèle de mémoire JVM. Comme indiqué dans l'article précédent, je ne vais pas les discuter un par un ici, et expliquer principalement la zone de tas.

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

Avant jdk 1.8, la zone de tas était principalement divisée en jeune génération, ancienne génération et génération permanente. Après jdk 1.8, la génération permanente a été supprimée et la zone MetaSpace a été ajoutée. Ici, partagez principalement jdk 1.8.

Selon jdk1.8, la logique de la zone de tas est abstraite en trois parties:

(1) Nouvelle génération: y compris la zone Eden, la zone S0 (également appelée depuis la zone), S21 (également appelée zone TO)

(2) Vieillesse

(3) zone Metaspace

2. Quelle est la taille de la mémoire de la nouvelle génération et de l'ancienne génération?

Selon les recommandations officielles, la nouvelle génération en représente un tiers (Eden: S0: S1 = 8: 1: 1) et l'ancienne génération les deux tiers. Par conséquent, le diagramme d'allocation de mémoire est le suivant:

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

3. Comment fonctionne la récupération du CPG?

L'objet s'exécute d'abord dans la zone Eden. Lorsque la mémoire d'Eden est pleine, Eden effectuera deux opérations: récupérer les objets inutilisés et placer les objets non récupérés dans la zone s0. A ce moment, la zone s0 et la zone s1 échangent des noms, c'est-à-dire s0- > s1, s1-> s0, la zone Eden a été récupérée une fois, et l'espace est libéré. ​​Lorsque Eden est à nouveau plein la prochaine fois, les mêmes étapes sont exécutées et exécutées à tour de rôle. Lorsque la zone Eden est récupérée, les objets restants dépassent la capacité s0. Un GC mineur sera déclenché. À ce moment, les objets non récupérés seront placés dans l'ancienne zone et exécutés en boucle. Lorsque la zone Eden déclenche le GC mineur et que la capacité restante de l'objet est supérieure à la capacité restante de l'ancienne zone, l'ancienne zone déclenchera un GC majeur , Un GC complet sera déclenché à ce moment. Il est à noter qu'en général, le GC majeur sera accompagné d'une récupération complète du GC. Le GC complet est très gourmand en performances. Faites attention au réglage de la JVM.

L'image suivante est une photo GC prise par moi dans l'environnement de production, l'outil de surveillance VisualVM

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

4. Quels sont les algorithmes de récupération de place?

(1) Algorithme Mark-clear

L'algorithme se décompose en deux étapes, à savoir l'étape de marquage et l'étape de nettoyage: tout d'abord, tous les objets à recycler sont marqués, puis les objets marqués sont recyclés. Cet algorithme est inefficace et sujet à la fragmentation de la mémoire.

a. Faible efficacité: il faut parcourir la mémoire deux fois, marquer la première fois et recycler l'objet marqué la deuxième fois

b. Comme il s’agit d’un segment de mémoire non contigu, il est sujet à la fragmentation. Lorsque l’objet est trop volumineux, le GC complet est susceptible de se produire

La figure ci-dessous est un diagramme schématique de la comparaison de l'algorithme de balayage de marque avant et après la récupération

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

(2) Algorithme de marque-copie

Cet algorithme résout le problème de la faible efficacité de l'algorithme "marquer et balayer" et la majeure partie de la fragmentation de la mémoire. Il divise la mémoire en deux blocs de taille égale et n'utilise qu'un bloc à la fois. Lorsqu'un bloc doit être recyclé, seule la zone de bloc Les objets survivants sont copiés dans un autre bloc, puis le bloc de mémoire est nettoyé immédiatement et le cycle se répète.

La figure ci-dessous est un diagramme schématique de l'algorithme de marque-copie avant et après le recyclage

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

Cependant, comme la plupart des objets de la jeune génération ont un temps de séjour très court, 98% des objets sont rapidement recyclés, et il y a très peu d'objets survivants. Il n'est pas nécessaire de diviser la mémoire selon 1: 1, mais selon 8: 1: 1. Diviser,

Mettez 2% des objets survivants dans s0 (de la zone).

Voici un diagramme schématique de la division selon Eden: s0: s1 = 8: 1: 1

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

(3) Algorithme de tri par marquage

L'algorithme est divisé en deux étapes, le marquage et le tri: tout d'abord, tous les objets survivants sont marqués, ces objets sont déplacés vers une extrémité, puis la mémoire à l'extérieur de la limite d'extrémité est directement nettoyée. Puisque les objets dans la vieillesse vivent plus longtemps, cet algorithme convient.

Le processus de marquage est toujours le même que le processus de "marquage-balayage", mais les étapes suivantes ne consistent pas à nettoyer directement les objets recyclables, mais à déplacer tous les objets survivants à une extrémité, puis à nettoyer directement la mémoire en dehors de la limite d'extrémité.

Ce qui suit est un diagramme schématique de la période de récupération et de post-récupération de "l'algorithme de tri par marque"

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

(4) Algorithme de collecte générationnel

L'algorithme est l'algorithme JVM actuel, utilisant la pensée générationnelle, le modèle est le suivant:

5 conseils pour bien comprendre le modèle de mémoire JVM [pour le développement Java sur 3 ans]

5. Quels sont les collecteurs de GC courants?

(1) SerialGC

SerialGC est également appelé collecteur série, et c'est aussi le collecteur GC le plus basique. Il convient principalement aux processeurs monocœur. La nouvelle génération adopte l'algorithme de réplication et l'ancienne génération adopte l'algorithme de compression de marque. L'application doit être suspendue pendant le fonctionnement.

Par conséquent, cela entraînera des problèmes STW. Marquez le paramètre dans JVM comme: -XX: + UseSerialGC.

(2) ParallèleGC

ParallelGC est basé sur SerialGC et résout principalement le problème série de SerialGC. Il est changé en problème parallèle pour résoudre le problème de multi-threading, mais cela causera également un problème STW. Paramètres clés Jvm:

a.-XX: + UseParNewGC, ce qui signifie la nouvelle génération parallèle (algorithme de réplication) l'ancienne série (mark-compression)

b.XX: + UseParallelOldGC, la vieillesse est également parallèle

(3) CMS GC

CMSGC appartient au collecteur de vieillesse. Il adopte "l'algorithme de balayage de marque" et ne causera pas de problèmes de STW. Les réglages des paramètres dans jvm:

-XX: + UseConcMarkSweepGC, ce qui signifie que la vieillesse utilise le collecteur CMS

(4) Les ordures d'abord

Garbage First est orienté vers le garbage collector jvm. Il satisfait une courte pause tout en atteignant un débit élevé. Il convient aux processeurs multicœurs et aux grands serveurs de mémoire. Il est également le garbage collector par défaut de jdk9.

Cinq résumé

Analyse approfondie du modèle de mémoire JVM, qui se concentre sur la relation entre jdk, jre et jvm, chargeur de classe jvm, division de mémoire de tas jvm, collecteur GC et algorithme de recyclage GC, etc., le biais global est théorique, en raison de l'espace limité, l'éditeur correspond Une pratique de réglage des performances JVM avec plus de 400 pages de notes d'étude, faites attention aux espèces publiques Hao: Kylin change le bogue, partagez-le avec vous, cet article n'analyse pas comment ces technologies sont utilisées dans le réglage réel de la JVM, et le sera dans le prochain Partagez avec vous dans l'article.

Je suppose que tu aimes

Origine blog.51cto.com/14994509/2596518
conseillé
Classement