List All Classes Loaded in a Specific Class Loader – 列出在特定类加载器中加载的所有类

最后修改: 2020年 8月 17日

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

1. Overview

1.概述

In this tutorial, we’ll analyze the technique to list all the classes loaded by a specific class loader in Java, using the Java Instrumentation API. We’ll also see how to create and load a Java agent to acquire an Instrumentation instance and invoke the required methods to accomplish our task.

在本教程中,我们将分析使用Java Instrumentation API的技术在Java中列出由特定类加载器加载的所有类。我们还将看到如何创建和加载一个Java代理来获取一个Instrumentation实例并调用所需的方法来完成我们的任务。

2. Class Loaders in Java

2.Java中的类加载器

The class loaders are an integral part of the JRE (Java Runtime Environment). Their job is to dynamically load classes into the Java Virtual Machine. In other words, they load classes into memory on-demand when required by the application. The article on Java class loaders talks about their different types and provides a detailed understanding of how they work.

类加载器是JRE(Java Runtime Environment)的一个组成部分。它们的工作是动态地将类加载到Java虚拟机中。换句话说,它们在应用程序需要时按需将类加载到内存中。关于Java类加载器的文章讲述了它们的不同类型,并详细介绍了它们的工作原理。

3. Using the Instrumentation API

3.使用Instrumentation API

The Instrumentation interface provides the getInitiatedClasses(Classloader loader) method that can be invoked to return an array comprising all the classes loaded by the particular loader. Let’s see how this works.

Instrumentation接口提供了getInitiatedClasses(Classloader loader)方法,可以被调用以 返回一个由特定装载器装载的所有类组成的数组。让我们看看这个方法是如何工作的。

First, we need to create and load an agent to acquire an instance of the Instrumentation interface. A Java agent is a tool to instrument programs running on the JVM (Java Virtual Machine).

首先,我们需要创建并加载一个代理,以获取Instrumentation接口的实例。一个Java代理是一个工具,可以对运行在JVM(Java虚拟机)上的程序进行检测。

In other words, it can add or modify the bytecode of methods for the purpose of gathering data. We’ll require an agent to get a handle on the Instrumentation instance and invoke the required method.

换句话说,它可以为了收集数据而添加或修改方法的字节码。我们需要一个agent来掌握Instrumentation实例并调用所需方法。

There are multiple ways to create and load an agent. In this tutorial, we’ll use the static loading approach using the premain method and the -javaagent option.

有多种方法可以创建和加载一个代理。在本教程中,我们将使用premain方法和-javaagent选项,采用静态加载方式。

3.1. Creating a Java Agent

3.1.创建一个Java代理

To create a Java agent, we need to define the premain method to which the Instrumentation instance will be passed on agent load. Let’s now create the ListLoadedClassesAgent class:

要创建一个Java代理,我们需要定义premain方法,Instrumentation实例将被传递到代理加载时。现在我们来创建ListLoadedClassesAgent类。

public class ListLoadedClassesAgent {

    private static Instrumentation instrumentation;

    public static void premain(String agentArgs, Instrumentation instrumentation) {
        ListLoadedClassesAgent.instrumentation = instrumentation;
    }
}

3.2. Defining the listLoadedClasses Methods

3.2.定义listLoadedClasses方法

In addition to defining the agent, we’ll define and expose a static method to return an array of loaded classes for a given class loader.

除了定义代理之外,我们还将定义并公开一个静态方法,以返回给定类加载器的加载类数组。

Note that if we pass a class loader with a null value to the getInitiatedClasses method, it returns the classes loaded by the bootstrap class loader.

注意,如果我们将一个带有null值的类加载器传递给getInitiatedClasses方法,它将返回由bootstrap类加载器加载的类

Let’s see the code in action:

让我们看看代码的作用。

public static Class<?>[] listLoadedClasses(String classLoaderType) {
    return instrumentation.getInitiatedClasses(
      getClassLoader(classLoaderType));
}

private static ClassLoader getClassLoader(String classLoaderType) {
    ClassLoader classLoader = null;
    switch (classLoaderType) {
        case "SYSTEM":
            classLoader = ClassLoader.getSystemClassLoader();
            break;
        case "EXTENSION":
            classLoader = ClassLoader.getSystemClassLoader().getParent();
            break;
        case "BOOTSTRAP":
            break;
        default:
            break;
    }
    return classLoader;
}

Note that if we’re using Java 9 or above, we can use the getPlatformClassLoader method. This will list the classes loaded by the Platform class loader. In that case, the switch case will also contain:

注意,如果我们使用的是Java 9或更高版本,我们可以使用getPlatformClassLoader方法。这将列出由平台类加载器加载的类。在这种情况下,switch case也将包含。

case "PLATFORM":
    classLoader = ClassLoader.getPlatformClassLoader();
    break;

3.3. Creating the Agent Manifest File

3.3.创建代理清单文件

Now, let’s create a manifest file, MANIFEST.MF, with appropriate attributes for our agent to run, including:

现在,让我们创建一个清单文件,MANIFEST.MF,并为我们的代理运行提供适当的属性,包括。

Premain-Class: com.baeldung.loadedclasslisting.ListLoadedClassesAgent

The complete list of manifest attributes for an agent JAR file is available on the official documentation of the java.lang.instrument package.

代理JAR文件的完整清单属性可在java.lang.instrument软件包的官方文档中找到。

3.4. Loading the Agent and Running the Application

3.4.加载代理并运行应用程序

Let’s now load the agent and run the application. First, we need the agent JAR file with a manifest file containing the Premain-Class information. Additionally, we need the application JAR file with a manifest file containing the Main-Class information. The Launcher class containing the main method will start our application. Then we’ll be able to print the classes loaded by different types of class loaders:

现在让我们加载代理并运行应用程序。首先,我们需要代理JAR文件和一个包含Premain-Class信息的清单文件。此外,我们还需要带有包含Main-Class信息的manifest文件的应用程序JAR文件。包含main方法的Launcher类将启动我们的应用程序。然后我们就可以打印由不同类型的类加载器加载的类。

public class Launcher {

    public static void main(String[] args) {
        printClassesLoadedBy("BOOTSTRAP");
        printClassesLoadedBy("SYSTEM");
        printClassesLoadedBy("EXTENSION");
    }

    private static void printClassesLoadedBy(String classLoaderType) {
        System.out.println(classLoaderType + " ClassLoader : ");
        Class<?>[] classes = ListLoadedClassesAgent.listLoadedClasses(classLoaderType);
        Arrays.asList(classes)
            .forEach(clazz -> System.out.println(clazz.getCanonicalName()));
    }
}

Next, let’s statically load the Java agent and start our application:

接下来,让我们静态地加载Java代理并启动我们的应用程序。

java -javaagent:agent.jar -jar app.jar

After running the above command, we’ll see the output:

运行上述命令后,我们会看到输出。

BOOTSTRAP ClassLoader :
java.lang.ClassValue.Entry[]
java.util.concurrent.ConcurrentHashMap.Segment
java.util.concurrent.ConcurrentHashMap.Segment[]
java.util.StringTokenizer
..............
SYSTEM ClassLoader : 
java.lang.Object[]
java.lang.Object[][]
java.lang.Class
java.lang.Class[]
..............
EXTENSION ClassLoader :
byte[]
char[]
int[]
int[][]
short[]

4. Conclusion

4.总结

In this tutorial, we learned about the technique to list all the classes loaded in a specific class loader.

在本教程中,我们学习了列出特定类加载器中加载的所有类的技术。

First, we created the Java Agent. After that, we defined the method to list the loaded classes using the Java Instrumentation API. Finally, we created the agent manifest files, loaded the agent, and ran our application.

首先,我们创建了Java代理。之后,我们定义了使用JavaInstrumentation API列出加载的类的方法。最后,我们创建了代理清单文件,加载了代理,并运行了我们的应用程序。

As always, the complete source code of the example can be found over on GitHub.

一如既往,该示例的完整源代码可以在GitHub上找到