Maison  >  Article  >  Java  >  Explication graphique détaillée du modèle de mémoire Java JMM

Explication graphique détaillée du modèle de mémoire Java JMM

高洛峰
高洛峰original
2017-03-19 11:47:571557parcourir

JMM est un niveau incontournable pour un programmeur qui souhaite avoir une compréhension approfondie de Java. Cet article est plus théorique et aussi simple à comprendre que possible. S'il y a des erreurs, j'espère que vous pourrez me corriger.

Parlons ensuite de l'allocation de mémoire principale de jvm d'abord

JMM java内存模型图文详解

1 pile de machine virtuelle Java (pile virtuelle Java )

La pile de machines virtuelles est privée au thread. Chaque thread a sa propre pile de machines virtuelles, qui est le modèle de mémoire pour l'exécution de la méthode Java. , il créera un stack frame sur la pile de la machine virtuelle. Le stack frame est une structure de données qui stocke principalement la variable locale (type de base, référence de objet , retour. Type d'adresse (pointant vers l'adresse d'une instruction de bytecode)), pile d'opérations (faisant référence à la pile d'instructions d'opération après la compilation de la méthode), liaison dynamique, sortie de méthode. De manière générale, la mémoire Java est divisée en pile et tas, et la pile fait référence à la pile de machine virtuelle. Mais l'allocation de mémoire de Java n'est pas si simple.

La liaison dynamique est expliquée comme suit :

Chaque cadre de pile contient un runtime d'exécution constante référence à la méthode à laquelle appartient le cadre de pile dans le pool. détenu pour les supports Lien dynamique lors de l'appel de méthode. Un grand nombre de références de symboles sont stockées dans le fichier

Class. Les instructions d'appel de méthode dans le bytecode prennent comme paramètre la référence de symbole pointant vers la méthode dans le pool de constantes. Certaines de ces références de symboles seront converties en références directes lors de la phase de chargement de la classe ou lors de la première utilisation. Cette conversion est appelée analyse statique . L'autre partie sera convertie en référence directe lors de chaque exécution, cette partie est appelée lien dynamique.

L'explication de la sortie de méthode est la suivante :

  • Lorsque l'exécution rencontre une instruction de retour, la valeur de retour sera transmise à l'appelant de méthode supérieur. Cette méthode de sortie est. appelé Sortie d'achèvement normal (Normal Method Invocation Completion) De manière générale, le compteur PC de l'appelant peut être utilisé comme adresse de retour.

  • Lorsqu'une exception est rencontrée lors de l'exécution et que le corps de la méthode actuelle n'est pas traité, la méthode se ferme à ce moment-là, il n'y a pas de valeur de retour, appelée Abrupt. Achèvement de l'appel de méthode. ), l'adresse de retour doit être déterminée via la table Exception Handler.

Il y aura deux exceptions dans la pile de machines virtuelles, l'une est le MOO commun et l'autre est StackOverFlowError. StackOverflowError est généralement causé par des appels récursifs La profondeur de la pile est également limitée dans la machine virtuelle, sinon la machine virtuelle pleurera s'il s'agit d'un appel récursif sans restriction. Inutile de dire que le MOO se produira lorsque la mémoire demandée est supérieure à ce qui est actuellement détenu par la pile de machines virtuelles (l'espace de la pile de machines virtuelles peut être étendu dynamiquement, mais la mémoire allouée à la JVM est également limitée, donc la pile de machines virtuelles n'est pas extensible à l'infini) de).

 2 Pile de méthodes locales

La pile de méthodes locales et la pile de machines virtuelles sont fondamentalement similaires, sauf que la pile de machines virtuelles exécute le bytecode de classe, tandis que la pile de méthodes locales Ce qui est exécuté dans la pile de méthodes est le service de la méthode locale, qui appelle en fait différentes implémentations de la même méthode écrites en C ou en C selon différentes plates-formes de système d'exploitation.

 3 Zone de méthode

  La zone de méthode est une zone partagée par les threads et est utilisée pour stocker les informations de classe qui ont été chargées par la machine virtuelle ( classe Pour les données de bytecode, veuillez noter que si vous chargez beaucoup de classes en même temps, vous devez augmenter l'espace dans la zone de méthode, sinon cela provoquera un MOO. Cependant, s'il y a trop de classes, vous pouvez utiliser le chargement paresseux. . Comme le mécanisme de chargement paresseux de Spring, essayez d'éviter de charger trop de classes (🎜>), de constantes, de variables statiques et de code compilé par le compilateur juste à temps (JIT) et d'autres données en même temps. La zone de méthode est en fait ce que nous appelons la zone de génération permanente ( est limitée au mécanisme de mise en œuvre de la machine virtuelle hotspot La raison pour laquelle elle est appelée génération permanente est que les données ici sont rarement récupérées). en raison du chargement, les classes ne disparaîtront pas dans un court laps de temps. De nombreuses méthodes créeront des objets dans le tas basé sur la classe. Les variables statiques sont généralement les nœuds racines des algorithmes gc et de recherche, tandis que les constantes ne changeront pas du tout. . Les données sont rarement nettoyées.

La spécification de la machine virtuelle Java a également des restrictions très lâches sur ce domaine. En plus d'être un espace physiquement discontinu, elle permet également une taille et une évolutivité fixes, et n'a pas besoin d'implémenter le garbage collection. Relativement parlant, le comportement du garbage collection est relativement rare dans ce domaine (il convient donc d'accorder plus d'attention à la définition des constantes et des variables statiques). La collecte de mémoire dans la zone méthode aura toujours lieu, mais la collecte de mémoire dans cette zone est principalement destinée au recyclage du pool constant et au déchargement des types.

D'une manière générale, le recyclage de la mémoire dans le domaine des méthodes est difficile à satisfaire. Lorsque la zone de méthode ne peut pas répondre aux exigences d’allocation de mémoire, une exception OutOfMemoryError est levée.

 4 Pool de constantes d'exécution

 Avant JDK1.6StringLe pool de constantes est situé dans la zone de méthode.
 Le pool de constantes de chaîne JDK1.7 a été déplacé vers le tas.

Java est un langage connecté dynamiquement Le rôle du pool de constantes est très important en plus des différents types de base définis dans le code (comme int. , long, etc.) et les types d'objets (tels que String et Array) incluent également certaines références de symboles sous forme de texte, telles que :

Noms complets de classes et interfaces ;

Noms et descripteurs de champs

Méthodes, noms et descripteurs ;

En langage C, si un programme souhaite appeler une fonction dans une autre bibliothèque, lors de la connexion, la position de la fonction dans la bibliothèque (c'est-à-dire relative à la bibliothèque Le décalage en début de fichier) sera écrit dans le programme. A l'exécution, la fonction est appelée directement à cette adresse

Dans le langage Java, tout est dynamique. Lors de la compilation, si un appel à d'autres méthodes de classe ou une référence à d'autres champs de classe est trouvé, ce qui est enregistré dans le fichier de classe ne peut être qu'une référence de symbole sous forme de texte. Lors du processus de connexion, la machine virtuelle recherche en fonction de ce texte. informations. La méthode ou le champ correspondant.

Par conséquent, contrairement aux soi-disant « constantes » dans le langage Java, le contenu des « constantes » dans le fichier de classe est très riche. Ces constantes sont concentrées dans une zone de la classe et stockées les unes après les autres. Ici, on les appelle le « pool constant ».

La technologie de pool constant en Java semble créer certains objets de manière pratique et rapide lorsqu'un objet est nécessaire, il peut être retiré du pool (s'il n'y en a pas dans le pool, créez-en un). permet de gagner beaucoup de temps lorsque vous devez créer à plusieurs reprises des variables égales. Le pool constant est en fait un espace mémoire, différent de l'espace de tas où se trouvent les objets créés à l'aide du mot-clé new.

L'ensemble du pool de constantes sera référencé par un index de la JVM Tout comme l'ensemble des éléments du tableau est accessible en fonction de l'index, la JVM traite également les informations stockées dans ces pools de constantes en fonction de l'index. méthode d'index. En fait, le pool constant est dans le programme Java. Le processus de lien dynamique joue un rôle crucial (mentionné ci-dessus). Ce qui suit est extrait de "Compréhension approfondie de la machine virtuelle Java".

En plus de la version de la classe, des champs, des méthodes, des interfaces et d'autres informations, le fichier Class contient également des informations selon lesquelles le pool de constantes est utilisé pour stocker divers littéraux et références de symboles générés par le compilateur. des informations seront Une fois la classe chargée, elle est stockée dans le pool de constantes d'exécution dans la zone des méthodes. La machine virtuelle Java a des réglementations strictes sur chaque partie de la classe (y compris le pool constant). Le type de données utilisé pour stocker chaque octet doit avoir des exigences de spécification, afin qu'il puisse être reconnu, chargé et exécuté par la machine virtuelle. De manière générale, en plus de sauvegarder les références de symboles décrites dans le fichier Class, les références directes traduites sont également stockées dans le pool de constantes d'exécution.

Une autre caractéristique importante du pool de constantes d'exécution par rapport au pool de constantes de fichier de classe est qu'il est dynamique. La machine virtuelle Java n'exige pas que les constantes puissent être générées uniquement lors de la compilation, c'est-à-dire qu'elles ne sont pas pré-générées. -intégré dans le pool de constantes du fichier de classe. Seul le contenu peut entrer dans le pool de constantes d'exécution dans la zone de méthode. De nouvelles constantes peuvent également être placées dans le pool de constantes pendant l'exécution.

Le pool de constantes fait partie de la zone de méthode, il est donc limité par la mémoire. Lorsqu'il ne peut pas demander suffisamment de mémoire, une exception OutOfMemoryError sera levée

 5 Heap

Le tas est la plus grande zone de mémoire et le seul endroit utilisé pour stocker les instances d'objets. Cet endroit est également le principal champ de bataille de l'algorithme gc. Cependant, avec le développement du JIT (compilation juste à temps) et la maturité de la technologie d'évasion, tous les objets ne sont pas créés sur le tas. Ce qui suit est un extrait de « Compréhension approfondie de la machine virtuelle Java ».

Dans le langage et l'environnement de programmation Java, un compilateur juste-à-time (compilateur JIT) est un programme qui convertit le bytecode Java (y compris les instructions qui doivent être interprétées). Un programme qui convertit les instructions en instructions qui peuvent être envoyées directement au processeur. Lorsque vous écrivez un programme Java, les instructions du langage source seront compilées en bytecode par le compilateur Java, plutôt qu'en code d'instruction correspondant à une plate-forme matérielle de processeur spécifique (par exemple, le microprocesseur Pentium d'Intel ou le processeur System/390 d'IBM). Le bytecode est un code indépendant de la plate-forme qui peut être envoyé à n'importe quelle plate-forme et exécuté sur cette plate-forme.

L'allocation de mémoire de Java est à peu près la suivante. La jvm est également équipée de nombreux paramètres pour ajuster les données ci-dessus. Je ne les énumérerai pas ici, mais je les expliquerai en détail dans un article distinct lié au gc. Parlons de la façon dont JVM gère les problèmes causés par la concurrence à l'ère des processeurs multicœurs.

 Contrôle de la concurrence

 Un processeur multicœur peut exécuter plusieurs threads simultanément, et chaque thread possède son propre espace de travail local (en fait, il est le système cache et les registres alloués à chaque cœur), qui stocke les données obtenues à partir de la mémoire principale ci-dessus sous forme de copie à exécuter dans l'espace de travail si les données sont partagées entre plusieurs threads et que les threads le sont. L'échange de données ne peut pas être effectué, ce qui entraîne le problème des données incohérentes des variables partagées. Java contrôle la visibilité des variables partagées via des mécanismes tels que le verrouillage volatile synchronisé.

JMM java内存模型图文详解

Synchronisé et verrouillé auront des chapitres séparés pour expliquer respectivement les mécanismes de mise en œuvre. Inutile de dire que ces deux-là sont garantis en termes de visibilité et d'atomicité. Volatile garantit uniquement la visibilité des données. Ce n'est que lorsque les données sont lues et chargées que les modifications des données dans les autres threads seront détectées dans le thread actuel. Si vous réussissez ces deux étapes, vous ne pouvez qu'être gêné. 🎜>En fait, ce que fait volatile, c'est éviter d'utiliser le cache et de ne pas stocker les données de la mémoire principale dans la mémoire de travail des threads. Lors des étapes de lecture et de chargement, les données sont obtenues à partir de la mémoire principale afin que les autres threads puissent les détecter. la Modification des variables ). Volatile n'est qu'une solution pour assurer la visibilité en raison des mauvaises performances de la synchronisation dans les premières versions de jdk. La version actuelle de jdk de synchronisé et de verrouillage a été optimisée dans une certaine mesure, il n'est donc généralement pas recommandé d'utiliser des variables volatiles à moins que vous sachiez ce que vous utilisez volatile pour le moment, car cela ne garantit pas l'exactitude de la concurrence.

JMM java内存模型图文详解

lire et charger des variables de copie de la mémoire principale vers la mémoire de travail actuelle, utiliser et attribuer du code d'exécution, modifier les valeurs des variables partagées, stocker et écrire utiliser les données de la mémoire de travail pour actualiser la mémoire principale Contenu lié à la mémoire, où l'utilisation et l'attribution peuvent apparaître plusieurs fois. volatile convient à certaines opérations idempotentes. Ceci sera expliqué dans l'implémentation du nofairsync de lock.

À ce stade, je dois parler du principe de ce qui se passe

avant. Il s'agit d'une relation d'ordre partiel entre deux opérations définies dans le modèle de mémoire Java. Si l'opération A se produit avant l'opération B, cela signifie qu'avant l'opération B, l'impact de l'opération A peut être observé par l'opération B. , « Influence » inclut la modification. la valeur des variables partagées en mémoire, l'envoi de messages, l'appel de méthodes, etc. Cela n'a fondamentalement rien à voir avec la séquence des événements dans le temps. Ce principe est particulièrement important. C'est la base principale pour juger s'il y a concurrence dans les données et si le fil est sûr.

Voici huit règles du modèle de mémoire Java qui garantissent que cela se produit avant. Elles existent déjà sans l'aide d'un synchroniseur et peuvent être utilisées directement dans le codage. Si la relation entre deux opérations ne figure pas dans cette liste et ne peut être déduite des règles suivantes, elles n'ont aucune garantie d'ordre et la machine virtuelle peut les réorganiser de manière aléatoire (afin d'utiliser pleinement le CPU et d'améliorer l'utilisation, le jvm, JVM réorganisera les codes ou les opérations non pertinents, de sorte que les opérations qui doivent attendre les E/S ou d'autres ressources soient placées à l'arrière, tandis que les autres opérations qui peuvent être effectuées instantanément sont placées à l'avant et exécutées en premier, en utilisant pleinement les ressources du processeur).

1. Règles de séquence du programme : dans un thread séparé, selon la séquence de flux d'exécution du code du programme, les opérations effectuées en premier (dans le temps) se produisent – ​​avant (dans le temps) les opérations effectuées plus tard.

2. Gestion des règles de verrouillage : une opération de déverrouillage se produit - avant (dans l'ordre chronologique, la même chose ci-dessous) une opération de verrouillage sur la même serrure.

3. VolatileRègles de variable : Une opération d'écriture sur une variable volatile se produit, avant une opération de lecture ultérieure sur la variable.

4. Règles de démarrage du fil : la méthode start() de l'objet Thread se produit avant chaque action de ce fil.

5. Règles de fin de thread : toutes les opérations du thread se produisent : avant la détection de la fin de ce thread, le thread peut être détecté via la fin de la méthode Thread.join(), la valeur de retour de Thread. isAlive(), etc. L'exécution a été interrompue.

6. Règles d'interruption du thread : L'appel à la méthode thread interrompu() se produit, avant que l' événement ne se produise lorsque le code du thread interrompu détecte l'interruption.

7. Règles de finalisation des objets : L'initialisation d'un objet est terminée ( constructeur fin de l'exécution) se produit - avant le début de sa méthode finalize().

8. Transitivité : Si l'opération A se produit – avant l'opération B, et que l'opération B se produit – avant l'opération C, alors on peut conclure que A se produit – avant l'opération C.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn