Class Loaders in Java – Java中的类加载器

最后修改: 2018年 4月 8日

中文/混合/英文(键盘快捷键:t)

1. Introduction to Class Loaders

1.类加载器简介

Class loaders are responsible for loading Java classes dynamically to the JVM (Java Virtual Machine) during runtime. They’re also part of the JRE (Java Runtime Environment). Therefore, the JVM doesn’t need to know about the underlying files or file systems in order to run Java programs thanks to class loaders.

类加载器负责在运行期间动态加载Java类到JVM (Java虚拟机)。它们也是JRE(Java运行时环境)的一部分。因此,由于有了类加载器,JVM不需要知道底层文件或文件系统,就可以运行Java程序。

Furthermore, these Java classes aren’t loaded into memory all at once, but rather when they’re required by an application. This is where class loaders come into the picture. They’re responsible for loading classes into memory.

此外,这些Java类并不是一下子就加载到内存中的,而是在应用程序需要它们的时候加载。这就是类加载器进入画面的地方。它们负责将类加载到内存中。

In this tutorial, we’ll talk about different types of built-in class loaders and how they work. Then we’ll introduce our own custom implementation.

在本教程中,我们将讨论不同类型的内置类加载器以及它们如何工作。然后我们将介绍我们自己的自定义实现。

2. Types of Built-in Class Loaders

2.内置类加载器的类型

Let’s start by learning how we can load different classes using various class loaders:

让我们先来学习一下如何使用各种类加载器加载不同的类。

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

When executed, the above method prints:

执行时,上述方法会打印出来。

Class loader of this class:sun.misc.Launcher$AppClassLoader@18b4aac2
Class loader of Logging:sun.misc.Launcher$ExtClassLoader@3caeaf62
Class loader of ArrayList:null

As we can see, there are three different class loaders here: application, extension, and bootstrap (displayed as null).

我们可以看到,这里有三个不同的类加载器:应用、扩展和bootstrap(显示为null)。

The application class loader loads the class where the example method is contained. An application or system class loader loads our own files in the classpath.

应用程序类加载器加载的是包含示例方法的类。应用程序或系统类加载器在classpath中加载我们自己的文件。

Next, the extension class loader loads the Logging class. Extension class loaders load classes that are an extension of the standard core Java classes.

接下来,扩展类加载器加载Logging类。扩展类加载器加载的类是标准核心Java类的扩展。

Finally, the bootstrap class loader loads the ArrayList class. A bootstrap or primordial class loader is the parent of all the others.

最后,bootstrap类加载器加载ArrayList类。一个引导类或原始类加载器是所有其他类的父类。

However, we can see that for the ArrayList, it displays null in the output. This is because the bootstrap class loader is written in native code, not Java, so it doesn’t show up as a Java class. As a result, the behavior of the bootstrap class loader will differ across JVMs.

然而,我们可以看到,对于ArrayList,它在输出中显示null这是因为bootstrap类加载器是用本地代码编写的,而不是Java,所以它不会显示为一个Java类。因此,bootstrap类加载器的行为在不同的JVM上会有所不同。

Now let’s discuss each of these class loaders in more detail.

现在让我们更详细地讨论这些类的加载器。

2.1. Bootstrap Class Loader

2.1.Bootstrap类加载器

Java classes are loaded by an instance of java.lang.ClassLoader. However, class loaders are classes themselves. So the question is, who loads the java.lang.ClassLoader itself?

Java类是由java.lang.ClassLoader的一个实例加载的。然而,类加载器本身就是类。所以问题是,谁来加载java.lang.ClassLoader本身

This is where the bootstrap or primordial class loader comes into play.

这就是引导器或原始类加载器发挥作用的地方。

It’s mainly responsible for loading JDK internal classes, typically rt.jar and other core libraries located in the $JAVA_HOME/jre/lib directory. Additionally, the Bootstrap class loader serves as the parent of all the other ClassLoader instances.

它主要负责加载JDK的内部类,通常是rt.jar和位于$JAVA_HOME/jre/lib目录下的其他核心库。此外,Bootstrap类加载器作为所有其他ClassLoader实例的父级

This bootstrap class loader is part of the core JVM and is written in native code, as pointed out in the above example. Different platforms might have different implementations of this particular class loader.

这个引导类加载器是核心JVM的一部分,并且是用本地代码编写的,正如上面的例子所指出的。不同的平台可能对这个特定的类加载器有不同的实现。

2.2. Extension Class Loader

2.2.扩展类加载器

The extension class loader is a child of the bootstrap class loader, and takes care of loading the extensions of the standard core Java classes so that they’re available to all applications running on the platform.

扩展类加载器是bootstrap类加载器的一个子程序,负责加载标准核心Java类的扩展,以便在平台上运行的所有应用程序都能使用这些类。

The extension class loader loads from the JDK extensions directory, usually the $JAVA_HOME/lib/ext directory, or any other directory mentioned in the java.ext.dirs system property.

扩展类加载器从 JDK 扩展目录中加载,通常是 $JAVA_HOME/lib/ext 目录,或 java.ext.dirs 系统属性中提到的任何其他目录。

2.3. System Class Loader

2.3.系统类加载器

The system or application class loader, on the other hand, takes care of loading all the application level classes into the JVM. It loads files found in the classpath environment variable, -classpath, or -cp command line option. It’s also a child of the extensions class loader.

另一方面,系统或应用程序类加载器负责将所有应用程序级别的类加载到JVM中。它加载在classpath环境变量-classpath,-cp命令行选项中发现的文件。它也是扩展类加载器的一个子程序。

3. How Do Class Loaders Work?

3.Class Loaders如何工作?

Class loaders are part of the Java Runtime Environment. When the JVM requests a class, the class loader tries to locate the class and load the class definition into the runtime using the fully qualified class name.

类加载器是Java运行时环境的一部分。当JVM请求一个类时,类加载器会尝试定位该类,并使用完全合格的类名将该类定义加载到运行时中。

The java.lang.ClassLoader.loadClass() method is responsible for loading the class definition into runtime. It tries to load the class based on a fully qualified name.

java.lang.ClassLoader.loadClass()方法负责将类定义加载到运行时。它试图根据一个完全合格的名称来加载类。

If the class isn’t already loaded, it delegates the request to the parent class loader. This process happens recursively.

如果该类尚未加载,它将请求委托给父类加载器。这个过程是递归进行的。

Eventually, if the parent class loader doesn’t find the class, then the child class will call the java.net.URLClassLoader.findClass() method to look for classes in the file system itself. 

最终,如果父类加载器没有找到该类,那么子类将调用java.net.URLClassLoader.findClass()方法,在文件系统本身中寻找类。

If the last child class loader isn’t able to load the class either, it throws java.lang.NoClassDefFoundError or java.lang.ClassNotFoundException.

如果最后一个子类加载器也无法加载该类,它会抛出java.lang.NoClassDefFoundErrorjava.lang.ClassNotFoundException。

Let’s look at an example of the output when ClassNotFoundException is thrown:

让我们看看当ClassNotFoundException被抛出时的输出示例。

java.lang.ClassNotFoundException: com.baeldung.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)

If we go through the sequence of events right from calling java.lang.Class.forName(), we can see that it first tries to load the class through the parent class loader, and then java.net.URLClassLoader.findClass() to look for the class itself.

如果我们浏览一下从调用java.lang.Class.forName()开始的事件序列,我们可以看到它首先尝试通过父类加载器加载类,然后java.net.URLClassLoader.findClass()来寻找类本身。

When it still doesn’t find the class, it throws a ClassNotFoundException.

当它仍然没有找到该类时,就会抛出一个ClassNotFoundException.

Now let’s examine three important features of class loaders.

现在让我们来看看类加载器的三个重要特征。

3.1. Delegation Model

3.1.授权模式

Class loaders follow the delegation model, where 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.

类加载器遵循委托模型,其中在请求找到一个类或资源时,ClassLoader实例将委托父类加载器搜索该类或资源

Let’s say we have a request to load an application class into the JVM. The system class loader first delegates the loading of that class to its parent extension class loader, which in turn delegates it to the bootstrap class loader.

假设我们有一个将一个应用类加载到JVM的请求。系统类加载器首先将该类的加载委托给它的父扩展类加载器,后者又将其委托给引导类加载器。

Only if the bootstrap and then the extension class loader are unsuccessful in loading the class, the system class loader tries to load the class itself.

只有在bootstrap和扩展类加载器都不能成功加载该类时,系统类加载器才会尝试自己加载该类。

3.2. Unique Classes

3.2.独特的类

As a consequence of the delegation model, it’s easy to ensure unique classes, as we always try to delegate upwards.

作为委托模型的结果,我们很容易确保唯一的类,因为我们总是试图向上委托

If the parent class loader isn’t able to find the class, only then will the current instance attempt to do so itself.

如果父类加载器无法找到该类,那么当前实例才会自己尝试这样做。

3.3. Visibility

3.3.可见性

In addition, children class loaders are visible to classes loaded by their parent class loaders.

此外,子类加载器对由其父类加载器加载的类是可见的

For instance, classes loaded by the system class loader have visibility into classes loaded by the extension and bootstrap class loaders, but not vice-versa.

例如,由系统类加载器加载的类对由扩展和引导类加载器加载的类具有可见性,但反之亦然。

To illustrate this, if Class A is loaded by the application class loader, and class B is loaded by the extensions class loader, then both A and B classes are visible as far as other classes loaded by the application class loader are concerned.

为了说明这一点,如果A类被应用类加载器加载,B类被扩展类加载器加载,那么就应用类加载器加载的其他类而言,A和B类都是可见的。

Class B, however, is the only class visible to other classes loaded by the extension class loader.

然而,B类是唯一对其他被扩展类加载器加载的类可见的类。

4. Custom ClassLoader

4.自定义ClassLoader

The built-in class loader is sufficient for most cases where the files are already in the file system.

对于文件已经在文件系统中的大多数情况,内置的类加载器已经足够了。

However, in scenarios where we need to load classes out of the local hard drive or a network, we may need to make use of custom class loaders.

然而,在我们需要从本地硬盘或网络中加载类的情况下,我们可能需要利用自定义类加载器。

In this section, we’ll cover some other use cases for custom class loaders and demonstrate how to create one.

在本节中,我们将介绍自定义类加载器的一些其他用例,并演示如何创建一个。

4.1. Custom Class Loaders Use-Cases

4.1.自定义类加载器用例

Custom class loaders are helpful for more than just loading the class during runtime. A few use cases might include:

自定义类加载器的作用不仅仅是在运行时加载类。一些用例可能包括。

  1. Helping to modify the existing bytecode, e.g. weaving agents
  2. Creating classes dynamically suited to the user’s needs, e.g. in JDBC, switching between different driver implementations is done through dynamic class loading.
  3. Implementing a class versioning mechanism while loading different bytecodes for classes with the same names and packages. This can be done either through a URL class loader (load jars via URLs) or custom class loaders.

Below are more concrete examples where custom class loaders might come in handy.

下面是一些更具体的例子,自定义类加载器可能会派上用场。

Browsers, for instance, use a custom class loader to load executable content from a website. A browser can load applets from different web pages using separate class loaders. The applet viewer, which is used to run applets, contains a ClassLoader that accesses a website on a remote server instead of looking in the local file system.

例如,浏览器使用自定义类加载器从网站加载可执行内容。浏览器可以使用单独的类加载器从不同的网页加载小程序。用于运行小程序的小程序查看器包含一个ClassLoader,它可以访问远程服务器上的网站,而不是在本地文件系统中寻找。

It then loads the raw bytecode files via HTTP, and turns them into classes inside the JVM. Even if these applets have the same name, they’re considered different components if loaded by different class loaders.

然后,它通过HTTP加载原始字节码文件,并在JVM中把它们变成类。即使这些小程序有相同的名字,如果由不同的类加载器加载,它们也被认为是不同的组件

Now that we understand why custom class loaders are relevant, let’s implement a subclass of ClassLoader to extend and summarise the functionality of how the JVM loads classes.

现在我们理解了为什么自定义类加载器是相关的,让我们实现一个ClassLoader的子类来扩展和总结JVM如何加载类的功能。

4.2. Creating Our Custom Class Loader

4.2.创建我们的自定义类加载器

For illustration purposes, let’s say we need to load classes from a file using a custom class loader.

为了说明问题,假设我们需要使用一个自定义的类加载器从一个文件中加载类。

We need to extend the ClassLoader class and override the findClass() method:

我们需要扩展ClassLoader类并覆盖findClass()方法:

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

In the above example, we defined a custom class loader that extends the default class loader, and loads a byte array from the specified file.

在上面的例子中,我们定义了一个自定义的类加载器,它扩展了默认的类加载器,并从指定文件中加载一个字节数组。

5. Understanding java.lang.ClassLoader

5.了解java.lang.ClassLoader

Let’s discuss a few essential methods from the java.lang.ClassLoader class to get a clearer picture of how it works.

让我们讨论一下java.lang.ClassLoader类中的几个基本方法,以便更清楚地了解它的工作原理。

5.1. The loadClass() Method

5.1.loadClass()方法

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

This method is responsible for loading the class given a name parameter. The name parameter refers to the fully qualified class name.

这个方法负责加载一个给定的名字参数的类。name参数指的是完全合格的类名。

The Java Virtual Machine invokes the loadClass() method to resolve class references, setting resolve to true. However, it isn’t always necessary to resolve a class. If we only need to determine if the class exists or not, then the resolve parameter is set to false.

Java虚拟机调用loadClass()方法来解析类引用,将resolve设置为true。然而,并不总是需要解析一个类。如果我们只需要确定该类是否存在,那么将 resolve 参数设置为false

This method serves as an entry point for the class loader.

这个方法是类加载器的一个入口点。

We can try to understand the internal working of the loadClass() method from the source code of java.lang.ClassLoader:

我们可以尝试从java.lang.ClassLoader的源代码中了解loadClass()方法的内部工作:

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

The default implementation of the method searches for classes in the following order:

该方法的默认实现按以下顺序搜索类。

  1. Invokes the findLoadedClass(String) method to see if the class is already loaded.
  2. Invokes the loadClass(String) method on the parent class loader.
  3. Invoke the findClass(String) method to find the class.

5.2. The defineClass() Method

5.2.defineClass()方法

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

This method is responsible for the conversion of an array of bytes into an instance of a class. Before we use the class, we need to resolve it.

这个方法负责将一个字节数组转换为一个类的实例。在我们使用该类之前,我们需要对其进行解析。

If the data doesn’t contain a valid class, it throws a ClassFormatError.

如果数据不包含一个有效的类,它会抛出一个ClassFormatError.

Also, we can’t override this method, since it’s marked as final.

另外,我们不能重写这个方法,因为它被标记为最终方法。

5.3. The findClass() Method

5.3.findClass()方法

protected Class<?> findClass(
  String name) throws ClassNotFoundException

This method finds the class with the fully qualified name as a parameter. We need to override this method in custom class loader implementations that follow the delegation model for loading classes.

这个方法以完全合格的名字作为参数来查找类。我们需要在自定义类加载器的实现中覆盖这个方法,因为它遵循加载类的委托模式。

In addition, loadClass() invokes this method if the parent class loader can’t find the requested class.

此外,loadClass()在父类加载器找不到请求的类时,会调用这个方法。

The default implementation throws a ClassNotFoundException if no parent of the class loader finds the class.

如果类加载器的父类没有找到该类,默认实现会抛出一个ClassNotFoundException

5.4. The getParent() Method

5.4.getParent()方法

public final ClassLoader getParent()

This method returns the parent class loader for delegation.

该方法返回委托的父类加载器。

Some implementations, like the one seen before in Section 2, use null to represent the bootstrap class loader.

一些实现,比如之前在第2节看到的,使用null来表示引导类加载器。

5.5. The getResource() Method

5.5.getResource()方法

public URL getResource(String name)

This method tries to find a resource with the given name.

这个方法试图找到一个具有给定名称的资源。

It will first delegate to the parent class loader for the resource. If the parent is null, the path of the class loader built into the virtual machine is searched. 

它将首先委托给该资源的父类加载器。如果父类是null,将搜索虚拟机中内置的类加载器的路径。

If that fails, then the method will invoke findResource(String) to find the resource. The resource name specified as an input can be relative or absolute to the classpath.

如果失败,那么该方法将调用findResource(String)来查找资源。作为输入指定的资源名称可以是相对于classpath的,也可以是绝对的。

It returns an URL object for reading the resource, or null if the resource can’t be found or the invoker doesn’t have adequate privileges to return the resource.

它返回一个用于读取资源的URL对象,如果找不到资源或调用者没有足够的权限返回资源,则返回空。

It’s important to note that Java loads resources from the classpath.

值得注意的是,Java从classpath中加载资源。

Finally, resource loading in Java is considered location-independent, as it doesn’t matter where the code is running as long as the environment is set to find the resources.

最后,Java中的资源加载被认为是与位置无关的,因为只要环境被设置为寻找资源,代码在哪里运行并不重要。

6. Context Classloaders

6.语境类加载器

In general, context class loaders provide an alternative method to the class-loading delegation scheme introduced in J2SE.

一般来说,上下文类加载器提供了一种替代 J2SE 中引入的类加载委托方案的方法。

Like we learned before, 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.

就像我们之前学到的,JVM中的类加载器遵循一个分层模型,这样每个类加载器都有一个父类,除了引导类加载器之外。

However, sometimes when JVM core classes need to dynamically load classes or resources provided by application developers, we might encounter a problem.

然而,有时当JVM核心类需要动态加载应用开发者提供的类或资源时,我们可能会遇到一个问题。

For example, in JNDI, the core functionality is implemented by the bootstrap classes in rt.jar. But these JNDI classes may load JNDI providers implemented by independent vendors (deployed in the application classpath). This scenario calls for the bootstrap class loader (parent class loader) to load a class visible to the application loader (child class loader).

例如,在JNDI中,核心功能由rt.jar.中的引导类实现。但是这些JNDI类可以加载由独立供应商实现的JNDI提供者(部署在应用程序classpath中)。这种情况要求引导类加载器(父类加载器)加载一个对应用程序加载器(子类加载器)可见的类。

J2SE delegation doesn’t work here, and to get around this problem, we need to find alternative ways of class loading. This can be achieved using thread context loaders.

J2SE委托在这里不起作用,为了解决这个问题,我们需要找到其他的类加载方式。这可以通过线程上下文加载器来实现。

The java.lang.Thread class has a method, getContextClassLoader(), that returns the ContextClassLoader for the particular thread. The ContextClassLoader is provided by the creator of the thread when loading resources and classes.

java.lang.Thread类有一个方法,getContextClassLoader(),它返回特定线程的ContextClassLoaderContextClassLoader是由线程的创建者在加载资源和类时提供的。

If the value isn’t set, then it defaults to the class loader context of the parent thread.

如果该值没有设置,那么它默认为父线程的类加载器上下文。

7. Conclusion

7.结论

Class loaders are essential to execute a Java program. In this article, we provided a good introduction to them.

类加载器对于执行一个Java程序来说是必不可少的。在这篇文章中,我们对它们进行了很好的介绍。

We discussed the different types of class loaders, namely Bootstrap, Extensions, and System class loaders. Bootstrap serves as a parent for all of them, and is responsible for loading the JDK internal classes. Extensions and system, on the other hand, load classes from the Java extensions directory and classpath, respectively.

我们讨论了不同类型的类加载器,即Bootstrap、Extensions和System类加载器。Bootstrap作为所有这些类的父类,负责加载JDK的内部类。另一方面,Extensions和system则分别从Java扩展目录和classpath中加载类。

We also learned how class loaders work and examined some features, such as delegation, visibility, and uniqueness. Then we briefly explained how to create a custom class loader. Finally, we provided an introduction to Context class loaders.

我们还学习了类加载器是如何工作的,并研究了一些特性,如委托、可见性和唯一性。然后我们简要地解释了如何创建一个自定义的类加载器。最后,我们对Context类加载器进行了介绍。

As always, the source code for these examples can be found over on GitHub.

一如既往,这些例子的源代码可以在GitHub上找到