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;
}
📌 注意:
- 传入
null
给getInitiatedClasses
会返回 Bootstrap ClassLoader 加载的类。 - Java 9 引入了 Platform ClassLoader,需单独处理。
3.3. 创建 Agent 的 MANIFEST 文件
Agent 必须打包为 JAR,并在 MANIFEST.MF
中声明 Premain-Class
:
Premain-Class: com.baeldung.loadedclasslisting.ListLoadedClassesAgent
其他可选属性如 Can-Redefine-Classes
、Can-Retransform-Classes
根据需要添加。
完整规范参考官方文档:java.lang.instrument
3.4. 加载 Agent 并运行程序
我们需要两个 JAR:
agent.jar
:包含 agent 类和正确 MANIFESTapp.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,实现了 列出任意类加载器已加载类 的功能。
核心步骤总结如下:
- ✅ 编写 agent,实现
premain
方法获取Instrumentation
实例 - ✅ 调用
getInitiatedClasses(ClassLoader)
获取类数组 - ✅ 打包 agent 并通过
-javaagent
参数加载 - ✅ 在主程序中调用 agent 暴露的方法
该技术在 JVM 调优、类冲突排查、热部署分析等场景中非常有用。简单粗暴,但威力十足。💪
完整示例代码见 GitHub:https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-jvm-2