Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When a class is loaded in JVM

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.

like image 985
user1745356 Avatar asked Oct 23 '14 10:10

user1745356


2 Answers

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.

like image 194
mindex Avatar answered Oct 12 '22 13:10

mindex


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.

like image 37
Holger Avatar answered Oct 12 '22 14:10

Holger