1. 概述

本文将深入探讨如何使用 Java 的 Instrumentation API,来列出 某个特定类加载器(ClassLoader)所加载的所有类。我们会通过创建并加载一个 Java Agent 来获取 Instrumentation 实例,并调用其提供的方法完成目标。

这种方式在排查类加载问题、分析内存占用或做 JVM 层面的监控时非常实用。✅

2. Java 中的类加载器

类加载器是 JRE(Java 运行时环境)的核心组件之一,负责在运行时 动态地将类加载到 JVM 中。它们按需加载类,只有当程序真正需要某个类时才会触发加载过程。

Java 提供了多种类型的类加载器,典型的有:

  • Bootstrap ClassLoader:加载 JVM 核心类(如 java.lang.*),通常由 C/C++ 实现。
  • Platform ClassLoader(Java 9+):负责加载平台模块(java.se 等)。
  • System (Application) ClassLoader:加载应用 classpath 下的类。

类加载器之间存在父子关系,遵循“委托模型”(Delegation Model)。这部分机制在《Java 类加载器详解》一文中已有充分说明,这里不再赘述。

3. 使用 Instrumentation API

Instrumentation 接口提供了 getInitiatedClasses(ClassLoader) 方法,可以 返回指定类加载器已加载的所有类的数组。这是实现我们目标的关键。

但问题来了:如何获取 Instrumentation 实例?⚠️
它不能通过常规方式实例化,必须通过 Java Agent 机制,在 JVM 启动时注入。

3.1. 创建 Java Agent

要使用 Instrumentation,必须编写一个 Java Agent,并定义 premain 方法。JVM 在启动时会优先加载 agent,并将 Instrumentation 实例传入。

下面是一个简单的 agent 实现:

public class ListLoadedClassesAgent {

    private static Instrumentation instrumentation;

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

premain 方法是 agent 的入口点,签名必须匹配。
❌ 如果方法签名错误,agent 将无法加载。

3.2. 定义 listLoadedClasses 方法

我们在 agent 中添加一个静态方法,用于根据类加载器类型返回已加载的类数组:

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":
            // null 表示 Bootstrap ClassLoader
            break;
        case "PLATFORM":
            // Java 9+
            classLoader = ClassLoader.getPlatformClassLoader();
            break;
        default:
            break;
    }
    return classLoader;
}

📌 注意:

  • 传入 nullgetInitiatedClasses 会返回 Bootstrap ClassLoader 加载的类。
  • Java 9 引入了 Platform ClassLoader,需单独处理。

3.3. 创建 Agent 的 MANIFEST 文件

Agent 必须打包为 JAR,并在 MANIFEST.MF 中声明 Premain-Class

Premain-Class: com.baeldung.loadedclasslisting.ListLoadedClassesAgent

其他可选属性如 Can-Redefine-ClassesCan-Retransform-Classes 根据需要添加。

完整规范参考官方文档:java.lang.instrument

3.4. 加载 Agent 并运行程序

我们需要两个 JAR:

  1. agent.jar:包含 agent 类和正确 MANIFEST
  2. app.jar:主程序,包含 Main-Class

主程序示例:

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

启动命令(关键):

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

运行后输出示例:

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[]

⚠️ 踩坑提醒:

  • 确保 agent.jar 路径正确,否则会报 Error opening zip file
  • MANIFEST 中的类名必须带完整包路径,且不能有空格或换行错误。
  • premain 方法必须是 public static,参数顺序不能错。

4. 总结

本文通过 Java Agent 和 Instrumentation API,实现了 列出任意类加载器已加载类 的功能。

核心步骤总结如下:

  1. ✅ 编写 agent,实现 premain 方法获取 Instrumentation 实例
  2. ✅ 调用 getInitiatedClasses(ClassLoader) 获取类数组
  3. ✅ 打包 agent 并通过 -javaagent 参数加载
  4. ✅ 在主程序中调用 agent 暴露的方法

该技术在 JVM 调优、类冲突排查、热部署分析等场景中非常有用。简单粗暴,但威力十足。💪

完整示例代码见 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-jvm-2


原始标题:List All Classes Loaded in a Specific Class Loader

» 下一篇: Java周报,347