如何列出特定类加载器中加载的所有类

48

出于调试目的和好奇心,我想列出加载到特定类加载器的所有类。

由于大多数类加载器的方法都是受保护的,因此,实现我想要的功能的最佳方法是什么?

谢谢!


2个回答

63

试试这个。这是一种黑客式的解决方案,但它会起作用。

在任何类加载器(自1.0以来,在Sun的实现下)中,classes字段保存了对由加载器定义的类的强引用,因此它们不会被垃圾回收。您可以通过反射获益。

Field f = ClassLoader.class.getDeclaredField("classes");
f.setAccessible(true);

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Vector<Class> classes =  (Vector<Class>) f.get(classLoader);

在Android上我得到了“在类Ljava/lang/ClassLoader中没有字段类”。 - Nathan H
1
@NathanH,从技术上讲,Android甚至不是Java(直到它采用OpenJDK),但正如我所提到的,这是一种依赖于类加载器实现方式已有17年之久的黑客式解决方案。不能保证该提议的黑客解决方案将来会起作用。此外,在Android上,我真的看不到运行类似中间件代码(具有动态类加载)的必要性,因此不需要获取加载的类。 - bestsss
Android可以进行动态类加载,我经常使用JavaX来实现。事实上,我通过这种方式加载所有的主程序(应用程序只是一个存根)。 - Stefan Reich
有一段时间,ClassLoader类的字段无法通过反射访问。 即使使用“setAccessible”方法也不行。 请参见https://dev59.com/WnTYa4cB1Zd3GeqPycPe#17805624。 - William

34

Instrumentation.getInitiatedClasses(ClassLoader) 可以实现你想要的功能。

根据文档:

返回一个由加载器作为起始加载器的所有类组成的数组。

我不确定“起始加载器”是什么意思,如果这不能给出正确结果,请尝试使用 getAllLoadedClasses() 方法,并手动按 ClassLoader 进行过滤。


如何获取 Instrumentation 实例

只有代理 JAR(与应用程序 JAR 分离)才能获得 Instrumentation 接口的实例。一种简单的方法是创建一个代理 JAR,其中包含一个带有 premain 方法的类,该方法仅在系统属性中保存对 Instrumentation 实例的引用。

代理类示例:

public class InstrumentHook {

    public static void premain(String agentArgs, Instrumentation inst) {
        if (agentArgs != null) {
            System.getProperties().put(AGENT_ARGS_KEY, agentArgs);
        }
        System.getProperties().put(INSTRUMENTATION_KEY, inst);
    }

    public static Instrumentation getInstrumentation() {
        return (Instrumentation) System.getProperties().get(INSTRUMENTATION_KEY);
    }

    // Needn't be a UUID - can be a String or any other object that
    // implements equals().    
    private static final Object AGENT_ARGS_KEY =
        UUID.fromString("887b43f3-c742-4b87-978d-70d2db74e40e");

    private static final Object INSTRUMENTATION_KEY =
        UUID.fromString("214ac54a-60a5-417e-b3b8-772e80a16667");

}

示例清单:

Manifest-Version: 1.0
Premain-Class: InstrumentHook

生成的 JAR 文件必须由应用程序引用并在启动应用程序时(使用 -javaagent 选项)在命令行上指定。它可能会在不同的 ClassLoader 中加载两次,但这不是问题,因为系统 Properties 是每个进程单例。
示例应用程序类
public class Main {
    public static void main(String[] args) {
        Instrumentation inst = InstrumentHook.getInstrumentation();
        for (Class<?> clazz: inst.getAllLoadedClasses()) {
            System.err.println(clazz.getName());
        }
    }
}

但是如何获取一个Instrumentation实例? - Arne Burmeister
2
@Arne Burmeister,请查看包描述:http://java.sun.com/javase/6/docs/api/java/lang/instrument/package-summary.html - finnw
2
@Yaneeve,是的。我在我的回答中添加了一个示例。 - finnw
3
“启动类加载器”是第一个被请求加载类的类加载器,不管哪个类加载器定义了这个类(即使是引导类加载器)。这个概念适用于 Class.forName (即通过本地代码进行加载),但不适用于直接调用 ClassLoader.loadClassClassLoader.findLoadedClass 的作用是检查在记录中是否有给定名称的类已经被启动类加载器初始化。 - bestsss
顺便说一句:答案是不正确的,因为几乎从来不需要某个层次结构中的初始类加载器,而是需要将定义的类存储在Vector/<Class>/字段中的定义类加载器。 - bestsss
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接