Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why NoClassDefFoundError caused by static field initialization failure?

Here is a interesting java question.

the following simple java program contains static field initialized by a method statically. Actually, I force the method which calculate the intiailize value to raise a NullPointException, When I access such a static field, a NoClassDefFoundError will raised. it seems the VM treat the Class is not complete.

But when I access the Class, it still available;

Does anyone knows why?

class TestClass {
    public static TestClass instance = init();

    public static TestClass init() {
       String a = null;
       a.charAt(0); //force a null point exception;
       return new TestClass();
    }
}

class MainClass {
    static public void main(String[] args) {
       accessStatic(); // a ExceptionInInitializerError raised cause by NullPointer
       accessStatic(); //now a NoClassDefFoundError occurs;

       // But the class of TestClass is still available; why?
       System.out.println("TestClass.class=" + TestClass.class);
    }

    static void accessStatic() {
        TestClass a;

        try {
            a = TestClass.instance; 
        } catch(Throwable e) {
            e.printStackTrace();
        }
    }   
}
like image 203
ext2 Avatar asked Jun 15 '11 02:06

ext2


3 Answers

Yes, that's usually why NoClassDefFoundError is raised. It's evilly named, that's all. It should've been named as "class init failed exception" or something.

Becuase of the misleading name, java programmers who got this error wasted hundreds of man years trying to figure out why the class cannot be found.

Whenever you see this exception, you should check the log upwards, and try to find out the root cause when the class failed to init.

like image 39
irreputable Avatar answered Oct 23 '22 21:10

irreputable


The answer to such questions is usually buried somewhere in the specs... (§12.4.2)

What happens when classes are initialized:

Steps 1-4 are somewhat unrelated to this question. Step 5 here is what triggers the exception:

5. If the Class object is in an erroneous state, then initialization is not possible. Release the lock on the Class object and throw a NoClassDefFoundError.

6-8 continue the initialization, 8 executes the initializers, and what usually happens is in step 9:

9. If the execution of the initializers completes normally, then lock this Class object, label it fully initialized, notify all waiting threads, release the lock, and complete this procedure normally.

But we got an error in the initializer so:

10. Otherwise, the initializers must have completed abruptly by throwing some exception E. If the class of E is not Error or one of its subclasses, then create a new instance of the class ExceptionInInitializerError, with E as the argument, and use this object in place of E in the following step. But if a new instance of ExceptionInInitializerError cannot be created because an OutOfMemoryError occurs, then instead use an OutOfMemoryError object in place of E in the following step.

Yep, we see an ExceptionInInitializerError b/c of the null pointer exception.

11. Lock the Class object, label it erroneous, notify all waiting threads, release the lock, and complete this procedure abruptly with reason E or its replacement as determined in the previous step. (Due to a flaw in some early implementations, a exception during class initialization was ignored, rather than causing an ExceptionInInitializerError as described here.)

And then the class is marked erroneous which is why we get the exception from step 5 the second time.


The surprising part is the third printout which shows that TestClass.class in MainClass actually holds a reference to a physical Class object.

Probably because TestClass still exists, it's just marked erroneous. It has been already loaded and verified.

like image 136
trutheality Avatar answered Oct 23 '22 22:10

trutheality


When I access such a static field, a NoClassDefFoundError will raised. it seems the VM treat the Class is not complete.

That is correct ...

But when I access the Class, it still available

Yes.

The class loader has not tried to remove the broken class because:

  • it would be difficult to do,
  • it would be extremely difficult to do safely,
  • it would leave the JVM in a state where an application could easily waste lots of time repeatedly loading and reloading broken code, and
  • the specs say (or at least imply) that it shouldn't; see other answers for details.

To get into a state where this inconsistency is visible, your application has to catch ClassDefNotFoundError (or a superclass) and attempted to recover from it. It is a well documented fact that Error exceptions are generally not recoverable; i.e. if you attempt to recover, the JVM may end up in an inconsistent state. That is what has happened here ... with respect to the classes that were being loaded / initialized.

like image 4
Stephen C Avatar answered Oct 23 '22 21:10

Stephen C