I got two examples:
Example 1:
public class A {
}
public class B {
public void m(A a) {
}
}
public class C {
public static void main(String[] args) {
B b = new B();
System.out.println("hello!");
}
}
Compile all three classes. Remove A.class. Run main. No exception is thrown.
Example 2:
public class D {
}
public class E {
public void omg(D d) {
}
public static void main(String[] args) {
E e = new E();
}
}
Compile the classes. Remove D.class. Run main method.
Exception in thread "main" java.lang.NoClassDefFoundError: D
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
at java.lang.Class.getMethod0(Unknown Source)
at java.lang.Class.getMethod(Unknown Source)
at sun.launcher.LauncherHelper.getMainMethod(Unknown Source)
at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Caused by: java.lang.ClassNotFoundException: D
at java.net.URLClassLoader$1.run(Unknown Source)
Why? D is never referenced.
Both are allowed by JavaVM specification. In Chapter 5. Loading, Linking, and Initializing we have:
For example, a Java Virtual Machine implementation may choose to resolve each symbolic reference in a class or interface individually when it is used ("lazy" or "late" resolution), or to resolve them all at once when the class is being verified ("eager" or "static" resolution).
My wild guess is that Sun/Oracle chose to do the "static" resolution for initial("main") class because it's likely that methods in the main class are to be invoked very soon.
Your class has a reference to the class D
within the method public void omg(D d)
.
Normally the JVM from Sun/Oracle uses lazy resolution, so it wouldn’t matter as long as you don’t use that method, however, you can see from the stack trace that the main method is searched via the reflective operation Class.getMethod
which makes the difference.
You can verify this with the following code:
public class B {
public static void main(String[] args) {
D.main(args);
}
}
class D {
public void foo(E e) {}
public static void main(String[] args) {
System.out.println("hello world");
}
}
class E { }
Here, removing E.class
after compiling and running java B
will not produce an error. Now insert a reflective method lookup into the code:
public class B {
public static void main(String[] args) {
try {
D.class.getMethod("main", String[].class);
} catch(NoSuchMethodException ex) {
ex.printStackTrace();
}
D.main(args);
}
}
class D {
public void foo(E e) {}
public static void main(String[] args) {
System.out.println("hello world");
}
}
class E { }
Now, removing the class E
after compiling produces java.lang.NoClassDefFoundError: E
when you run with java B
. So the manually triggered method lookup reproduces the behavior as in your original code example though class D
is not the main
class here.
Note that you can fix the problem by removing the public
modifier from the method foo
. The reason is that Class.getMethod
only considers public
methods and skips all others.
This also applies to your original code example: removing public
from the method omg
will make the NoClassDefFoundError
disappear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With