JVM --- Disposition de la mémoire instanciée et emplacement d'accès des objets

Disposition de la mémoire d'instanciation d'objet et emplacement d'accès

Questions d'entrevue

  1. Comment les objets sont-ils stockés dans la JVM?
  2. Qu'y a-t-il dans les informations d'en-tête d'objet?
  3. Que contient l'en-tête de l'objet Java?

Commencez par la méthode et les étapes de création d'objet:
Insérez la description de l'image ici

La façon dont l'objet est créé

  • new: La méthode la plus courante pour appeler la méthode de classe statique de getInstance dans la classe singleton, la méthode statique de XXXFactory;
  • Méthode newInstance de la classe: une méthode marquée comme obsolète dans JDK9, car elle ne peut appeler que le constructeur de paramètre vide;
  • NewInstance du constructeur (XXX): méthode de réflexion, vous pouvez appeler le constructeur avec vide ou avec des paramètres;
  • Utilisez clone (): n'appelez aucun constructeur et demandez à la classe actuelle d'implémenter l'interface clone dans l'interface clonable;
  • Utiliser la sérialisation: la sérialisation est généralement utilisée pour la transmission réseau Socket;
  • Objenesis, une bibliothèque tierce.

Étapes de création d'objet

Regardez le processus de création de l'objet à partir du bytecode:

Exemple de code:

public class ObjectTest {
    
    
    public static void main(String[] args) {
    
    
        Object obj = new Object();
    }
}

Bytecode:

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class java/lang/Object
         3: dup           
         4: invokespecial #1                  // Method java/lang/Object."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 9: 0
        line 10: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
            8       1     1   obj   Ljava/lang/Object;
}

1. Déterminez si la classe correspondant à l'objet est chargée, liée et initialisée

  1. Lorsque la machine virtuelle rencontre une nouvelle instruction, vérifiez d'abord si les paramètres de cette instruction peuvent localiser une référence symbolique d'une classe dans le pool de constantes de Metaspace (zone de méthode), et vérifiez si la classe représentée par cette référence symbolique a été chargée, analysé et initialisé. (C'est-à-dire pour déterminer si la méta-information existe);
  2. Si la classe n'est pas chargée, en mode de délégation parent, utilisez le chargeur de classe actuel pour rechercher le fichier .class correspondant avec ClassLoader + nom de package + clé de nom de classe. Si le fichier est introuvable, ClassNotFoundException sera levée. Si trouvé , La classe est chargée et l'objet Classe correspondant est généré.

2. Allouer de la mémoire à l'objet

  1. Calculez d'abord la taille de l'espace occupé par l'objet, puis divisez un bloc de mémoire dans le tas pour le nouvel objet. Si la variable membre d'instance est une variable de référence, allouez simplement l'espace de la variable de référence, d'une taille de 4 octets;
  2. Si la mémoire est régulière: utilisez la collision de pointeurs pour allouer de la mémoire.
    • Si la mémoire est régulière, la machine virtuelle utilisera la méthode Bump The Point pour allouer de la mémoire à l'objet. Cela signifie que toute la mémoire utilisée est d'un côté, la mémoire libre est stockée de l'autre côté et un pointeur est placé au milieu comme indicateur du point de division. L'allocation de mémoire consiste simplement à déplacer le pointeur vers la mémoire libre pour une distance égale à la taille de l'objet.
    • Si le garbage collector choisit Serial, ParNew, qui est basé sur l'algorithme de compression, la machine virtuelle utilise cette méthode d'allocation. En général, les collisions de pointeurs sont utilisées lors de l'utilisation du collecteur avec le processus Compact.
    • L'algorithme de compression de marques (défragmentation) défragmentera la mémoire. Un côté de la mémoire du tas stocke des objets et l'autre côté est une zone libre.
  3. Si la mémoire n'est pas régulière: liste libre.
    • Si la mémoire n'est pas régulière, la mémoire utilisée et la mémoire inutilisée sont entrelacées, la machine virtuelle utilisera la liste libre pour allouer de la mémoire à l'objet. Cela signifie que la machine virtuelle maintient une liste pour enregistrer les blocs de mémoire disponibles. Lors de la redistribution, recherchez un espace suffisamment grand dans la liste pour diviser l'instance d'objet et mettre à jour le contenu de la liste. Cette méthode d'allocation est devenue la "Free List (Free List)".
    • Le choix de la méthode d'allocation est déterminé par le fait que le tas Java est régulier ou non, et si le tas Java est régulier ou non est déterminé par le fait que le ramasse-miettes utilisé a une fonction de compression.
    • Une fois que l'algorithme de balayage des marques nettoie la mémoire du tas, il y aura de nombreux fragments de mémoire.

3. Traitez les problèmes de concurrence

  1. Utilisez CAS + échec de nouvelle tentative pour garantir l'atomicité de la mise à jour;
  2. Pré-allouez TLAB pour chaque ensemble de threads en définissant le paramètre -XX: + UseTLAB (mécanisme de verrouillage de zone);
  3. Allouez une zone pour chaque thread dans la zone Eden.

4. Initialisez l'espace alloué

  1. Toutes les propriétés sont définies sur des valeurs par défaut pour garantir que les champs d'instance d'objet peuvent être utilisés directement sans attribuer de valeurs.
  2. L'ordre d'attribution des valeurs aux propriétés d'objet:
    • Initialisation de la valeur par défaut de la propriété
    • Afficher l'initialisation / l'initialisation du bloc de code (relation parallèle, qui est le premier et qui verra l'ordre d'écriture du code)
    • Initialiseur

5. Définissez l'en-tête d'objet de l'objet

  • Stockez la classe de l'objet (c'est-à-dire les informations de métadonnées de la classe), le HashCode de l'objet, les
    informations GC de l'objet, les informations de verrouillage et d'autres données dans l'en-tête d'objet de l'objet. Le paramètre spécifique de ce processus dépend de l'implémentation JVM.

6. Exécutez la méthode init pour initialiser

  1. Du point de vue d'un programme Java, l'initialisation n'a officiellement commencé. Initialisez les variables membres, exécutez le bloc de code instancié, appelez la méthode constructeur de la classe et affectez la première adresse de l'objet dans le tas à la variable de référence;
  2. Donc en général (déterminé par l'instruction invokespecial dans le bytecode), la méthode init sera exécutée après la nouvelle instruction pour initialiser l'objet selon les souhaits du programmeur, de sorte qu'un objet vraiment utilisable est considéré comme créé.

Regardez la méthode init du point de vue du bytecode

/**
 * 测试对象实例化的过程
 *  ① 加载类元信息 - ② 为对象分配内存 - ③ 处理并发问题  - ④ 属性的默认初始化(零值初始化)
 *  - ⑤ 设置对象头的信息 - ⑥ 属性的显式初始化、代码块中初始化、构造器中初始化
 *
 *
 *  给对象的属性赋值的操作:
 *  ① 属性的默认初始化 - ② 显式初始化 / ③ 代码块中初始化 - ④ 构造器中初始化
 */

public class Customer{
    
    
    int id = 1001;
    String name;
    Account acct;

    {
    
    
        name = "匿名客户";
    }
    public Customer(){
    
    
        acct = new Account();
    }

}
class Account{
    
    

}

Informations sur le bytecode:

 0 aload_0
 1 invokespecial #1 <java/lang/Object.<init>>
 4 aload_0
 5 sipush 1001
 8 putfield #2 <com/atguigu/java/Customer.id>
11 aload_0
12 ldc #3 <匿名客户>
14 putfield #4 <com/atguigu/java/Customer.name>
17 aload_0
18 new #5 <com/atguigu/java/Account>
21 dup
22 invokespecial #6 <com/atguigu/java/Account.<init>>
25 putfield #7 <com/atguigu/java/Customer.acct>
28 return

Les instructions bytecode de la méthode init ():

  • La valeur par défaut de l'attribut est initialisée: id = 1001;
  • Afficher l'initialisation / l'initialisation du bloc de code: name = "Anonymous Client";
  • Initialisation du constructeur: acct = new Account ();

La disposition de la mémoire de l'objet
Insérez la description de l'image ici
Remarque: ce que pointe le pointeur de type est en fait la méta-information stockée dans la zone de méthode.

Résumé de la disposition de la mémoire

Insérez la description de l'image ici

Emplacement d'accès aux objets

Comment la JVM accède-t-elle à ses instances d'objet internes via les références d'objet dans le cadre de pile?
Insérez la description de l'image ici
Positionnement, accessible par référence sur la pile

Deux façons d'accéder aux objets: gérer l'accès et le pointeur direct:

1. Accès à la poignée

  • Inconvénients: un espace est ouvert dans l'espace du tas en tant que pool de descripteurs, et le pool de descripteurs lui-même occupera également de l'espace; les objets du tas ne sont accessibles que via deux accès pointeurs, ce qui est inefficace.
  • Avantages: la référence stocke l'adresse du handle stable. Lorsque l'objet est déplacé (il est courant de déplacer l'objet pendant le garbage collection), seul le pointeur de données d'instance dans le handle peut être modifié et la référence elle-même n'a pas besoin d'être modifiée .
    Insérez la description de l'image ici

2. Pointeur direct (adopté par HotSpot)

  • Caractéristiques: Le pointeur direct est une référence dans la table des variables locales, qui pointe directement vers l'instance dans le tas, et il y a un pointeur de type dans l'instance d'objet, qui pointe vers les données de type d'objet dans la zone de méthode.
  • Inconvénients: La valeur de la référence doit être modifiée lorsque l'objet est déplacé (il est courant de déplacer des objets pendant le garbage collection).
    Insérez la description de l'image ici

Je suppose que tu aimes

Origine blog.csdn.net/qq_33626996/article/details/114652416
conseillé
Classement