CHAPTER 12: Execution Previous
Previous
Java Language
Java Language
Index
Index
Next
Next

12.4 Initialization of Classes and Interfaces

12.4.1 When Initialization Occurs , 12.4.2 Detailed Initialization Procedure , 12.4.3 Implications for Code Generation

Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class. Initialization of an interface consists of executing the initializers for fields (constants) declared there.

Before a class is initialized, its superclass must be initialized, but interfaces implemented by the class need not be initialized. Similarly, the superinterfaces of an interface need not be initialized before the interface is initialized.


12.4.1 When Initialization Occurs

A class or interface type T will be initialized at its first active use, which occurs if:

All other uses of a type are passive uses.

The intent here is that a class or interface type has a set of initializers that put it in a consistent state, and that this state is the first state that is observed by other classes. The static initializers and class variable initializers are executed in textual order, and may not refer to class variables declared in the class whose declarations appear textually after the use, even though these class variables are in scope (S8.5). This restriction is designed to detect, at compile time, most circular or otherwise malformed initializations.

As shown in an example in S8.5, the fact that initialization code is unrestricted allows examples to be constructed where the value of a class variable can be observed when it still has its initial default value, before its initializing expression is evaluated, but such examples are rare in practice. (Such examples can be also constructed for instance variable initialization; see the example at the end of S12.5). Java provides the full power of the language in these initializers; programmers must exercise some care. This power places an extra burden on code generators, but this burden would arise in any case because Java is concurrent (S12.4.3).

Before a class is initialized, its superclasses are initialized, if they have not previously been initialized.

Thus, the test program:


class Super {
	static { System.out.print("Super "); }
}

class One {
	static { System.out.print("One "); }
}

class Two extends Super {
	static { System.out.print("Two "); }
}

class Test {
	public static void main(String[] args) {
		One o = null;
		Two t = new Two();
		System.out.println((Object)o == (Object)t);
	}
}

prints:

Super Two false

The class One is never initialized, because it not used actively and therefore is never linked to. The class Two is initialized only after its superclass Super has been initialized.

A reference to a field is an active use of only the class or interface that actually declares it, even though it might be referred to through the name of a subclass, a subinterface, or a class that implements an interface. The test program:


class Super { static int taxi = 1729; }

class Sub extends Super {
	static { System.out.print("Sub "); }
}

class Test {
	public static void main(String[] args) {
		System.out.println(Sub.taxi);
	}
}

prints only:

1729

because the class Sub is never initialized; the reference to Sub.taxi is a reference to a field actually declared in class Super and is not an active use of the class Sub .

Initialization of an interface does not, of itself, require initialization of any of its superinterfaces. Thus, the test program:


interface I {
	int i = 1, ii = Test.out("ii", 2);
}

interface J extends I {
	int j = Test.out("j", 3), jj = Test.out("jj", 4);
}

interface K extends J {
	int k = Test.out("k", 5);
}

class Test {

	public static void main(String[] args) {
		System.out.println(J.i);
		System.out.println(K.j);
	}

	static int out(String s, int i) {
		System.out.println(s + "=" + i);
		return i;
	}
}

produces the output:


1
j=3
jj=4
3

The reference to J.i is to a field that is a compile-time constant; therefore, it does not cause I to be initialized. The reference to K.j is a reference to a field actually declared in interface J that is not a compile-time constant; this causes initialization of the fields of interface J , but not those of its superinterface I , nor those of interface K . Despite the fact that the name K is used to refer to field j of interface J , interface K is not actively used.


12.4.2 Detailed Initialization Procedure

Because Java is multithreaded, initialization of a class or interface requires careful synchronization, since some other thread may be trying to initialize the same class or interface at the same time. There is also the possibility that initialization of a class or interface may be requested recursively as part of the initialization of that class or interface; for example, a variable initializer in class A might invoke a method of an unrelated class B, which might in turn invoke a method of class A. The implementation of the Java Virtual Machine is responsible for taking care of synchronization and recursive initialization by using the following procedure. It assumes that the Class object has already been verified and prepared, and that the Class object contains state that indicates one of four situations:

The procedure for initializing a class or interface is then as follows:

  1. Synchronize (S14.17) on the Class object that represents the class or interface to be initialized. This involves waiting until the current thread can obtain the lock for that object (S17.13).
  2. If initialization is in progress for the class or interface by some other thread, then wait (S20.1.6) on this Class object (which temporarily releases the lock). When the current thread awakens from the wait , repeat this step.
  3. If initialization is in progress for the class or interface by the current thread, then this must be a recursive request for initialization. Release the lock on the Class object and complete normally.
  4. If the class or interface has already been initialized, then no further action is required. Release the lock on the Class object and complete normally.
  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. Otherwise, record the fact that initialization of the Class object is now in progress by the current thread and release the lock on the Class object.
  7. Next, if the Class object represents a class rather than an interface, and the superclass of this class has not yet been initialized, then recursively perform this entire procedure for the superclass. If necessary, verify and prepare the superclass first. If the initialization of the superclass completes abruptly because of a thrown exception, then lock this Class object, label it erroneous, notify all waiting threads (S20.1.10), release the lock, and complete abruptly, throwing the same exception that resulted from initializing the superclass.
  8. Next, execute either the class variable initializers and static initializers of the class, or the field initializers of the interface, in textual order, as though they were a single block, except that final class variables and fields of interfaces whose values are compile-time constants are initialized first (S8.3.2.1, S9.3.1, S13.4.8).
  9. If the execution of the initializers completes normally, then lock this Class object, label it fully initialized, notify all waiting threads (S20.1.10), release the lock, and complete this procedure normally.
  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.
  11. Lock the Class object, label it erroneous, notify all waiting threads (S20.1.10), 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 of Java, a exception during class initialization was ignored, rather than causing an ExceptionInInitializerError as described here.)


12.4.3 Implications for Code Generation

Code generators need to preserve the points of possible initialization of a class or interface, inserting an invocation of the initialization procedure just described. If this initialization procedure completes normally and the Class object is fully initialized and ready for use, then the invocation of the initialization procedure is no longer necessary and it may be eliminated from the code-for example, by patching it out or otherwise regenerating the code.

Compile-time analysis may, in some cases, be able to eliminate many of the checks that a type has been initialized from the generated code, if an initialization order for a group of related types can be determined. Such analysis must, however, fully account for the fact that Java is concurrent and that initialization code is unrestricted.

Top© 1996 Sun Microsystems, Inc. All rights reserved.