Back to your Class roots, continued

Find runtime origins for all loaded classes

July 25, 2003

Q: Can you find the complete set of loaded classes from within a Java program and then determine from which file each class was loaded?

A:

From the previous Java Q&A installment, we know that given a Class object, you can usually trace it back to its origins, via the getClassLocation() method explained in that article.

Thus, we just need to find the set of classes currently loaded in the JVM. This, however, is the difficult part: The java.lang.ClassLoader API is not designed to provide any information about classes loaded by a given classloader instance, and Java classloaders are not required to register themselves in any programmatically accessible way. (A Sun Microsystems-compatible JVM will also show classes as they load if you use a -verbose:class JVM option. However, you cannot access this information programmatically.)

Let's tackle this problem in stages. I concentrate on pure Java ideas first and then mention other options later.

All classes loaded by a given classloader

It is not widely known that every java.lang.ClassLoader instance in a Sun-compatible Java 2 Platform, Standard Development Kit (J2SDK) has a private field that is a Vector of classes loaded and defined by that loader instance:

    /*
     * The classes loaded by this classloader. This table's only purpose
     * is to keep the classes from being garbage collected until the loader
     * is garbage collected.
     */
    private Vector classes = new Vector();

Observe that I say "defined" above: this is important. The classloader in question will not necessarily define every class requested via loadClass(): the class can get delegated to the loader's parent instead. Only the classes whose bytes are submitted to java.lang.ClassLoader.defineClass() by a given classloader instance shows up in its classes vector. Also, the JVM directly populates this field, so it always contains valid information (it even contains dynamically created classes such as dynamic proxies).

The following code (available with this article's download) exploits the information in the classes field:

public abstract class ClassScope
{
    public static Class [] getLoadedClasses (final ClassLoader loader)
    {
        if (loader == null) throw new IllegalArgumentException ("null input: loader");
        if (CLASSES_VECTOR_FIELD == null)
            throw new RuntimeException ("ClassScope::getLoadedClasses() cannot" +
            " be used in this JRE", CVF_FAILURE);
        
        try
        {
            final Vector classes = (Vector) CLASSES_VECTOR_FIELD.get (loader);
            if (classes == null) return EMPTY_CLASS_ARRAY;
            
            final Class [] result;
            
            // Note: Vector is synchronized in Java 2, which helps us make
            // the following into a safe critical section:
            
            synchronized (classes)
            {
                result = new Class [classes.size ()];
                classes.toArray (result);
            }
            
            return result;
        }
        // This should not happen if  was successful:
        catch (IllegalAccessException e)
        {
            e.printStackTrace (System.out);
            
            return EMPTY_CLASS_ARRAY;
        }
    }
    
    
    private static final Field CLASSES_VECTOR_FIELD; // Set in  [can be null]
    
    private static final Class [] EMPTY_CLASS_ARRAY = new Class [0];
    private static final Throwable CVF_FAILURE; // Set in 
    
    static
    {
        Throwable failure = null;
        
        Field tempf = null;
        try
        {
            // This can fail if this is not a Sun-compatible JVM
            // or if the security is too tight:
            
            tempf = ClassLoader.class.getDeclaredField ("classes");
            if (tempf.getType () != Vector.class)
                throw new RuntimeException ("not of type java.util.Vector: " +
                tempf.getType ().getName ());
            tempf.setAccessible (true);
        }
        catch (Throwable t)
        {
            failure = t;
        }
        CLASSES_VECTOR_FIELD = tempf;
        CVF_FAILURE = failure;
    }
} // End of class

Note that getLoadedClasses()'s result is a snapshot: the list of loaded classes varies depending on when you call the method. (The list grows over time, however, when the method is invoked on the same classloader instance.)

Of course, this code is not 100 percent portable: it depends on gaining access to a private ClassLoader field. Although this field is present in many modern JVMs, there is no guarantee it will continue to exist. Furthermore, I use java.lang.reflect.AccessibleObject.setAccessible() to read a private field, which doesn't work if the runtime security is sufficiently tight.

For all these reasons, the ClassScope class prepares to be nonfunctional once loaded. But it won't throw any class initialization errors at load time and only complains on active method use.

Despite its limitations, ClassScope can be quite handy for debugging classloading. If I add an overloaded version of getLoadedClasses() to aggregate results from several classloaders:

    public static Class [] getLoadedClasses (final ClassLoader [] loaders)
    {
        if (loaders == null) throw new IllegalArgumentException ("null input: loaders");
        
        final List /* Class */ resultList = new LinkedList (); 
        
        for (int l = 0; l < loaders.length; ++ l)
        {
            final ClassLoader loader = loaders [l];
            if (loader != null)
            {
                final Class [] classes = getLoadedClasses (loaders [l]);     
                resultList.addAll (Arrays.asList (classes));
            }
        }
        
        final Class [] result = new Class [resultList.size ()];
        resultList.toArray (result);
        
        return result;
    }

then it's easy to monitor application and extension classes in various simple programs. For example, this test:

public class Main
{
    public static void main (final String [] args)
    {
        // Load some dummy classes just to make things more interesting:
        new Object () {};
        new Object () {};
        
        final ClassLoader appLoader = ClassLoader.getSystemClassLoader ();
        final ClassLoader extLoader = appLoader.getParent ();
        final ClassLoader [] loaders = new ClassLoader [] {appLoader, extLoader};
        
        final Class [] classes = ClassScope.getLoadedClasses (loaders);
        for (int c = 0; c < classes.length; ++ c)
        {
            final Class cls = classes [c];
            System.out.println ("[" + cls.getName () + "]:");
            System.out.println ("  loaded by [" + cls.getClassLoader ().getClass ().getName () + "]");
            System.out.println ("  from [" + ClassScope.getClassLocation (cls) + "]");
        }
    }
} // End of class

prints the following:

>java -cp bin Main
[Main]:
  loaded by [sun.misc.Launcher$AppClassLoader]
  from [file:/ ... /bin/Main.class]
[Main]:
  loaded by [sun.misc.Launcher$AppClassLoader]
  from [file:/ ... /bin/Main.class]
[Main]:
  loaded by [sun.misc.Launcher$AppClassLoader]
  from [file:/ ... /bin/Main.class]
[com.vladium.utils.ClassScope]:
  loaded by [sun.misc.Launcher$AppClassLoader]
  from [file:/ ... /bin/com/vladium/utils/ClassScope.class]

All classes loaded by all relevant classloaders

If you know anything about classloading, you should now understand that ClassScope can't see everything. The Main test above does not show bootstrap (rt.jar) classes, and it won't track classes defined by any custom classloader that might be created by the profiled application.

Monitoring bootstrap classes is nearly impossible in pure Java. However, ClassScope's saving grace is that Java programmers are often only interested in their own application classes (e.g., the ones loaded via -classpath).

The problem with custom classloaders is less fundamental: it is simply an issue of knowing what all these loaders are and somehow retrieving their references to pass into getLoadedClasses().

In general a JVM can have any number of classloader instances, so how do you find them all? I know about some standard ones, and I used them in Main above, but what about the rest?

First realize you shouldn't always be interested in all classloaders in the JVM. Many exist for a good reason, such as to isolate applications in the same JVM (e.g., Web applications in the same Web container). But, if you can determine only those classloaders responsible for loading your particular set of classes in a JVM, you should be content.

As it happens, this is quite possible. Starting with the current class, you can figure out its classloader, then that classloader's parent loader, and so on. What's more, you can repeat the same process for the class that called the current class and so on until you reach the end of the current method's call stack. The resulting set of classloaders should be pretty encompassing.

The static ClassScope.getCallerClassLoaderTree() method performs the necessary magic (download the source code to see full details). This version of Main shows how, from any point in your code, you can construct such a relevant classloader set without manually enumerating classloaders:

public class Main
{
    public static void main (final String [] args)
    {
        // Load some dummy classes just to make things more interesting:
        new Object () {};
        new Object () {};
        final ClassLoader [] loaders = ClassScope.getCallerClassLoaderTree ();
        
        final Class [] classes = ClassScope.getLoadedClasses (loaders);
        for (int c = 0; c < classes.length; ++ c)
        {
            final Class cls = classes [c];
            System.out.println ("[" + cls.getName () + "]:");
            System.out.println ("  loaded by [" + cls.getClassLoader ().getClass ().getName () + "]");
            System.out.println ("  from [" + ClassScope.getClassLocation (cls) + "]");
        }
    }
} // End of class

If you turn this code into a JavaServer Pages (JSP) page for Apache Tomcat, for example, you should see classes loaded by org.apache.catalina.loader.StandardClassLoader and org.apache.catalina.loader.WebappClassLoader, among others.

All classes loaded by all JVM classloaders

If you really insist on monitoring classes loaded by absolutely every possible classloader in the JVM, it is hard to do so in pure Java.

You might try forcing every classloader instance to register itself in some global data structure. For example, if you modify both java.lang.ClassLoader constructors:

    protected ClassLoader() {
    SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkCreateClassLoader();
    }
    this.parent = getSystemClassLoader();
    initialized = true;
    
    ClassLoaderRegistry.register (this);
    }
    protected ClassLoader(ClassLoader parent) {
    ...  
    ClassLoaderRegistry.register (this);
    }

to call into the following class:

public abstract class ClassLoaderRegistry
{
    public static void register (final ClassLoader loader)
    {
        if (loader != null)
        {
            synchronized (CLASSLOADER_SET)
            {
                CLASSLOADER_SET.put (loader, null);
            }
        }
    }
    
    public static ClassLoader [] getClassLoaders ()
    {
        final List /* ClassLoader */ resultList = new LinkedList ();
        
        synchronized (CLASSLOADER_SET)
        {
            // We are using a weak map: be careful when traversing the key set
            // [it's unsafe to use resultList.addAll(CLASSLOADER_SET.keySet())]
            
            for (Iterator keys = CLASSLOADER_SET.keySet ().iterator (); keys.hasNext (); )
            {
                // Note that WeakHashMap guarantees that the weak key will not be
                // cleared between hasNext() and next():
                resultList.add (keys.next ());
            }
        }
        
        final ClassLoader [] result = new ClassLoader [resultList.size ()];
        resultList.toArray (result);
        
        return result; 
    }
    
    
    // This field is used as a 'weak set' to avoid interfering with class
    // and classloader unloading and garbage collection: 
    private static final WeakHashMap /* ClassLoader->null */ CLASSLOADER_SET
        = new WeakHashMap ();
} // End of class

you will in fact track every classloader instance created in the JVM. This happens because every classloader constructor eventually chains all the way down to one of the above two constructors. Then you can use ClassLoaderRegistry.getClassLoaders() followed by the usual ClassScope.getLoadedClasses() to get a full class listing snapshot.

Of course, you need to deploy both the modified java.lang.ClassLoader and ClassLoaderRegistry in the JVM's bootstrap classpath (using the -Xbootclasspath/p: JVM option) to make this work. This approach is probably only for dedicated Java hackers.

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more