Class Loaders en Java

Chargeurs de classes en Java

1. Introduction aux chargeurs de classe

Les chargeurs de classe sont responsables deloading Java classes during runtime dynamically to the JVM (machine virtuelle Java). En outre, ils font partie du JRE (Java Runtime Environment). Par conséquent, la JVM n’a pas besoin de connaître les fichiers ou les systèmes de fichiers sous-jacents pour exécuter des programmes Java grâce aux chargeurs de classes.

De plus, ces classes Java ne sont pas chargées en mémoire en une seule fois, mais lorsqu'elles sont requises par une application. C'est ici que les chargeurs de classe entrent en scène. Ils sont responsables du chargement des classes en mémoire.

Dans ce didacticiel, nous allons parler des différents types de chargeurs de classe intégrés, de leur fonctionnement et d'une introduction à notre propre implémentation personnalisée.

Lectures complémentaires:

Comprendre les fuites de mémoire en Java

Découvrez quelles sont les fuites de mémoire en Java, comment les reconnaître au moment de l'exécution, leurs causes et les stratégies pour les prévenir.

Read more

ClassNotFoundException vs NoClassDefFoundError

Découvrez les différences entre ClassNotFoundException et NoClassDefFoundError.

Read more

2. Types de chargeurs de classe intégrés

Commençons par apprendre comment différentes classes sont chargées à l'aide de différents chargeurs de classes à l'aide d'un exemple simple:

public void printClassLoaders() throws ClassNotFoundException {

    System.out.println("Classloader of this class:"
        + PrintClassLoader.class.getClassLoader());

    System.out.println("Classloader of Logging:"
        + Logging.class.getClassLoader());

    System.out.println("Classloader of ArrayList:"
        + ArrayList.class.getClassLoader());
}

Lorsqu'elle est exécutée, la méthode ci-dessus imprime:

Class loader of this class:[email protected]
Class loader of Logging:[email protected]
Class loader of ArrayList:null

Comme nous pouvons le voir, il existe trois chargeurs de classe différents ici; application, extension et bootstrap (affichées sous la formenull).

Le chargeur de classe de l'application charge la classe dans laquelle la méthode exemple est contenue. An application or system class loader loads our own files in the classpath.

Ensuite, l'extension on charge la classeLogging. Extension class loaders load classes that are an extension of the standard core Java classes.

Enfin, le bootstrap charge la classeArrayList. A bootstrap or primordial class loader is the parent of all the others.

Cependant, nous pouvons voir que le dernier out, pour lesArrayList, il affichenull dans la sortie. This is because the bootstrap class loader is written in native code, not Java – so it doesn’t show up as a Java class. Pour cette raison, le comportement du chargeur de classe d'amorçage diffère selon les JVM.

Voyons maintenant plus en détail chacun de ces chargeurs de classes.

2.1. Chargeur de classe Bootstrap

[.s1] Les classes #Java sont chargées par une instance dejava.lang.ClassLoader. Cependant, les chargeurs de classes sont eux-mêmes des classes. Par conséquent, la question est de savoir qui charge lejava.lang.ClassLoader lui-même? #

C'est là que le bootstrap ou le chargeur de classe primordial entre en scène.

[.s1] # Il est principalement responsable du chargement des classes internes JDK, généralementrt.jar et d'autres bibliothèques principales situées dans$JAVA_HOME/jre/lib directory. De plus,Bootstrap class loader serves as a parent of all the other ClassLoader instances. #

This bootstrap class loader is part of the core JVM and is written in native code comme indiqué dans l'exemple ci-dessus. Différentes plates-formes peuvent avoir différentes implémentations de ce chargeur de classe particulier.

2.2. Chargeur de classe d'extension

Leextension class loader is a child of the bootstrap class loader and takes care of loading the extensions of the standard core Java classes afin qu'il soit disponible pour toutes les applications exécutées sur la plate-forme.

Le chargeur de classe d'extension se charge à partir du répertoire des extensions JDK, généralement le répertoire$JAVA_HOME/lib/ext ou tout autre répertoire mentionné dans la propriété systèmejava.ext.dirs.

2.3. Chargeur de classe système

Le chargeur de classe système ou d'application, quant à lui, se charge de charger toutes les classes de niveau application dans la JVM. It loads files found in the classpath environment variable, -classpath or -cp command line option. En outre, il s'agit d'un enfant du chargeur de classe Extensions.

3. Comment fonctionnent les chargeurs de classe?

Les chargeurs de classes font partie de l'environnement d'exécution Java. Lorsque la machine virtuelle Java demande une classe, le chargeur de classe essaie de localiser la classe et de charger la définition de classe dans l'environnement d'exécution à l'aide du nom de classe qualifié complet.

Lesjava.lang.ClassLoader.loadClass() method is responsible for loading the class definition into runtime. Il essaie de charger la classe en fonction d'un nom complet.

[.s1] #Si la classe n'est pas déjà chargée, elle délègue la demande au chargeur de classe parent. Ce processus se produit de manière récursive. #

Finalement, si le chargeur de classe parent ne trouve pas la classe, la classe enfant appellera la méthodejava.net.URLClassLoader.findClass() pour rechercher des classes dans le système de fichiers lui-même.

Si le dernier chargeur de classe enfant ne parvient pas non plus à charger la classe, il renvoiejava.lang.NoClassDefFoundError or java.lang.ClassNotFoundException.

Examinons un exemple de sortie lorsque ClassNotFoundException est levée.

java.lang.ClassNotFoundException: com.example.classloader.SampleClassLoader
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)

Si nous parcourons la séquence d'événements dès l'appel dejava.lang.Class.forName(), nous pouvons comprendre qu'il essaie d'abord de charger la classe via le chargeur de classe parent, puisjava.net.URLClassLoader.findClass() pour rechercher la classe elle-même.

Lorsqu'il ne trouve toujours pas la classe, il lance unClassNotFoundException.

Il existe trois caractéristiques importantes des chargeurs de classe.

3.1. Modèle de délégation

[.s1] Les chargeurs #Class suivent le modèle de délégation oùon request to find a class or resource, a ClassLoader instance will delegate the search of the class or resource to the parent class loader. #

[.s1] # Disons que nous avons une requête pour charger une classe d'application dans la JVM. Le chargeur de classe système délègue d'abord le chargement de cette classe à son chargeur de classe d'extension parent qui à son tour la délègue au chargeur de classe d'amorçage. #

Ce n'est que si le bootstrap, puis le chargeur de classe d'extension ne parviennent pas à charger la classe, le chargeur de classe système essaie de charger la classe elle-même.

3.2. Classes uniques

[.s1] #En raison du modèle de délégation, il est facile de garantirunique classes as we always try to delegate upwards. #

[.s1] #Si le chargeur de classe parent n’est pas en mesure de trouver la classe, alors seulement l’instance actuelle tentera de le faire elle-même. #

3.3. Visibilité

[.s1] #En outre,children class loaders are visible to classes loaded by its parent class loaders. #

[.s1] #Par exemple, les classes chargées par le chargeur de classe système ont une visibilité sur les classes chargées par l'extension et les chargeurs de classe Bootstrap, mais pas l'inverse. #

[.s1] #Pour illustrer cela, si la classe A est chargée par un chargeur de classe d'application et que la classe B est chargée par le chargeur de classe d'extensions, alors les classes A et B sont visibles en ce qui concerne les autres classes chargées par le chargeur de classe d'application . #

La classe B, néanmoins, est la seule classe visible en ce qui concerne les autres classes chargées par le chargeur de classe d'extension.

4. ClassLoader personnalisé

Le chargeur de classes intégré suffirait dans la plupart des cas où les fichiers sont déjà dans le système de fichiers.

Toutefois, dans les scénarios où nous devons charger des classes à partir du disque dur local ou d'un réseau, il peut être nécessaire d'utiliser des chargeurs de classes personnalisés.

Dans cette section, nous aborderons d'autres cas d'utilisation des chargeurs de classe personnalisés et nous montrerons comment en créer un.

4.1. Cas d'utilisation de chargeurs de classes personnalisés

Les chargeurs de classes personnalisés sont utiles pour plus que le chargement de la classe pendant l'exécution, quelques cas d'utilisation peuvent inclure:

  1. Aider à modifier le bytecode existant, par ex. agents de tissage

  2. Créer des classes dynamiquement adaptées aux besoins de l’utilisateur. e.g in JDBC, switching between different driver implementations is done through dynamic class loading.

  3. Implémentation d'un mécanisme de gestion de version de classe lors du chargement de différents bytecodes pour des classes portant le même nom et le même package. Cela peut être effectué via un chargeur de classes d'URL (load jars via des URL) ou des chargeurs de classes personnalisés.

Il existe d'autres exemples concrets dans lesquels des chargeurs de classes personnalisés peuvent s'avérer utiles.

Browsers, for instance, use a custom class loader to load executable content from a website. Un navigateur peut charger des applets à partir de différentes pages Web en utilisant des chargeurs de classe séparés. Le visualiseur d'applet qui est utilisé pour exécuter des applets contient unClassLoader qui accède à un site Web sur un serveur distant au lieu de chercher dans le système de fichiers local.

Et puis charge les fichiers de bytecode bruts via HTTP et les transforme en classes à l'intérieur de la JVM. Même si cesapplets have the same name, they are considered as different components if loaded by different class loaders.

Maintenant que nous comprenons pourquoi les chargeurs de classes personnalisés sont pertinents, implémentons une sous-classe deClassLoader pour étendre et résumer les fonctionnalités de la façon dont la JVM charge les classes.

4.2. Création de notre chargeur de classe personnalisé

À des fins d'illustration, disons que nous devons charger des classes à partir d'un fichier à l'aide d'un chargeur de classe personnalisé.

Nous devons étendre la classeClassLoader et remplacer la méthodefindClass():

public class CustomClassLoader extends ClassLoader {

    @Override
    public Class findClass(String name) throws ClassNotFoundException {
        byte[] b = loadClassFromFile(name);
        return defineClass(name, b, 0, b.length);
    }

    private byte[] loadClassFromFile(String fileName)  {
        InputStream inputStream = getClass().getClassLoader().getResourceAsStream(
                fileName.replace('.', File.separatorChar) + ".class");
        byte[] buffer;
        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
        int nextValue = 0;
        try {
            while ( (nextValue = inputStream.read()) != -1 ) {
                byteStream.write(nextValue);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        buffer = byteStream.toByteArray();
        return buffer;
    }
}

Dans l'exemple ci-dessus, nous avons défini un chargeur de classes personnalisé qui étend le chargeur de classes par défaut et charge un tableau d'octets à partir du fichier spécifié.

5. Comprendrejava.lang.ClassLoader

Discutons de quelques méthodes essentielles de la classejava.lang.ClassLoader pour avoir une idée plus claire de son fonctionnement.

5.1. La méthodeloadClass()

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

Cette méthode est responsable du chargement de la classe à l'aide d'un paramètre de nom. Le paramètre name fait référence au nom de classe complet.

La machine virtuelle Java appelle la méthodeloadClass() pour résoudre le paramètre de référence de classe résoudre entrue. Cependant, il n’est pas toujours nécessaire de résoudre une classe. If we only need to determine if the class exists or not, then resolve parameter is set to false.

Cette méthode sert de point d'entrée pour le chargeur de classes.

On peut essayer de comprendre le fonctionnement interne de la méthodeloadClass() à partir du code source dejava.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.
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

L'implémentation par défaut de la méthode recherche les classes dans l'ordre suivant:

  1. Appelle la méthodefindLoadedClass(String) pour voir si la classe est déjà chargée.

  2. Appelle la méthodeloadClass(String) sur le chargeur de classe parent.

  3. Appelez la méthodefindClass(String) pour trouver la classe.

5.2. La méthodedefineClass()

protected final Class defineClass(
  String name, byte[] b, int off, int len) throws ClassFormatError

Cette méthode est responsable de la conversion d'un tableau d'octets en une instance d'une classe. Et avant d'utiliser la classe, nous devons la résoudre.

Si les données ne contiennent pas de classe valide, elles lèvent unClassFormatError.

De plus, nous ne pouvons pas remplacer cette méthode car elle est marquée comme finale.

5.3. La méthodefindClass()

protected Class findClass(
  String name) throws ClassNotFoundException

Cette méthode recherche la classe avec le nom complet en tant que paramètre. Nous devons redéfinir cette méthode dans les implémentations de chargeur de classes personnalisées qui suivent le modèle de délégation pour le chargement de classes.

De plus,loadClass() appelle cette méthode si le chargeur de classe parent n’a pas pu trouver la classe demandée.

L'implémentation par défaut lève unClassNotFoundException si aucun parent du chargeur de classe ne trouve la classe.

5.4. La méthodegetParent()

public final ClassLoader getParent()

Cette méthode retourne le chargeur de classe parent pour la délégation.

Certaines implémentations comme celle vue précédemment dans la section 2. utiliseznull pour représenter le chargeur de classe d'amorçage.

5.5. La méthodegetResource()

public URL getResource(String name)

Cette méthode tente de trouver une ressource portant le nom donné.

Il déléguera d'abord au chargeur de classe parent pour la ressource. If the parent is null, the path of the class loader built into the virtual machine is searched.

Si cela échoue, la méthode invoquerafindResource(String) pour trouver la ressource. Le nom de ressource spécifié en tant qu'entrée peut être relatif ou absolu par rapport au chemin de classe.

Il renvoie un objet URL pour lire la ressource, ou null si la ressource est introuvable ou si l’invocateur ne dispose pas des privilèges adéquats pour renvoyer la ressource.

Il est important de noter que Java charge les ressources à partir du chemin de classe.

Enfin,resource loading in Java is considered location-independent car peu importe où le code s'exécute tant que l'environnement est configuré pour trouver les ressources.

6. Classloaders de contexte

En général, les chargeurs de classes de contexte fournissent une méthode alternative au schéma de délégation de chargement de classes introduit dans J2SE.

Comme nous l’avons appris auparavant,classloaders in a JVM follow a hierarchical model such that every class loader has a single parent with the exception of the bootstrap class loader.

Cependant, il peut arriver que des problèmes surviennent lorsque les classes principales de la machine virtuelle Java doivent charger de manière dynamique les classes ou les ressources fournies par les développeurs d'applications.

Par exemple, dans JNDI, la fonctionnalité principale est implémentée par des classes d'amorçage enrt.jar.. Mais ces classes JNDI peuvent charger des fournisseurs JNDI implémentés par des fournisseurs indépendants (déployés dans le chemin de classe de l'application). Ce scénario appelle le chargeur de classe d'amorçage (chargeur de classe parent) pour charger une classe visible au chargeur d'application (chargeur de classe enfant).

La délégation J2SE ne fonctionne pas ici et pour contourner ce problème, nous devons trouver des moyens alternatifs de chargement de classe. Et cela peut être réalisé en utilisant des chargeurs de contexte de thread.

La classejava.lang.Thread a une méthodegetContextClassLoader() that returns the ContextClassLoader for the particular thread. LeContextClassLoader est fourni par le créateur du thread lors du chargement des ressources et des classes.

Si la valeur n’est pas définie, elle utilise par défaut le contexte du chargeur de classe du thread parent.

7. Conclusion

Les chargeurs de classes sont essentiels pour exécuter un programme Java. Nous avons fourni une bonne introduction dans le cadre de cet article.

Nous avons parlé de différents types de chargeurs de classes, à savoir - chargeurs de démarrage, extensions et systèmes. Bootstrap sert de parent à tous et est responsable du chargement des classes internes du JDK. Les extensions et le système, quant à eux, chargent des classes à partir du répertoire des extensions Java et du chemin de classe respectivement.

Nous avons ensuite abordé le fonctionnement des chargeurs de classes. Nous avons également abordé certaines fonctionnalités telles que la délégation, la visibilité et le caractère unique, suivies d’une brève explication sur la création d’un programme personnalisé. Enfin, nous avons fourni une introduction aux chargeurs de classes de contexte.

Des échantillons de code, comme toujours, peuvent être trouvésover on GitHub.