Maison  >  Article  >  Java  >  Mécanisme de chargement de classe JVM pour l'apprentissage Java

Mécanisme de chargement de classe JVM pour l'apprentissage Java

青灯夜游
青灯夜游avant
2018-10-16 16:35:251972parcourir

Cet article vous présentera le mécanisme de chargement de la classe Jvm dans l'apprentissage Java. Il a une certaine valeur de référence. Les amis dans le besoin peuvent s'y référer. J'espère qu'il vous sera utile.

1. Présentation

La machine virtuelle charge le fichier de classe (flux d'octets binaires) dans la mémoire, vérifie, convertit, analyse et initialise les données, et forme enfin un fichier qui peut être utilisée directement par la machine virtuelle de type Java, cette série de processus est le mécanisme de chargement de la classe.

2. Calendrier de chargement des classes

Une classe commence par son chargement dans la mémoire par la machine virtuelle jusqu'à ce qu'elle soit déchargée de la mémoire. L'ensemble du cycle de vie comprend : chargement— - Vérification - Préparation - Analyse - Initialisation - Utilisation - Désinstallation Ces 7 étapes. Les trois parties de vérification, de préparation et d'analyse sont collectivement appelées connexions.

Le schéma du cycle de vie est le suivant :

L'ordre des cinq étapes de chargement, vérification, préparation, initialisation et déchargement est déterminé, et le Le processus de chargement de la classe doit démarrer dans cet ordre, la phase d'analyse ne l'est pas nécessairement : elle peut démarrer après l'initialisation dans certains cas, c'est aussi pour prendre en charge la liaison dynamique du langage Java.

Quelles situations peuvent déclencher la phase d'initialisation d'un cours ? (Prémisse : le chargement, la vérification et la préparation ont naturellement été exécutés)

  1. Lorsque vous rencontrez les quatre instructions new, getstatic, putstatic et Ensurestatic, si la classe n'a pas été initialisée, son initialisation sera déclenché. (Les scénarios les plus courants qui déclenchent ces quatre instructions au travail : nouvelle instanciation d'objets, lecture ou définition de champs statiques de la classe [sauf modifications finales ou champs statiques qui ont été mis dans le pool constant], appel de static méthodes de la classe)

  2. Lors de l'utilisation de la réflexion

  3. Lors de l'initialisation d'une classe, si sa classe parent n'a pas encore été initialisée, vous devez déclencher d'abord l'initialisation de la classe parent

  4. Lorsque la machine virtuelle démarre, vous devez spécifier une classe principale à exécuter (la classe contenant la méthode principale la machine virtuelle le fera en premier). initialiser cette classe

  5. Utiliser lorsque le langage dynamique jdk1.7 est pris en charge, si le résultat final de l'analyse d'une instance java.lang.invoke.MethodHandle est le handle de méthode de REF_getStatic, REF_putStatic, REF_invokeStatic . Si la classe correspondant à ce handle n'a pas été initialisée, vous devez d'abord déclencher son initialisation

Remarque : toutes les méthodes de référencement de la classe ne déclencheront pas l'initialisation (référence passive par exemple). : créer un tableau, référençant les variables modifiées finales et les variables statiques des sous-classes qui font référence aux classes parentes ne déclenchera pas l'initialisation de la sous-classe mais déclenchera l'initialisation de la classe parent

Processus de chargement de classe

- Chargement

Le chargement est une étape de chargement de classe. Pendant la phase de chargement, la machine virtuelle doit effectuer les trois choses suivantes

  1. Obtenir le flux d'octets binaires qui définit cette classe via le nom complet de la classe

  2. Convertir la structure de stockage statique représentée par ce flux d'octets en structure de données d'exécution de la zone de méthode

  3. Générer une représentation de ce type en mémoire L'objet java.lang.Object sert de point d'accès à diverses données de cette classe dans la zone méthode

Par rapport aux autres étapes de chargement de classe, la l'étape de chargement (pour être précis, dans l'étape de chargement L'action d'obtenir le flux d'octets binaires d'une classe) est la plus contrôlable par le développeur. Parce que la phase de chargement peut être complétée à l'aide du chargeur de classe de démarrage fourni par le système, ou elle peut être complétée par le chargeur de classe personnalisé du développeur (c'est-à-dire en remplaçant la méthode loadClass() du chargeur de classe).

Une fois le chargement terminé, le flux d'octets binaires externes est converti au format requis par la machine virtuelle et stocké dans la zone de méthode, puis un objet de la classe java.lang.Class est instancié dans la mémoire . Cet objet servira d'interface externe au programme pour accéder à ces types de données dans la zone méthode.

Une partie de la phase de chargement et de la phase de connexion sont liées, et la vérification et d'autres opérations ne peuvent être effectuées qu'une fois le chargement terminé. Ces actions prises en sandwich dans le chargement appartiennent toujours à la phase de connexion, et les instants de début de ces deux phases conservent toujours une séquence fixe.

- Vérification

La vérification est la première étape de la connexion, afin de garantir que les informations contenues dans le flux d'octets binaires chargé sont conformes à la spécification de la machine virtuelle.

La phase de vérification est grossièrement divisée en 4 actions de vérification suivantes :

Vérification du format de fichier  : Vérifiez si le flux d'octets est conforme à la spécification de format de fichier Class. Par exemple : qu'il commence par le nombre magique 0xCAFEBABE, que les numéros de version majeure et mineure se trouvent dans la plage de traitement de la machine virtuelle actuelle, que les constantes du pool de constantes aient des types non pris en charge...

Vérification des métadonnées : Effectuer une analyse sémantique des informations décrites par le bytecode. Par exemple : cette classe a-t-elle une classe parent et si elle hérite correctement de la classe parent.

Vérification du bytecode : Grâce à l'analyse du flux de données et du flux de contrôle, il est déterminé que la sémantique du programme est légale et logique (pour parler franchement, cela signifie analyser le corps de la méthode du classe pour garantir que la méthode est dans Cela n'endommagera pas la machine virtuelle lors de son exécution).

Vérification de la référence du symbole : Assurez-vous que l'action d'analyse peut être exécutée normalement.

La phase de vérification est très importante, mais pas forcément une phase nécessaire (car elle n'a aucun impact sur la durée d'exécution du programme). Si tout le code en cours d'exécution a été utilisé et vérifié à plusieurs reprises, la vérification peut être désactivée pendant la phase d'implémentation à l'aide du paramètre -Xverify:none.

- Préparez

à allouer formellement de la mémoire aux variables de classe et à définir la valeur initiale de la variable de classe. La mémoire utilisée par ces variables sera allouée dans la zone méthode.

Remarque :

  • À l'heure actuelle, seules les variables statiques sont allouées, et non les variables d'instance seront allouées avec l'instance d'objet. Dans le tas Java

  • la valeur initiale est généralement la valeur zéro du type de données. Si vous définissez une variable statique public static int value = 123; alors la valeur initiale de value est 0 au lieu de 123 lors de la phase de préparation.

  • Les variables modifiées par final sont initialisées à la valeur spécifiée par l'attribut lors de la phase de préparation. Par exemple : public static final int value = 123 ; alors la valeur initiale de value dans la phase de préparation est 123 ;

- Analyse

La phase d'analyse est le processus dans lequel la machine virtuelle remplace les références de symboles dans le pool constant par des références directes . L'action d'analyse est principalement effectuée sur sept types de références de symboles : classes ou interfaces, champs, méthodes de classe, méthodes d'interface, types de méthodes, descripteurs de méthode et qualificatifs de site d'appel.

Référence de symbole : Utilisez un ensemble de symboles pour décrire la cible de la référence. Le symbole peut être n'importe quelle forme de littéral.

Référence directe : un pointeur vers la cible, un décalage relatif ou une poignée qui peut localiser indirectement la cible.

-Initialisation

La phase d'initialisation est le processus d'exécution de la méthode du constructeur de classe(). Dans la phase de préparation, les variables se sont vu attribuer une valeur initiale requise par le système, et dans la phase d'initialisation, les variables de classe et autres ressources sont initialisées en fonction des valeurs de paramètres définies par le programmeur.

Méthode Constructeur de classe() : Elle 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 fusionne les instructions dans le bloc de code statique.

L'ordre dans lequel le compilateur collecte est déterminé par l'ordre dans lequel les instructions apparaissent dans le fichier source ; un bloc de code statique ne peut accéder qu'aux variables définies avant le bloc statique, aux variables définies après celui-ci et aux variables définies dans le bloc statique précédent Les valeurs peuvent être attribuées, mais ne sont pas accessibles. La méthode

非法向前引用示例

public class SuperClass {
    public static int va;
    static {
        value = 1;            //可以编译通过
        va = value;           //报错  非法向前引用
        System.out.println("父类初始化");
    }

    public static int value = 123;
}

() n'est pas nécessaire pour les classes ou les interfaces. S'il n'y a pas de bloc de code statique dans une classe et qu'il n'y a pas d'opération d'affectation aux variables, alors le compilateur n'a pas besoin de le faire. Générer la méthode pour cette classe

Les blocs statiques ne peuvent pas être utilisés dans les interfaces, mais les opérations d'affectation de variables peuvent toujours être effectuées, donc les interfaces et les classes généreront des méthodes La différence est que l'initialisation de l'interface ne nécessite pas d'abord l'initialisation de la classe parent. L'initialisation de l'interface parent ne sera déclenchée que lorsque les variables de l'interface parent seront utilisées. De plus, la classe d’implémentation de l’interface ne déclenchera pas l’instanciation de l’interface.

La machine virtuelle garantit que la méthode () d'une classe est correctement verrouillée et synchronisée dans plusieurs threads. Si plusieurs threads initialisent une classe, un seul thread exécutera la classe (), les autres threads sont en attente. Seul le thread actif peut terminer l'exécution. S'il y a une opération de longue durée dans la méthode () d'une classe, cela peut entraîner le blocage de plusieurs threads. Dans les applications réelles, ce blocage est souvent très caché.

4. Chargeur de classe

L'équipe de conception de la machine virtuelle a mis l'action "d'obtenir le flux d'octets binaires décrivant cette classe via le nom complet d'une classe" dans le chargement de classe dans Java. en dehors de la machine virtuelle afin que l'application puisse décider comment obtenir les classes requises. Le bloc de code qui implémente cette action est appelé chargeur de classe.

Du point de vue des développeurs Java, les chargeurs de classe sont grossièrement divisés en trois types suivants

Bootstrap Classloader (Bootstrap Classloader)  : Responsable du stockage dans lib (Javahome est le répertoire d'installation de jdk), ou dans le chemin spécifié par le paramètre -Xbootclasspath, et est reconnu par la machine virtuelle (reconnu uniquement par le nom de fichier, tel que rt.jar, classes dont les noms ne correspondent pas à The la bibliothèque ne sera pas chargée même si elle est placée sous 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 utilisé directement par les programmes Java.

Extension Classloader  : Ce chargeur est implémenté par sun.misc.Launcher$ExtClassLoader, qui est responsable du chargement du répertoire libext, ou par java Toutes les bibliothèques de classes du. chemin système spécifié par la variable système ext.dirs. Les développeurs peuvent utiliser directement le chargeur de classe d'extension.

Application Classloader (Application Classloader)  : Ce chargeur est implémenté par sun.misc.Launcher$AppClassLoader, qui est responsable du chargement de la bibliothèque de classes spécifiée sur le chemin de classe utilisateur (ClassPath) . Les développeurs peuvent utiliser ce chargeur directement. S'il n'y a pas de chargeur de classe personnalisé dans l'application, il s'agit du chargeur de classe exécuté par défaut par le programme. (Chargeur système)

我们的应用程序都是由这3种类加载器相互配合进行加载的。如果有必要,还可以加入自定义的类加载器。

这些类加载器之间的关系如下图:

 

5.双亲委派模型: 

双亲委派模型的工作过程是:如果一个类加载器收到了一个类加载请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的加载器都是如此,因此所有的加载请求最终都应该到达顶层的启动类加载器。只有当父加载无法完成这个加载请求时,子加载器才会尝试自己去加载。

双亲委派机制:

1、当ApplicationClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。

2、当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。

3、如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载;

4、若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

ClassLoader源码分析:    

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 先检查此类是否已被加载
            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;
        }
    }                                         

双亲委派模型意义:

  • 系统类防止内存中出现多份同样的字节码

  • 保证Java程序安全稳定运行

参考

《深入理解Java虚拟机》 

总结:以上就是本篇文的全部内容,希望能对大家的学习有所帮助。更多相关教程请访问Java视频教程java开发图文教程bootstrap视频教程

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:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer