48

For debug reasons, and curiosity, I wish to list all classes loaded to a specific class loader.

Seeing as most methods of a class loader are protected, what is the best way to accomplish what I want?

Thanks!

Jon Seigel
  • 12,251
  • 8
  • 58
  • 92
Yaneeve
  • 4,751
  • 10
  • 49
  • 87

2 Answers2

63

Try this. It's a hackerish solution but it will do.

The field classes in any classloader (under Sun's impl since 1.0) holds hard reference to the classes defined by the loader so they won't be GC'd. You can take a benefit from via reflection.

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

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Vector<Class> classes =  (Vector<Class>) f.get(classLoader);
abarisone
  • 3,707
  • 11
  • 35
  • 54
bestsss
  • 11,796
  • 3
  • 53
  • 63
  • On Android I get `No field classes in class Ljava/lang/ClassLoader` – Nathan H Sep 20 '16 at 08:00
  • 1
    @NathanH, technically, Android is not even Java (until it gets OpenJDK), but as I've mentioned it's a hackerish solution relying on the way classloader has been implemented for over 17 years. There is no guarantee the proposed hack would work in the future, either. Also on Android I can't really see running middleware alike code (with dynamic classloading), so there should be no need to get the loaded classes. – bestsss Dec 03 '16 at 23:22
  • Android can do dynamic classloading, I do it all the time with [JavaX](http://javax.ai1.lol). In fact, I load all my main programs that way (the app is just a stub). – Stefan Reich May 20 '17 at 11:17
  • For a while, fields of ClassLoader class are not accessible via reflection. Even with `setAccessible` method. See https://stackoverflow.com/a/17805624/9710708 – William Jan 18 '22 at 17:42
33

Instrumentation.getInitiatedClasses(ClassLoader) may do what you want.

According to the docs:

Returns an array of all classes for which loader is an initiating loader.

I'm not sure what "initiating loader" means though. If that does not give the right result try using the getAllLoadedClasses() method and manually filtering by ClassLoader.


How to get an instance of Instrumentation

Only the agent JAR (which is separate from the application JAR) can get an instance of the Instrumentation interface. A simple way to make it available to the application is to create an agent JAR containing one class with a premain method that does nothing but save a reference to the Instrumentation instance in the system properties.

Example agent class:

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");

}

Example manifest:

Manifest-Version: 1.0
Premain-Class: InstrumentHook

The resulting JAR must then be referenced by the application and specified on the command line (with the -javaagent option) when launching the application. It might be loaded twice in different ClassLoaders, but that is not a problem since the system Properties is a per-process singleton.

Example application class

public class Main {
    public static void main(String[] args) {
        Instrumentation inst = InstrumentHook.getInstrumentation();
        for (Class<?> clazz: inst.getAllLoadedClasses()) {
            System.err.println(clazz.getName());
        }
    }
}
parivana
  • 299
  • 2
  • 10
finnw
  • 47,861
  • 24
  • 143
  • 221
  • But how to get an Instrumentation instance? – Arne Burmeister Apr 21 '10 at 09:09
  • 2
    @Arne Burmeister, see the package description: http://java.sun.com/javase/6/docs/api/java/lang/instrument/package-summary.html – finnw Apr 21 '10 at 09:13
  • so if I get it correctly, I have to write an agent, which contains a method signatured as "public static void premain(String agentArgs, Instrumentation inst);" and then invoke the Instrumentation methods via the inst ref? – Yaneeve Apr 21 '10 at 11:29
  • 2
    @Yaneeve, yes. Added an example to my answer. – finnw Apr 21 '10 at 14:28
  • 3
    *Initiating classloader* is the 1st classloader that was requested to load a class, regardless which classloader defines the class (even if the bootstrap does). The notion is active for `Class.forName` [i.e. going through the native code] but not for direct calls for `ClassLoader.loadClass`. `ClassLoader.findLoadedClass` checks exactly that, if a class with the given name has been recorded in the booking as initialed by the classloader. – bestsss Apr 21 '12 at 18:33
  • On a side note: the answer is incorrect since almost never you need the initiating classloader from some hierarchy but the the defining classloader that stored the defined classes in a field Vector/**/ – bestsss Apr 21 '12 at 18:35
  • @bestsss not the first loader, but any classloader through which the class has been requested. There can be more than one initiating class loader for a class. Note how the documentation says “…for which loader is **an** initiating loader” rather than “… **the** initiating loader”. – Holger Apr 29 '19 at 15:35