如何在不使用Instrumentation API时获取JVM加载的所有类的名称与包信息
无需Instrumentation启动参数获取JVM所有加载类的方案(Java 9+)
以下是几种不依赖启动时Instrumentation参数,能获取包括启动类加载器、平台类加载器及自定义类加载器加载类的可行方案:
方案1:动态加载Agent获取Instrumentation
虽然Instrumentation通常需要启动参数,但可以通过JDK Attach API在运行时动态加载Agent,从而绕过启动参数限制。
步骤与代码
- 编写Agent类,用于持有Instrumentation实例:
import java.lang.instrument.Instrumentation; public class LoadedClassesAgent { private static Instrumentation instrumentation; public static void agentmain(String args, Instrumentation inst) { instrumentation = inst; } public static Instrumentation getInstrumentation() { return instrumentation; } }
- 打包Agent时需要在MANIFEST.MF中指定Agent信息:
Agent-Class: LoadedClassesAgent Can-Redefine-Classes: true
将上述类打包为agent.jar。
- 主程序中使用Attach API加载Agent并获取所有类:
import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; import java.io.IOException; import java.lang.instrument.Instrumentation; public class LoadedClassesFetcher { public static void main(String[] args) throws Exception { // 获取当前JVM进程ID String pid = String.valueOf(ProcessHandle.current().pid()); // Attach到当前进程 VirtualMachine vm = VirtualMachine.attach(pid); try { // 加载Agent(替换为你的agent.jar实际路径) vm.loadAgent("/path/to/agent.jar"); // 获取Instrumentation实例 Class<?> agentClass = Class.forName("LoadedClassesAgent"); Instrumentation inst = (Instrumentation) agentClass.getMethod("getInstrumentation").invoke(null); // 遍历所有已加载类 Class<?>[] allClasses = inst.getAllLoadedClasses(); for (Class<?> cls : allClasses) { ClassLoader loader = cls.getClassLoader(); String loaderName = loader == null ? "Bootstrap ClassLoader" : loader.getClass().getName(); System.out.printf("类名: %s | 类加载器: %s%n", cls.getName(), loaderName); } } finally { vm.detach(); } } }
注意:需要确保项目依赖jdk.attach模块,编译时可能需要指定模块路径。
方案2:反射访问类加载器内部缓存
Java 9+的类加载器内部维护了已加载类的缓存集合,通过反射可以直接访问这些集合,覆盖所有类加载器的加载类。
代码示例
import java.lang.reflect.Field; import java.util.Map; import java.util.Set; import jdk.internal.loader.BootLoader; import jdk.internal.loader.ClassLoaders; public class LoadedClassesViaReflection { public static void main(String[] args) throws Exception { // 获取启动类加载器加载的类 Field bootClassesField = BootLoader.class.getDeclaredField("classes"); bootClassesField.setAccessible(true); Set<Class<?>> bootClasses = (Set<Class<?>>) bootClassesField.get(null); System.out.println("=== 启动类加载器加载的类 ==="); bootClasses.forEach(cls -> System.out.println(cls.getName())); // 获取平台类加载器加载的类 ClassLoader platformLoader = ClassLoaders.platformClassLoader(); Field platformClassesField = getClassLoaderClassesField(platformLoader.getClass()); platformClassesField.setAccessible(true); Map<String, Class<?>> platformClasses = (Map<String, Class<?>>) platformClassesField.get(platformLoader); System.out.println("\n=== 平台类加载器加载的类 ==="); platformClasses.keySet().forEach(System.out::println); // 获取系统类加载器加载的类 ClassLoader systemLoader = ClassLoaders.systemClassLoader(); Field systemClassesField = getClassLoaderClassesField(systemLoader.getClass()); systemClassesField.setAccessible(true); Map<String, Class<?>> systemClasses = (Map<String, Class<?>>) systemClassesField.get(systemLoader); System.out.println("\n=== 系统类加载器加载的类 ==="); systemClasses.keySet().forEach(System.out::println); // 遍历自定义类加载器及其加载的类 System.out.println("\n=== 自定义类加载器加载的类 ==="); ClassLoader currentLoader = systemLoader; while (currentLoader != null) { currentLoader = currentLoader.getParent(); if (currentLoader == null || currentLoader == platformLoader || currentLoader == BootLoader.getPlatformClassLoader()) { continue; } Field customClassesField = getClassLoaderClassesField(currentLoader.getClass()); customClassesField.setAccessible(true); Map<String, Class<?>> customClasses = (Map<String, Class<?>>) customClassesField.get(currentLoader); System.out.printf("类加载器: %s%n", currentLoader.getClass().getName()); customClasses.keySet().forEach(System.out::println); } } private static Field getClassLoaderClassesField(Class<?> loaderClass) throws NoSuchFieldException { // 适配Java 9+的ClassLoaderImpl父类 if (loaderClass.getSuperclass().getName().equals("jdk.internal.loader.ClassLoaderImpl")) { return loaderClass.getSuperclass().getDeclaredField("classes"); } // 兼容自定义类加载器的常见缓存字段 try { return loaderClass.getDeclaredField("classes"); } catch (NoSuchFieldException e) { return loaderClass.getDeclaredField("loadedClasses"); } } }
注意:运行时需要添加JVM参数--add-exports jdk.base/jdk.internal.loader=ALL-UNNAMED,因为BootLoader和ClassLoaderImpl属于JDK内部模块,默认对外不可访问。
方案3:通过JMX获取已加载类
利用HotSpot的JMX扩展Bean,可以直接获取所有已加载类的名称,再通过类名尝试获取类对象及对应的类加载器。
代码示例
import com.sun.management.HotSpotDiagnosticMXBean; import java.lang.management.ManagementFactory; import java.util.List; public class LoadedClassesViaJMX { public static void main(String[] args) throws Exception { HotSpotDiagnosticMXBean diagnosticBean = ManagementFactory.getPlatformMXBean(HotSpotDiagnosticMXBean.class); // 获取所有已加载且未卸载的类名 List<String> classNames = diagnosticBean.getLoadedClassNames(); for (String className : classNames) { try { Class<?> cls = Class.forName(className); ClassLoader loader = cls.getClassLoader(); String loaderName = loader == null ? "Bootstrap ClassLoader" : loader.getClass().getName(); System.out.printf("类名: %s | 类加载器: %s%n", className, loaderName); } catch (ClassNotFoundException e) { // 部分类可能已被卸载或无法通过当前类加载器访问 System.out.printf("类名: %s | 无法获取类对象(已卸载或不可访问)%n", className); } } } }
说明:该方案无需额外参数,但Class.forName()依赖调用者的类加载器,对于自定义类加载器加载的类,如果当前类加载器无法访问,则只能拿到类名,无法获取类对象和类加载器信息。
内容的提问来源于stack exchange,提问作者vi945786
相关产品推荐
相关产品推荐

