Etes-vous sûr de bien comprendre la "délégation parentale"? !

Pourquoi ne connaissez-vous pas le chemin pour devenir un dieu des ingénieurs Java chez GitHub 19k Star?

Récemment, lors du processus d'entrevue, j'aime poser quelques questions sur la nomination de mes parents, car j'ai trouvé que cette question peut vraiment m'aider à comprendre un candidat sous tous ses aspects.

Je me souviens que lors d'un entretien il y a quelques jours, j'ai parlé à un candidat du mécanisme de chargement des classes JVM, il a parlé de la délégation parentale et m'a confié avec confiance sa compréhension de la délégation parentale.

Parce qu'il est rare de rencontrer un candidat qui en sait beaucoup sur les connaissances, nous avons lancé une confrontation de "300 rounds" Après avoir posé ces questions, environ une demi-heure s'est écoulée.

Au final, la personne de suivi m'a dit: " Je ne m'attendais pas à ce que moi, un responsable technique qui avait travaillé pendant 7 ans, je sois affecté aux abus par mes parents !!! "

Passons en revue les questions que je lui ai posées et voyons combien de réponses vous pouvez répondre:

1. Qu'est-ce que la délégation parentale? 2. Pourquoi les parents ont-ils besoin de déléguer et quels sont les problèmes de ne pas déléguer? 3. La relation entre "chargeur parent" et "chargeur enfant" est-elle héritée? 4. Comment fonctionne la délégation parentale? 5. Puis-je détruire volontairement ce mécanisme de délégation parentale? Comment le détruire? 6. Pourquoi la substitution de la méthode loadClass peut interrompre la délégation parente Quelle est la différence entre cette méthode et findClass () et defineClass ()? 7. Parlez-moi des exemples de délégation parentale dont vous avez connaissance 8. Pourquoi est-ce que JNDI, JDBC, etc. ont besoin de rompre la délégation parentale? 9. Pourquoi TOMCAT détruit-il la délégation parentale? 10. Parlez de votre compréhension de la technologie modulaire!

Les 10 questions ci-dessus, commencent par le début, à combien de questions pouvez-vous probablement vous en tenir?

Quel est le mécanisme de délégation parent

Tout d'abord, nous savons que la machine virtuelle a besoin d'utiliser un chargeur de classe pour charger la classe. En Java, il existe de nombreux chargeurs de classe. Donc, lorsque la machine virtuelle Java veut charger un fichier .class, quelle classe doit être utilisée? Qu'en est-il du chargement du chargeur?

Cela doit mentionner le «mécanisme de délégation des parents».

Tout d'abord, nous devons savoir que les 4 types de chargeurs suivants sont pris en charge dans le système de langage Java:

  • Bootstrap ClassLoader Bootstrap ClassLoader
  • Extention ClassLoader Chargeur de classe étendue standard
  • Application ClassLoader Application ClassLoader
  • User ClassLoader Chargeur de classe défini par l'utilisateur

Il existe une relation hiérarchique entre ces quatre types de chargeurs, comme illustré ci-dessous

-w704

On considère généralement que le chargeur de la couche supérieure est le chargeur parent du chargeur de la couche suivante. Ensuite, à l'exception de BootstrapClassLoader, tous les chargeurs ont un chargeur parent.

Ensuite, le mécanisme dit de délégation parente fait référence à: lorsqu'un chargeur de classe reçoit une demande de chargement de classe, il ne chargera pas directement la classe spécifiée, mais déléguera la demande à son chargeur parent pour charger . Ce n'est que lorsque le chargeur parent ne peut pas charger la classe, le chargeur actuel sera responsable du chargement de la classe.

Alors, dans quelles circonstances le chargeur parent ne parviendra-t-il pas à charger une certaine classe?

En fait, les quatre types de chargeurs fournis en Java ont leurs propres responsabilités:

  • Bootstrap ClassLoader est principalement responsable du chargement des bibliothèques de classes Java Core, rt.jar, resources.jar, charsets.jar et class sous% JRE_HOME% \ lib.
  • Extention ClassLoader est principalement responsable du chargement des packages jar et des fichiers de classe dans le répertoire% JRE_HOME% \ lib \ ext.
  • Application ClassLoader, principalement responsable du chargement de toutes les classes sous le chemin de classe de l'application actuelle
  • L'utilisateur ClassLoader, chargeur de classe défini par l'utilisateur, peut charger le fichier de classe du chemin spécifié

En d'autres termes, une classe définie par l'utilisateur, telle que com.hollis.ClassHollis, ne sera de toute façon pas chargée par les chargeurs Bootstrap et Extension.

Pourquoi les parents doivent-ils déléguer?

Comme nous l'avons mentionné ci-dessus, en raison de la relation hiérarchique stricte entre les chargeurs de classe, la classe Java a également une relation hiérarchique.

En d'autres termes, cette relation hiérarchique est prioritaire.

Par exemple, une classe définie dans le package java.lang, car elle est stockée dans le rt.jar, sera confiée au Bootstrap ClassLoader et finalement chargée par le Bootstrap ClassLoader.

Et une classe com.hollis.ClassHollis définie par l'utilisateur, il sera toujours confié à Bootstrap ClassLoader, mais comme Bootstrap ClassLoader n'est pas responsable du chargement de la classe, elle sera chargée par l'extension ClassLoader, et l'extension ClassLoader n'est pas responsable de cette classe Le chargement sera finalement chargé par l'Application ClassLoader.

Ce mécanisme présente plusieurs avantages.

Premièrement, grâce à la délégation, le chargement répété des classes peut être évité . Lorsque le chargeur parent a déjà chargé une certaine classe, le chargeur enfant ne rechargera pas cette classe.

De plus, la sécurité est également garantie par la voie de la délégation parentale . Parce que lorsque Bootstrap ClassLoader est chargé, il ne chargera que les classes du package jar dans JAVA_HOME, comme java.lang.Integer, alors cette classe ne sera pas remplacée à volonté, à moins que quelqu'un ne s'exécute sur votre machine et ne détruit votre JDK.

Ensuite, cela peut empêcher quelqu'un de charger un java.lang.Integer personnalisé avec une fonction destructive. Cela peut efficacement empêcher la falsification de l'API Java principale.

La relation entre l'héritage "parent et chargeur enfant" est-elle héréditaire?

Beaucoup de gens voient des noms tels que chargeur parent et chargeur enfant, et pensent qu'il existe une relation d'héritage entre les chargeurs de classe en Java.

Même de nombreux articles sur Internet ont des vues erronées similaires.

Il faut préciser ici que dans le modèle de délégation parent, la relation parent-enfant entre les chargeurs de classe n'est généralement pas implémentée dans une relation d'héritage, mais utilise plutôt une relation de composition pour réutiliser le code du chargeur parent.

Voici la définition du chargeur parent dans ClassLoader:

public abstract class ClassLoader {
    // The parent class loader for delegation
    private final ClassLoader parent;
}

Comment fonctionne la délégation parentale?

Le modèle de délégation parent est important pour assurer le fonctionnement stable des programmes Java, mais sa mise en œuvre n'est pas compliquée.

Le code d'implémentation de la délégation parente est concentré dans la méthode loadClass () de java.lang.ClassLoader :

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;
        }
    }

Le code n'est pas difficile à comprendre, principalement les étapes suivantes:

1. Vérifiez d'abord si la classe a été chargée. 2. Si elle n'est pas chargée, appelez la méthode loadClass () du chargeur parent pour la charger. 3. Si le chargeur parent est vide, le chargeur de classe de démarrage est utilisé par défaut comme chargeur parent. 4. Si le chargement de la classe parente échoue, après avoir lancé une exception ClassNotFoundException, appelez votre propre méthode findClass () à charger.

Comment détruire activement le mécanisme de délégation parent?

Connaissant la réalisation du modèle de délégation parentale, il est très simple de détruire le mécanisme de délégation parentale.

Étant donné que le processus de délégation de ses parents est implémenté dans la méthode loadClass, si vous souhaitez détruire ce mécanisme, vous pouvez personnaliser un chargeur de classe et remplacer la méthode loadClass afin qu'elle n'effectue pas de délégation parente.

loadClass () 、 findClass () 、 defineClass () 区别

Il existe de nombreuses méthodes liées au chargement de classe dans ClassLoader. LoadClass a été mentionné précédemment. De plus, il existe également findClass et defineClass. Quelle est donc la différence entre ces méthodes?

  • loadClass ()
    • Il s'agit de la principale méthode de chargement de classe. Le mécanisme de délégation parent par défaut est implémenté dans cette méthode.
  • findClass ()
    • Charger le bytecode .class en fonction du nom ou de l'emplacement
  • definclass ()
    • Convertir le bytecode en classe

Nous devons développer loadClass et findClass. Comme nous l'avons déjà dit, lorsque nous voulons personnaliser un chargeur de classe, et que nous voulons casser le principe de la délégation parente, nous remplacerons la méthode loadClass.

Alors, que se passe-t-il si nous voulons définir un chargeur de classe, mais ne voulons pas casser le modèle de délégation parent?

À ce stade, vous pouvez hériter de ClassLoader et remplacer la méthode findClass. La méthode findClass () est une nouvelle méthode ajoutée par ClassLoader après JDK1.2.

 /**
 * @since  1.2
 */
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

Cette méthode ne lève qu'une exception, il n'y a pas d'implémentation par défaut.

Après JDK1.2, il n'est plus recommandé aux utilisateurs de remplacer directement la méthode loadClass (), mais il est recommandé d'implémenter votre propre logique de chargement de classe dans la méthode findClass ().

Parce que dans la logique de la méthode loadClass (), si le chargeur de classe parent ne parvient pas à se charger, il appellera sa propre méthode findClass () pour terminer le chargement.

Ainsi, si vous souhaitez définir votre propre chargeur de classe et suivre le modèle de délégation parentale, vous pouvez hériter de ClassLoader et implémenter votre propre logique de chargement dans findClass.

Exemples de délégation parentale rompue

La destruction du mécanisme de délégation parente n'est pas inhabituelle. De nombreux frameworks et conteneurs vont détruire ce mécanisme pour remplir certaines fonctions.

La première situation à détruire est devant la délégation parentale.

Comme le modèle de délégation parent a été introduit après JDK 1.2, les chargeurs de classe définis par l'utilisateur étaient déjà utilisés avant cela. Par conséquent, ceux-ci ne suivent pas le principe de la délégation parentale.

Le second est le cas où JNDI, JDBC, etc. doivent charger la classe d'implémentation de l'interface SPI.

La troisième consiste à implémenter des outils de déploiement à chaud remplaçables à chaud. Afin que le code prenne effet dynamiquement sans redémarrage, le module et le chargeur de classe sont remplacés ensemble pour réaliser le remplacement à chaud du code.

Le quatrième est l'émergence de conteneurs Web tels que tomcat.

Le cinquième est l'application de technologies modulaires telles que OSGI et Jigsaw.

Pourquoi JNDI, JDBC, etc. doivent-ils interrompre la délégation parentale?

Dans notre développement quotidien, la plupart du temps, nous appelons ces classes de base fournies par Java via des API, qui sont chargées par Bootstrap.

Cependant, en plus de l'API, il existe également une méthode SPI.

En tant que service JDBC typique, nous créons généralement une connexion à la base de données des manières suivantes:

Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mysql", "root", "1234");

Avant que le code ci-dessus ne soit exécuté, DriverManager sera d'abord chargé par le chargeur de classe, car la classe java.sql.DriverManager se trouve sous rt.jar, elle sera donc chargée par le chargeur racine.

Lorsque la classe est chargée, les méthodes statiques de la classe sont exécutées. L'un des éléments clés du code est:

ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);

Ce code essaiera de charger toutes les classes d'implémentation sous le chemin de classe qui implémentent l'interface du pilote.

Alors, voici le problème.

DriverManager est chargé par le chargeur racine, donc lorsque vous rencontrez le code ci-dessus lors du chargement, il essaiera de charger toutes les classes d'implémentation du pilote, mais ces classes d'implémentation sont essentiellement fournies par des tiers. Selon le principe de la délégation parente, les classes tierces ne peuvent pas être Le chargeur racine se charge.

Alors, comment résoudre ce problème?

Par conséquent, le principe de la délégation parente a été détruit en introduisant ThreadContextClassLoader (chargeur de contexte de thread, AppClassLoader par défaut) dans JDBC.

Nous pouvons voir quand nous plongeons dans la méthode ServiceLoader.load:

public static <S> ServiceLoader<S> load(Class<S> service) {
    ClassLoader cl = Thread.currentThread().getContextClassLoader();
    return ServiceLoader.load(service, cl);
}

Dans la première ligne, obtenez le chargeur de classe de contexte de thread du thread actuel AppClassLoader, qui est utilisé pour charger des classes d'implémentation spécifiques dans le chemin de classe.

Pourquoi Tomcat détruit-il la délégation parentale

Nous savons que Tomcat est un conteneur Web, donc un conteneur Web peut avoir besoin de déployer plusieurs applications.

Différentes applications peuvent dépendre de différentes versions de la même bibliothèque de classes tierce, mais le nom de chemin complet d'une certaine classe dans différentes versions de la bibliothèque de classes peut être le même.

Par exemple, plusieurs applications doivent s'appuyer sur hollis.jar, mais l'application A doit s'appuyer sur la version 1.0.0, mais l'application B doit s'appuyer sur la version 1.0.1. Une classe dans les deux versions est com.hollis.Test.class.

Si le mécanisme de chargement de classe délégué par le parent par défaut est adopté, il est impossible de charger plusieurs classes identiques.

Par conséquent, Tomcat enfreint le principe de la délégation parentale, fournit un mécanisme d'isolation et fournit un chargeur WebAppClassLoader distinct pour chaque conteneur Web.

Mécanisme de chargement de classe de Tomcat: pour réaliser l’isolement, la priorité est donnée au chargement des classes définies par l’application Web elle-même, de sorte qu’il ne respecte pas la convention de délégation parentale. Le chargeur de classe de chaque application: WebAppClassLoader est responsable du chargement des fichiers de classe dans son propre répertoire. Il sera chargé par CommonClassLoader plus tard, ce qui est le contraire de la délégation parentale.

Technologie de modularisation et mécanisme de chargement de classe

Ces dernières années, la technologie de modularisation a été très mature et la technologie de modularisation a été appliquée dans JDK 9.

En fait, dès avant JDK 9, le cadre d'OSGI était modulaire. La raison pour laquelle OSGI peut réaliser le remplacement à chaud du module et un contrôle précis de la visibilité interne du module est due à son mécanisme de chargement de classe spécial, entre les chargeurs. La relation n'est plus la structure arborescente du modèle de délégation parent, mais se transforme en une structure de réseau complexe.

-w942

Dans JDK, la délégation parentale n'est pas absolue.

Avant JDK9, les classes de base de JVM se trouvaient dans le package rt.jar, qui est également la pierre angulaire du fonctionnement JRE.

Ce n'est pas seulement une violation du principe de responsabilité unique, mais aussi de nombreuses classes inutiles sont également empaquetées lorsque le programme est compilé, provoquant un gonflement.

Dans JDK9, le JDK entier est construit sur la base de la modularité. Les précédents rt.jar et tool.jar sont divisés en dizaines de modules. Lors de la compilation, seuls les modules réellement utilisés sont compilés et chaque chargeur de classe effectue ses propres tâches. Responsable, ne chargez que les modules dont vous êtes responsable.

Class<?> c = findLoadedClass(cn);
if (c == null) {
    // 找到当前类属于哪个模块
    LoadedModule loadedModule = findLoadedModule(cn);
    if (loadedModule != null) {
        //获取当前模块的类加载器
        BuiltinClassLoader loader = loadedModule.loader();
        //进行类加载
        c = findClassInModuleOrNull(loadedModule, cn);
     } else {
          // 找不到模块信息才会进行双亲委派
            if (parent != null) {
              c = parent.loadClassOrNull(cn);
            }
      }
}

Pour résumer

Ce qui précède, de ce qu'est la délégation parentale, comment obtenir et détruire la délégation parentale, et des exemples de destruction de la délégation parentale, une introduction complète à la connaissance de la délégation parentale.

Je pense qu'en étudiant cet article, vous devez avoir une compréhension plus approfondie du mécanisme de délégation parentale.

Après avoir lu cet article, j'ai écrit en revers sur mon CV: Familier avec le mécanisme de chargement de classe de Java, et je vous demande si vous n'êtes pas satisfait!

À propos de l'auteur: série d'articles Hollis l'auteur a une quête unique pour les gens de codage, les experts techniques d'Alibaba, "les trois cours de programmeur," co-auteur, "les ingénieurs Java à la voie de Dieu."

Si vous avez des commentaires, des suggestions, ou souhaitez communiquer avec l'auteur, vous pouvez suivre le compte officiel [ Hollis ] et me laisser un message directement en arrière-plan.

Je suppose que tu aimes

Origine blog.csdn.net/hollis_chuang/article/details/112462198
conseillé
Classement