You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在不使用Instrumentation API时获取JVM加载的所有类的名称与包信息

无需Instrumentation启动参数获取JVM所有加载类的方案(Java 9+)

以下是几种不依赖启动时Instrumentation参数,能获取包括启动类加载器、平台类加载器及自定义类加载器加载类的可行方案:

方案1:动态加载Agent获取Instrumentation

虽然Instrumentation通常需要启动参数,但可以通过JDK Attach API在运行时动态加载Agent,从而绕过启动参数限制。

步骤与代码

  1. 编写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;
    }
}
  1. 打包Agent时需要在MANIFEST.MF中指定Agent信息:
Agent-Class: LoadedClassesAgent
Can-Redefine-Classes: true

将上述类打包为agent.jar。

  1. 主程序中使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 19:35:23