|
|
Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs
Page 2 of 3
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]
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.
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.
Archived Discussions (Read only)