The Artima Developer Community
Sponsored Link

Chapter 7 of Inside the Java Virtual Machine
The Lifetime of a Type
by Bill Venners

<<  Page 4 of 6  >>

Advertisement

Initialization

The final step required to ready a class or interface for its first active use is initialization, the process of setting class variables to their proper initial values. As used here, a "proper" initial value is the programmer's desired starting value for a class variable. A proper initial value contrasts with the default initial value given to class variables during preparation. As described above, the virtual machine assigns default values based only on each variable's type. Proper initial values, by contrast, are based on some master plan known only to the programmer.

In Java code, a proper initial value is specified via a class variable initializer or static initializer. A class variable initializer is an equals sign and expression next to a class variable declaration, as in:

// On CD-ROM in file classlife/ex1/Example1a.java
class Example1a {

    // "= 3 * (int) (Math.random() * 5.0)" is the class variable
    // initializer
    static int size = 3 * (int) (Math.random() * 5.0);
}

A static initializer is a block of code introduced by the static keyword, as in:

// On CD-ROM in file classlife/ex1/Example1b.java
class Example1b {

    static int size;

    // This is the static initializer
    static {

        size = 3 * (int) (Math.random() * 5.0);
    }
}

All the class variable initializers and static initializers of a type are collected by the Java compiler and placed into one special method. For classes, this method is called the class initialization method; for interfaces, the interface initialization method. In Java class files for both classes and interfaces, this method is named "". Regular methods of a Java application cannot invoke a method. This kind of method can only be invoked by the Java virtual machine, which invokes it to set a type's static variables to their proper initial values.

Initialization of a class consists of two steps:

  1. Initializing the class's direct superclass (if any), if the direct superclass hasn't already been initialized
  2. Executing the class's class initialization method, if it has one
When initializing a class's direct superclass, the same two steps listed above must be followed. As a result, the first class that will be initialized will always be Object, then all the classes on down the inheritance hierarchy to the class being actively used. Superclasses will be initialized before subclasses.

Initialization of an interface does not require initialization of its superinterfaces. Initialization of an interface consists of only one step:

The code of a () method does not explicitly invoke a superclass's () method. Before a Java virtual machine invokes the () method of a class, therefore, it must make certain the () methods of superclasses have been executed.

Java virtual machines must also make sure the initialization process is properly synchronized. If multiple threads need to initialize a class, only one thread should be allowed to perform the initialization while the other threads wait. After the active thread completes the initialization process, it must notify any waiting threads. See Chapter 20, "Thread Synchronization," for information about synchronization, wait and notify.

The () Method

As mentioned above, Java compilers place the code for class variable initializers and static initializers into the () method of the class file in the order in which they appear in the class or interface declaration. For example, given this class:

// On CD-ROM in file classlife/ex1/Example1c.java
class Example1c {

    static int width;
    static int height = (int) (Math.random() * 2.0);

    // This is the static initializer
    static {

        width = 3 * (int) (Math.random() * 5.0);
    }
}
The Java compiler generates the following () method:
// The code for height's class variable initializer begins here
                           // Invoke Math.random(), which will push
                           // a double return value
 0 invokestatic #6 <Method double random()>
 3 ldc2_w #8 <Double 2.0>  // Push double constant 2.0
 6 dmul                    // Pop two doubles, multiply, push result
 7 d2i                     // Pop double, convert to int, push int
                           // Pop int, store into class variable
                           // height
 8 putstatic #5 <Field int height>

// The code for the static initializer begins here
11 iconst_3                // Push int constant 3
                           // Invoke Math.random(), which will push
                           // a double return value
12 invokestatic #6 <Method double random()>
15 ldc2_w #10 <Double 5.0>  // Push double constant 5.0
18 dmul                    // Pop two doubles, multiply, push result
19 d2i                     // Pop double, convert to int, push int
20 imul                    // Pop two ints, multiply, push int result
                           // Pop int, store into class variable
                           // width
21 putstatic #7 <Field int width>
24 return                  // Return void from <clinit> method
This () method first executes the code for Example1c's only class variable initializer, which initializes height, then executes the code for the static initializer, which initializes width. The initialization is done in this order because the class variable initializer appears textually before the static initializer in the source code of the Example1c class.

Not all classes will necessarily have a () method in their class file. If a class declares no class variables or static initializers, it won't have a () method. If a class declares class variables, but doesn't explicitly initialize them with class variable initializers or static initializers, it won't have a () method. If a class contains only class variable initializers for static final variables, and those class variable initializers use compile-time constant expressions, that class won't have a () method. Only those classes that actually require Java code to be executed to initialize class variables to proper initial values will have a class initialization method.

Here's an example of a class that won't be awarded a () method by the Java compiler:

// On CD-ROM in file classlife/ex1/Example1d.java
class Example1d {

    static final int angle = 35;
    static final int length = angle * 2;
}
Class Example1d declares two constants, angle and length, and initializes them with expressions that are compile-time constants. The compiler knows that angle represents the value 35 and length represents the value 70. When the Example1d class is loaded by a Java virtual machine, angle and length are not stored as class variables in the method area. As a result, no () method is needed to initialize them. The angle and length fields are not class variables, they are constants, which are treated specially by the Java compiler.

Instead of treating Example1d's angle and length fields as class variables, the Java compiler places the constant int values they represent into the constant pool or bytecode streams of any class that uses them. For example, if a class uses Example1d's angle field, that class will not have in its constant pool a symbolic reference to the angle field of class Example1d. Instead, the class will have operands embedded in its bytecode streams that have the value 35. If the constant value of angle were outside the range of a short (-32,768 to 32,767), say 35,000, the class would have a CONSTANT_Integer_info entry in its constant pool with the value of 35,000.

Here's a class that uses both a constant and a class variable from other classes:

// On CD-ROM in file classlife/ex1/Example1e.java
class Example1e {

    // The class variable initializer for symbolicRef uses a symbolic
    // reference to the size class variable of class Example1a
    static int symbolicRef = Example1a.size;

    // The class variable initializer for localConst doesn't use a
    // symbolic reference to the length field of class Example1d.
    // Instead, it just uses a copy of the constant value 70.
    static int localConst = Example1d.length * (int) (Math.random()
        * 3.0);
}

The Java compiler generates the following () method for class Example1e:

// The code for symbolicRef's class variable initializer begins here:
                           // Push int value from Example1a.size.
                           // This getstatic instruction refers to a
                           // symbolic reference to Example1a.size.
 0 getstatic #9 <Field int size>
                           // Pop int, store into class variable
                           // symbolicRef
 3 putstatic #10 <Field int symbolicRef>

// The code for localConst's class variable intializer begins here:
                           // Expand byte operand to int, push int
                           // result. This is the local copy of
 6 bipush 70               // Example1d's length constant, 70.
                           // Invoke Math.random(), which will push
                           // a double return value
 8 invokestatic #8 <Method double random()>
11 ldc2_w #11 <Double 3.0> // Push double constant 3.0
14 dmul                    // Pop two doubles, multiply, push result
15 d2i                     // Pop double, convert to int, push int
16 imul                    // Pop two ints, multiply, push int result
                           // Pop int, store into class variable
                           // localConst
17 putstatic #7 <Field int localConst>
20 return                  // Return void from <clinit> method
The getstatic instruction at offset zero uses a symbolic reference (in constant pool entry nine) to the size field of class Example1a. The bipush instruction at offset six is followed by a byte that contains the constant value represented by Example1d.length. Example1e's constant pool contains no symbolic reference to anything in class Example1d.

Interfaces may also be awarded a () method in the class file. All fields declared in an interface are implicitly public, static, and final and must be initialized with a field initializer. If an interface has any field initializers that don't resolve at compile-time to a constant, that interface will have a () method. Here's an example:

// On CD-ROM in file classlife/ex1/Example1f.java
interface Example1f {

    int ketchup = 5;
    int mustard = (int) (Math.random() * 5.0);
}
The Java compiler generates the following () method for interface Example1f:
// The code for mustard's class variable initializer begins here
                          // Invoke Math.random(), which will push
                          // a double return value
 0 invokestatic #6 <Method double random()>
 3 ldc2_w #7 <Double 5.0> // Push double constant 2.0
 6 dmul                   // Pop two doubles, multiply, push result
 7 d2i                    // Pop double, convert to int, push int
                          // Pop int, store into class variable
                          // mustard
 8 putstatic #5 <Field int mustard>
11 return                 // Return void from <clinit> method
Note that only the mustard field is initialized by this () method. Because the ketchup field is initialized to a compile-time constant, it is treated specially by the compiler. Although types that use Example1f.mustard will contain a symbolic reference to the field, types that use Example1f.ketchup will contain a local copy of ketchup's constant value, 5.

Active versus Passive Use

As mentioned previously, the Java virtual machine initializes types on their first active use. Only six activities constitute an active use: creating a new instance of a class, invoking a static method declared in a class, accessing a non-constant static field declared in a class or interface, the invocation of certain reflective methods in the Java API, the initialization of a subclass of a class, and the designation of a class as the initial class when a Java virtual machine starts up.

A use of a non-constant static field is an active use of only the class or interface that actually declares the field. For example, a field declared in a class may be referred to via a subclass. A field declared in an interface may be referred to via a subinterface or class that implements the interface. These are passive uses of the subclass, subinterface, or class that implements the interface--uses that won't trigger their initialization. They are an active use only of the class or interface in which the field is actually declared. Here's an example that illustrates this principle:

// On CD-ROM in file classlife/ex2/NewParent.java
class NewParent {

    static int hoursOfSleep = (int) (Math.random() * 3.0);

    static {
        System.out.println("NewParent was initialized.");
    }
}

// On CD-ROM in file classlife/ex2/NewbornBaby.java
class NewbornBaby extends NewParent {

    static int hoursOfCrying = 6 + (int) (Math.random() * 2.0);

    static {
        System.out.println("NewbornBaby was initialized.");
    }
}

// On CD-ROM in file classlife/ex2/Example2.java
class Example2 {

    // Invoking main() is an active use of Example2
    public static void main(String[] args) {

        // Using hoursOfSleep is an active use of NewParent,
        // but a passive use of NewbornBaby
        int hours = NewbornBaby.hoursOfSleep;
        System.out.println(hours);
    }

    static {
        System.out.println("Example2 was initialized.");
    }
}

In the above example, executing main() of Example2 causes only Example2 and NewParent to be initialized. NewbornBaby is not initialized and need not be loaded. The following text is printed to the standard output:

Example2 was initialized.
NewParent was initialized.
2

A use of a field that is both static and final, and initialized by a compile-time constant expression, is not an active use of the type that declares the field. As mentioned above, the Java compiler resolves references to such fields to a local copy of the constant value that resides either in the referring class's constant pool, in its bytecode streams, or both. Here's an example that illustrates this special treatment of static final fields:

// On CD-ROM in file classlife/ex3/Angry.java
interface Angry {

    String greeting = "Grrrr!";

    int angerLevel = Dog.getAngerLevel();
}

// On CD-ROM in file classlife/ex3/Dog.java
class Dog {

    static final String greeting = "Woof, woof, world!";

    static {
        System.out.println("Dog was initialized.");
    }

    static int getAngerLevel() {

        System.out.println("Angry was initialized");
        return 1;
    }
}

// On CD-ROM in file classlife/ex3/Example3.java
class Example3 {

    // Invoking main() is an active use of Example3
    public static void main(String[] args) {

        // Using Angry.greeting is a passive use of Angry
        System.out.println(Angry.greeting);


        // Using Dog.greeting is a passive use of Dog
        System.out.println(Dog.greeting);
    }

    static {
        System.out.println("Example3 was initialized.");
    }
}

Running the Example3 application yields the following output:

Example3 was initialized.
Grrrr!
Woof, woof, world!

Had Angry been initialized, the string "Angry was initialized." would have been written to the standard output. Likewise, had Dog been initialized, the string "Dog was initialized." would have been written to the standard output. As you can see from the above output, neither interface Angry or class Dog were ever initialized during the execution of the Example3 application.

For more information about this special treatment of static final variables, see Chapter 8, "The Linking Model."

<<  Page 4 of 6  >>


Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use