views:

550

answers:

4

Is it possible to know whether a Java class has been loaded, without attempting to load it? Class.forName attempts to load the class, but I don't want this side effect. Is there another way?

(I don't want to override the class loader. I'm looking for a relatively simple method.)

+13  A: 

You can use the findLoadedClass(String) method in ClassLoader. It returns null if the class is not loaded.

Aleksi
Unfortunately, findLoadedClasses is protected, which means you will have to subclass ClassLoader to be able to access it.
staffan
Is it possible to call this method via reflection? (Supressing security checks)
Hosam Aly
+2  A: 

If you're in control of the source of the classes for which you are interested in whether they are loaded or not (which I doubt, but you don't state in your question), then you could register your load in a static initializer.

public class TestLoaded {
    public static boolean loaded = false;
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(loaded);
        ClassToTest.reportLoaded();
        System.out.println(loaded);
    }
    static class ClassToTest {
        static {
            System.out.println("Loading");
            TestLoaded.loaded = true;
        }
        static void reportLoaded() {
            System.out.println("Loaded");
        }
    }
}

Output:

false
Loading
Loaded
true
Stephen Denne
Thank you. This really is a nice way to use for my own classes. But I was hoping to find something more generic, in case I don't control the classes.
Hosam Aly
+5  A: 

(Thanks to Aleksi) This code:

public class TestLoaded {
     public static void main(String[] args) throws Exception {
          java.lang.reflect.Method m = ClassLoader.class.getDeclaredMethod("findLoadedClass", new Class[] { String.class });
          m.setAccessible(true);
          ClassLoader cl = ClassLoader.getSystemClassLoader();
          Object test1 = m.invoke(cl, "TestLoaded$ClassToTest");
          System.out.println(test1 != null);
          ClassToTest.reportLoaded();
          Object test2 = m.invoke(cl, "TestLoaded$ClassToTest");
          System.out.println(test2 != null);
     }
     static class ClassToTest {
          static {
               System.out.println("Loading " + ClassToTest.class.getName());
          }
          static void reportLoaded() {
               System.out.println("Loaded");
          }
     }
}

Produces:

false
Loading TestLoaded$ClassToTest
Loaded
true

Note that the example classes are not in a package. The full binary name is required.

An example of a binary name is "java.security.KeyStore$Builder$FileBuilder$1"

Stephen Denne
Just a side note: Remember to use the canonical name package.subpackage.ClassName when calling the method. This answer does not demonstrate this requirement as the class is not in a package.
Aleksi
Thanks for adding that note. While you do need the package, it is the "binary name" that is required. The canonical name of the class shown in the example is "TestLoaded.ClassToTest", which differs from the binary name... I'll edit the answer to clarify/link.
Stephen Denne
Thanks @spdenne and @Aleksi. I had tried to do this but it failed, although I'm not sure why. Your code above does work for me. As a side-note, you may want to check McDowell's reply about instrumentation. Thanks a lot.
Hosam Aly
+4  A: 

One way to do this would be to write a Java agent using the instrumentation API. This would allow you to record the loading of classes by the JVM.

public class ClassLoadedAgent implements ClassFileTransformer {

    private static ClassLoadedAgent AGENT = null;

    /** Agent "main" equivalent */
    public static void premain(String agentArguments,
      Instrumentation instrumentation) {
     AGENT = new ClassLoadedAgent();
     for (Class<?> clazz : instrumentation.getAllLoadedClasses()) {
      AGENT.add(clazz);
     }
     instrumentation.addTransformer(AGENT);
    }

    private final Map<ClassLoader, Set<String>> classMap = new WeakHashMap<ClassLoader, Set<String>>();

    private void add(Class<?> clazz) {
     add(clazz.getClassLoader(), clazz.getName());
    }

    private void add(ClassLoader loader, String className) {
     synchronized (classMap) {
      System.out.println("loaded: " + className);
      Set<String> set = classMap.get(loader);
      if (set == null) {
       set = new HashSet<String>();
       classMap.put(loader, set);
      }
      set.add(className);
     }
    }

    private boolean isLoaded(String className, ClassLoader loader) {
     synchronized (classMap) {
      Set<String> set = classMap.get(loader);
      if (set == null) {
       return false;
      }
      return set.contains(className);
     }
    }

    @Override
    public byte[] transform(ClassLoader loader, String className,
      Class<?> classBeingRedefined, ProtectionDomain protectionDomain,
      byte[] classfileBuffer) throws IllegalClassFormatException {
     add(loader, className);
     return classfileBuffer;
    }

    public static boolean isClassLoaded(String className, ClassLoader loader) {
     if (AGENT == null) {
      throw new IllegalStateException("Agent not initialized");
     }
     if (loader == null || className == null) {
      throw new IllegalArgumentException();
     }
     while (loader != null) {
      if (AGENT.isLoaded(className, loader)) {
       return true;
      }
      loader = loader.getParent();
     }
     return false;
    }

}

META-INF/MANIFEST.MF:

Manifest-Version: 1.0 
Premain-Class: myinstrument.ClassLoadedAgent

The downside is that you have to load the agent when you start the JVM:

java -javaagent:myagent.jar ....etcetera
McDowell
Thanks! It's the first time I get introduced to the instrumentation API. So which way would you advise to use? Instrumentation or reflection over ClassLoader.findLoadedClass?
Hosam Aly
Use the one that works best for your application. I would not expect either to work in 100% of situations.
McDowell