Compréhension approfondie du processus de chargement de classe jvm

Cet article est destiné à la lecture de notes.Je pense personnellement que cette partie est très importante par rapport à la collecte des ordures, donc je veux garder une note

1. Le cycle de vie d'une classe

Charge -> Authentification -> Préparer -> parse -> Initialisation -> Utilisation -> Désinstaller
Insérez la description de l'image ici
points:

  • La séquence des cinq étapes de chargement, vérification, préparation, initialisation et déchargement est déterminée
  • La phase d'analyse peut commencer après la phase d'initialisation dans certains cas
  • Le «chargement» réel de la première étape du processus de chargement de classe n'est pas clairement défini
  • Mais dans six cas, la classe doit être "initialisée" immédiatement:
    1. Lorsque vous rencontrez les quatre instructions de bytecode new, getstatic, putstatic ou invokestatic, si le type n'a pas été initialisé, vous devez d'abord déclencher sa phase d'initialisation.

Les conditions de déclenchement de ces quatre instructions sont les suivantes:

  • Lorsque vous utilisez le nouveau mot clé pour instancier un objet.
  • Lire ou définir un type de champ statique
  • Lors de l'appel d'une méthode statique d'une classe

2. Appel Reflect, initialiser s'il n'est pas initialisé
3. Classe principale d'exécution de la méthode principale
4. Le handle de la méthode Reflect se résout en REF_getStatic, REF_putStatic, REF_invokeStatic, REF_newInvokeSpecial La classe correspondante n'a pas été initialisée
5. La méthode d'interface modifiée par le mot-clé par défaut est La classe implémentée est initialisée en premier
. 6. Lorsque la classe est initialisée, si la classe parent n'est pas initialisée, l'initialisation de la classe parent est déclenchée.
Seuls ces six scénarios représentent des références actives à un type

Cas de mise en œuvre:

  • Référencer des champs statiques définis dans la classe parente via ses sous-classes ne déclenchera que l'initialisation de la classe parente et non les sous-classes.
  • Les constantes seront ajoutées au pool de constantes de la classe au moment de la compilation. En substance, il n'y a pas de référence directe à la classe qui définit la constante, donc elle ne déclenchera pas l'initialisation
  • Le tableau ne déclenchera pas l'initialisation de la classe. En effet, ce type de machine virtuelle génère automatiquement une sous-classe qui hérite directement de java.lang.Object. L'action de création est déclenchée par l'instruction bytecode newarray.
    Lorsque vous examinez si une classe est initialisée, déterminez si elle est référencée passivement, c'est-à-dire que l'initialisation de la classe est paresseuse.
    Il y a du matériel à tester ici. Je l'énumère et cela ne provoquera pas l'initialisation.
  • Objet de classe
  • Méthode LoadClass de chargement de classe
  • Lorsque le deuxième paramètre de Class.forname () est faux
  • L'interface a également un processus d'initialisation, qui est presque le même que la classe

Compréhension personnelle de l'initialisation de classe:
En fait, il a été mentionné ci-dessus, c'est-à-dire que l'initialisation de classe est paresseuse, ce qui peut économiser de l'espace mémoire. Bien sûr, certains programmes mettent l'accent sur un processus de préchauffage. Pas besoin d'initialiser

Exemple:
Insérez la description de l'image ici
Test 1:
Insérez la description de l'image ici
Insérez la description de l'image ici
Test 2:
Insérez la description de l'image ici
Insérez la description de l'image ici
Comment puis-je déclencher l'initialisation de MM?
Insérez la description de l'image ici
Ensuite, exécutez le code ci-dessus:
Insérez la description de l'image ici
Cela conduit à une conclusion: seuls les types de base et String peuvent être utilisés comme références directes à leur propre pool constant.
D'autres types de référence peuvent être utilisés comme classes finales. L'initialisation de cette classe est déclenchée, c'est-à-dire <cinit>, et le travail principal de <cinit> est d'initialiser les variables membres définies dans la classe.

2. Le processus de chargement des classes

2.1 Phase de "chargement"

Veuillez comprendre que l'étape "Chargement" est une étape de l'ensemble du processus "Chargement de classe"

1) Obtenez le flux d'octets binaires qui définit cette classe par son nom complet.
2) Convertissez la structure de stockage statique représentée par ce flux d'octets dans la structure de données d'exécution de la zone de méthode. 3) Générez un objet java.lang.Class représentant cette classe en mémoire comme point d'accès à diverses données de cette classe dans la zone de méthode .

Remarque:

  • La classe de tableau elle-même n'est pas créée par le chargeur de classe, mais le type d'élément de la classe de tableau est toujours chargé par le chargeur de classe
  • La phase de chargement n'est pas terminée, la phase de connexion a peut-être commencé

2.2 Phase de vérification

Le but est de s'assurer que les informations contenues dans le flux d'octets du fichier Class répondent à toutes les contraintes de la "Java Virtual Machine Specification", étape de garantie de la sécurité du langage Java

1. Vérification du format de fichier

  • Que cela commence par le nombre magique 0xCAFEBABE.
  • Si les numéros de version majeurs et mineurs sont dans la plage acceptée par la machine virtuelle Java actuelle.
  • Existe-t-il des types de constantes non pris en charge dans les constantes du pool de constantes (vérifiez l'indicateur de balise constant).
  • Y a-t-il une constante qui pointe vers une constante inexistante ou qui ne correspond pas au type dans diverses valeurs d'index qui pointent vers une constante?
  • Existe-t-il des données dans la constante CONSTANT_Utf8_info qui ne sont pas conformes au codage UTF-8?
  • S'il y a des informations supprimées ou supplémentaires dans chaque partie du fichier de classe et le fichier lui-même.
  • ... attends

2. Vérification des métadonnées (vérification sémantique des informations de métadonnées de classe)

  • Cette classe a-t-elle une classe parente (sauf java.lang.Object, toutes les classes doivent avoir une classe parente).
  • Indique si la classe parente de cette classe hérite d'une classe dont l'héritage n'est pas autorisé (une classe modifiée par final).
    Si la classe n'est pas une classe abstraite, si toutes les méthodes requises par sa classe parent ou son interface sont implémentées.
  • Si les champs et les méthodes de la classe entrent en conflit avec la classe parente (comme le remplacement du dernier champ de la classe parente, ou s'il y a une surcharge de méthode qui ne respecte pas les règles, comme les paramètres de méthode sont cohérents, mais le type de valeur de retour est différent, etc.)
  • ... attends

3. Vérification du bytecode

  • Assurez-vous que le type de données et la séquence de codes d'instructions de la pile d'opérandes peuvent fonctionner ensemble à tout moment
  • Assurez-vous qu'aucune instruction de saut ne sautera pas à l'instruction de bytecode en dehors du corps de la méthode.
  • · Assurez-vous que la conversion de type dans le corps de la méthode est toujours valide, par exemple, vous pouvez affecter un objet de sous-classe au type de données de classe parent, ce qui est sûr, mais affecter l'objet de classe parent au type de données de sous-classe, et même affecter l'objet à Un type de données qui n'a pas de relation d'héritage et qui n'est pas pertinent est dangereux et illégal.
  • ... attends

4. Vérification de la référence des symboles

  • Si le nom complet décrit par la chaîne de caractères dans la référence de symbole peut trouver la classe correspondante.
  • S'il existe des méthodes et des champs décrits par le descripteur de champ de la méthode et le nom simple dans la classe spécifiée.
  • Indique si l'accessibilité (privée, protégée, publique) de la classe, du champ et de la méthode dans la référence de symbole est accessible à la classe actuelle.

2.3 Étape de préparation

L'étape de préparation est l'étape d'allocation officielle de mémoire pour les variables définies dans la classe (ie les variables statiques, les variables modifiées par statique) et la définition de la valeur initiale des variables de classe

Remarque:
L'allocation de mémoire inclut uniquement les variables de classe, pas les variables d'instance. Les variables d'instance seront allouées dans le tas Java avec l'objet lorsque l'objet est instancié.
Exemples:

public static int value = 123;

La valeur initiale de la valeur de variable après la phase de préparation est 0 au lieu de 123, car aucune méthode Java n'a été exécutée à ce moment, et l' instruction putstatic avec la valeur attribuée à 123 est le programme est compilé et stocké dans la méthode constructeur de classe () Ainsi, l'action d'attribuer la valeur à 123 ne sera pas exécutée avant l'étape d'initialisation de la classe.
Mais pour:

public static final int value = 123;

Si l'attribut ConstantValue existe dans la table d'attributs de champ du champ de classe, la valeur de la variable sera initialisée à la valeur initiale spécifiée par l'attribut ConstantValue pendant l'étape de préparation

2.4 Étape de résolution

La phase de résolution est le processus de remplacement des références de symboles dans le pool constant par des références directes par la machine virtuelle Java

Pour clarifier le concept:
référence de symbole : la référence de symbole décrit la cible référencée avec un ensemble de symboles. Le symbole peut être n'importe quelle forme de littéral, tant qu'il peut être utilisé pour localiser la cible sans ambiguïté.

Référence directe: une référence directe est un pointeur qui peut pointer directement vers la cible, un décalage relatif ou une poignée qui peut être indirectement située sur la cible.
1. Analyse de classe ou d'interface
2. Analyse de terrain
3. Analyse de méthode

2.5 Phase d'initialisation

Ce n'est qu'à la phase d'initialisation que la machine virtuelle Java a réellement commencé à exécuter le code du programme Java écrit dans la classe et a remis le contrôle à l'application.
La phase d'initialisation est le processus d'exécution de la méthode class constructor ().

La méthode <clinit> () est générée par le compilateur qui collecte automatiquement les actions d'affectation de toutes les variables de classe de la classe et les instructions du bloc d'instructions statiques (bloc statique {}). L'ordre dans lequel le compilateur collecte les instructions se trouve dans le fichier source L'ordre d'occurrence détermine que seules les variables définies avant le bloc d'instructions statiques sont accessibles dans le bloc d'instructions statiques, et les variables définies après qu'il peut être affecté mais ne sont pas accessibles dans le bloc d'instructions statiques précédent

L'opportunité virtuelle Java garantit que la méthode <clinit> () de la classe parent a été exécutée avant l'exécution de la méthode <clinit> () de la classe enfant.

La méthode <clinit> () n'est pas nécessaire pour une classe ou une interface. S'il n'y a pas de bloc d'instructions statiques ou d'affectation de variables à une classe, le compilateur peut ne pas générer la méthode <clinit> () pour cette classe.
La méthode <clinit> () de l'interface d'exécution n'a pas besoin d'exécuter d'abord la méthode <clinit> () de l'interface parent, car l'interface parent sera initialisée uniquement lorsque les variables définies dans l'interface parent seront utilisées.

La machine virtuelle Java doit garantir que la méthode <clinit> () d'une classe est correctement verrouillée et synchronisée dans un environnement multithread

3. Chargeur de classe

L'équipe de conception de la machine virtuelle Java a l'intention de mettre l'action «d'obtenir un flux d'octets binaires décrivant une classe par son nom complet» pendant la phase de chargement de la classe en dehors de la machine virtuelle Java pour permettre à l'application de décider comment aller. Obtenez la classe requise. Le code qui implémente cette action est appelé "Class Loader".

Pour toute classe, le chargeur de classe qui la charge et la classe elle-même doivent établir conjointement son caractère unique dans la machine virtuelle Java . Chaque chargeur de classe possède un espace de noms de classe indépendant.

Autrement dit, si deux classes sont "égales" n'a de sens que si les deux classes sont chargées par le même chargeur de classe , sinon, même si les deux classes proviennent du même fichier de classe, elles sont identiques Chargement de machine virtuelle, tant que le chargeur de classe qui les charge est différent, ces deux classes doivent être inégales.

S'il y a un objet classe chargé par deux chargeurs de classe et que l'objet correspondant est généré, alors lorsque la vérification de type de l'objet appartient, le résultat est faux

3.1 Modèle de délégation des parents

Catégorie:
Bootstrap Class Loader (BootstrapClassLoader), ce chargeur de classe est implémenté en langage C ++ et fait partie de la machine virtuelle elle-même;
tous les autres chargeurs de classe, ces chargeurs de classe sont implémentés par le langage Java et existent indépendamment en dehors de la machine virtuelle Et tous hérités de la classe abstraite java.lang.ClassLoader.

Démarrer le chargeur de classe

Responsable du chargement et du stockage dans le répertoire <JAVA_HOME> \ lib, ou stocké dans le chemin spécifié par le paramètre -Xbootclasspath, et peut être reconnu par la machine virtuelle Java (identifié par le nom de fichier, tel que rt.jar, tools.jar, le nom ne correspond pas La bibliothèque de classes ne sera pas chargée même si elle est placée dans le répertoire lib) La bibliothèque de classes est chargée dans la mémoire de la machine virtuelle.
Le chargeur de classe de démarrage ne peut pas être directement référencé par le programme Java. Lorsque vous écrivez un chargeur de classe personnalisé, si vous devez déléguer la demande de chargement au chargeur de classe de démarrage pour traitement, vous pouvez utiliser directement null à la place.
Insérez la description de l'image ici

Chargeur de classe d'extension

Ce chargeur de classe est implémenté sous forme de code Java dans la classe sun.misc.Launcher $ ExtClassLoader. Il est responsable du chargement de toutes les bibliothèques du répertoire <JAVA_HOME> \ lib \ ext ou du chemin spécifié par la variable système java.ext.dirs.

Chargeur de classe d'application

Ce chargeur de classe est implémenté par sun.misc.Launcher $ AppClassLoader. Étant donné que le chargeur de classe d'application est la valeur de retour de la méthode getSystem-ClassLoader () dans la classe ClassLoader, il est également appelé le "chargeur de classe système" dans certains cas. Il est responsable du chargement de toutes les bibliothèques de classes sur le chemin de classe utilisateur (ClassPath), et les développeurs peuvent également utiliser ce chargeur de classe directement dans le code. Si vous n'avez pas personnalisé votre propre chargeur de classe dans l'application, il s'agit en général du chargeur de classe par défaut du programme.

Modèle de délégation parent

Insérez la description de l'image ici
Le modèle de délégation parent requiert qu'en plus du chargeur de classe de démarrage de niveau supérieur, le reste du chargeur de classe ait son propre chargeur de classe parent.
Cependant, la relation parent-enfant entre les chargeurs de classe n'est généralement pas implémentée dans une relation d'héritage (héritage), mais utilise généralement une relation de composition (composition) pour réutiliser le code du chargeur parent.

Processus de travail:
Si une charge de chargeur de classe de la classe a reçu une demande, il d' abord ne pas propre à essayer de charger cette classe , et que cette demande est déléguée au chargeur de classe parent complet , chaque niveau du chargeur de classe est vrai , Par conséquent, toutes les demandes de chargement doivent finalement être transférées vers le chargeur de classe de démarrage de niveau supérieur. Ce n'est que lorsque le chargeur parent signale qu'il ne peut pas terminer la demande de chargement (la classe requise n'est pas trouvée dans son étendue de recherche), le chargeur enfant Il essaiera de terminer le chargement par lui-même.

Avantages:
Un avantage évident est que les classes en Java ont une relation hiérarchique avec priorité avec leurs chargeurs de classe.

Par exemple, la classe java.lang.Object, qui est stockée dans rt.jar, quel que soit le chargeur de classe qui souhaite charger cette classe, elle est finalement déléguée au chargeur de classe de démarrage en haut du modèle à charger, donc la classe Object est dans le programme Dans toutes sortes d'environnements de chargeur de classe, la même classe peut être garantie.

Si vous laissez d'autres chargeurs de classe le charger, la détermination finale du type sera certainement déroutante

Implémentation du code:
Insérez la description de l'image ici

Détruisez la délégation parentale

loadclass-> findclass
JNDI service-> Le type de base doit être rappelé au code de l'utilisateur Déploiement à chaud-
> Lorsqu'une demande de chargement de classe est reçue, scannez la classe et chargez-la

Supplémentaire: différents chargeurs de classe chargent la même classe, et finalement getclass est égal à juger le faux cas

 public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        ClassLoader myClassLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(filename);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }
        };

        Class<?> aClass = myClassLoader.loadClass("com.lyq.boot.lexicalanalysis.A");
        Object o =  aClass.newInstance();
        A a = new A();
        System.out.println(o.getClass().equals(a.getClass()));
    }

Chargeur de classe de contexte

En d'autres termes, la classe chargée par le chargeur de classe parent doit être chargée par la
classe enfant . Le chargeur de classe enfant conserve la référence du chargeur de classe parent. Mais que faire si la classe chargée par le chargeur de classe parent doit accéder à la classe chargée par le chargeur de classe enfant? Le scénario le plus classique est le chargement JDBC.

JDBC est une interface standard définie par Java pour accéder à la base de données. Elle est incluse dans la bibliothèque de classe Java de base et chargée par le chargeur de classe racine. Les bibliothèques d'implémentation de divers fournisseurs de bases de données sont introduites en tant que dépendances tierces. Cette partie des bibliothèques d'implémentation est chargée par le chargeur de classe d'application.

Code pour obtenir la connexion Mysql:

// Charger le pilote

Class.forName("com.mysql.jdbc.Driver");

// Se connecter à la base de données

Connection conn = DriverManager.getConnection(url, user, password);

DriverManager est chargé par le chargeur de classe de démarrage. Le pilote de base de données (com.mysql.jdbc.Driver) utilisé par celui-ci est chargé par le chargeur de classe d'application. Il s'agit de la classe typique chargée par le chargeur de classe parent et accessible par la sous-classe. La classe chargée par le périphérique.

Pour la réalisation de ce processus, voir le code source de la classe DriverManager:

//建立数据库连接底层方法
private static Connection getConnection(
        String url, java.util.Properties info, Class<?> caller) throws SQLException {
    //获取调用者的类加载器
    ClassLoader callerCL = caller != null ? caller.getClassLoader() : null;
    synchronized(DriverManager.class) {
        //由启动类加载器加载的类,该值为null,使用上下文类加载器
        if (callerCL == null) {
            callerCL = Thread.currentThread().getContextClassLoader();
        }
    }
```java
    //...

    for(DriverInfo aDriver : registeredDrivers) {
        //使用上下文类加载器去加载驱动
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                //加载成功,则进行连接
                Connection con = aDriver.driver.connect(url, info);
                //...
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } 
        //...
    }
}
在上面的代码中留意改行代码:

```java
callerCL = Thread.currentThread().getContextClassLoader();

Cette ligne de code obtient le ContextClassLoader à partir du thread actuel, et où le ContextClassLoader est-il défini? Il est défini dans le code source du lanceur ci-dessus:

// Définit le chargeur de classe de contexte
Thread.currentThread (). SetContextClassLoader (this.loader); De
cette façon, le soi-disant chargeur de classe de contexte est essentiellement un chargeur de classe d'application. Par conséquent, le chargeur de classe de contexte n'est qu'un concept proposé pour résoudre l'accès inversé de la classe. Ce n'est pas un chargeur de classe flambant neuf. Il s'agit essentiellement d'un chargeur de classe d'application.

Publié 37 articles originaux · loué 6 · visites 4641

Je suppose que tu aimes

Origine blog.csdn.net/littlewhitevg/article/details/105521641
conseillé
Classement