This is the second post by Mattis, diving deep into JVM specifics. NoClassDefFoundErrors are a drag. The classloader mechanism in the Java specification is very powerful, but it also gives you plenty of ways to mess things up. In which jar did you put that class file, and why isn't your classloader looking in that jar? In rare cases, you might even have an application that works using Sun Java, but throws a NoClassDefFoundError with JRockit. Surely, this must be a JRockit bug? Not necessarily. There is a slight difference in how the two JVMs work that can explain this behaviour, especially if you modify your classloaders during runtime. Let's take an example: In a separate folder "foo", create a file Foo.java:
public class Foo { public Foo () { System.out.println("Foo created"); } }
Now, in your root folder for this experiment, create the file ClasspathTest.java:
import java.io.File; import java.net.URLClassLoader; import java.net.URL; import java.lang.reflect.Method; public class ClasspathTest { private static final Class[] parameters = new Class[]{URL.class}; // Adds a URL to the classpath (by some dubious means) // method.setAccessible(true) is not the trademark of good code public static void addURL(URL u) throws Exception { Method method = URLClassLoader.class.getDeclaredMethod("addURL", parameters); method.setAccessible(true); method.invoke((URLClassLoader) ClassLoader.getSystemClassLoader(), new Object[]{u}); } public static void main(String[] arg) throws Exception{ // Add foo to the classpath, then create a Foo object addURL(new File("foo").toURL()); Foo a = new Foo(); } }
This class has a method "addURL" that basically adds a URL to the classpath of the system classloader. The main method uses this method to first add the folder "foo" to the classpath and then creates a Foo object. When you compile this method, add "foo" to the classpath:
> javac -classpath .;foo Test.java
But when you run the program, don't add foo, simply run
> java Test
Using Sun Java, this will work fine. In the first line of main, we add the foo-folder to the classpath. When we create our first Foo-object, we find the Foo class in the foo folder. Using JRockit however, you get:
Exception in thread "Main Thread" java.lang.NoClassDefFoundError: Foo at ClasspathTest.main(ClasspathTest.java:20)
To understand this behaviour, you have to first understand how Sun and JRockit runs code. Sun Java is an interpreting JVM. This means that the first time you run a method, the JVM will interpret every line step by step. Therefore, Sun will first interpret and run the first line of main, adding "foo" to the classpath, and then the second line, creating the Foo object. JRockit however uses another strategy. The first time a method is run, the entire method is compiled into machine code. To do this, all classes used in the method needs to be resolved first. Therefore, JRockit tries to find the Foo class BEFORE the "foo" folder is added to the classpath, resulting in the NoClassDefFoundError (still thrown just before trying to use the class). So, who is right? Actually, according to the Java spec, both are. Resolving the classes can be done either right before the class is used or as early as during method invocation. For most developers, this is just trivia, but from time to time we see problems with this from customers. The solution? Don't modify your classloaders in the same method as you need the change to load a class. In the example, the following change works fine in both Sun and JRockit:
public static void main(String[] arg) throws Exception{ // Add foo to the classpath, then create a Foo object in another method addURL(new File("foo").toURL()); useFoo(); } public static void useFoo() { Foo a = new Foo(); }
Here, using JRockit, the class is not resolved until the method useFoo is compiled, which will be AFTER "foo" is added to the classpath. /Mattis PS: Adding URLs to the system classloader during runtime might not be a good idea. But when using your own defined classloaders, modifying these during runtime could very well be according to design.