The Artima Developer Community
Sponsored Link

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

<<  Page 5 of 6  >>

Advertisement

The Lifetime of an Object

Once a class has been loaded, linked, and initialized, it is ready for use. The program can access its static fields, invoke its static methods, or create instances of it. This section describes class instantiation and initialization, activities that take place at the beginning of an object's lifetime, and garbage collection and finalization, activities that mark the end of an object's lifetime.

Class Instantiation

In Java programs, classes can be instantiated explicitly or implicitly. The four ways a class can be instantiated explicitly are with the new operator, by invoking newInstance()on a Class or java.lang.reflect.Constructor object, by invoking clone() on any existing object, or by deserializing an object via the getObject() method of classjava.io.ObjectInputStream. Here is an example showing three ways to create a new class instance:

// On CD-ROM in file classlife/ex4/Example4.java
class Example4 implements Cloneable {

    Example4() {
        System.out.println("Created by invoking newInstance()");
    }

    Example4(String msg) {
        System.out.println(msg);
    }

    public static void main(String[] args)
        throws ClassNotFoundException, InstantiationException,
        IllegalAccessException, CloneNotSupportedException {

        // Create a new Example4 object with the new operator
        Example4 obj1 = new Example4("Created with new.");

        // Get a reference to the Class instance for Example4, then
        // invoke newInstance() on it to create a new Example4 object
        Class myClass = Class.forName("Example4");
        Example4 obj2 = (Example4) myClass.newInstance();

        // Make an identical copy of the the second Example4 object
        Example4 obj3 = (Example4) obj2.clone();
    }
}

When executed, the Example4 application prints this output:

Created with new.
Created by invoking newInstance()

Besides the four ways listed previously to explicitly instantiate objects in Java source code, there are several situations in which objects will be instantiated implicitly--without an explicit new, newInstance(), clone(), or ObjectInputStream.readObject() appearing in the source.

Possibly the first implicitly instantiated objects of any Java application are the String objects that hold the command line arguments. References to these objects, one for each command-line argument, are delivered in the String array passed as the sole parameter to the main() method of every application.

Two other ways a class can be instantiated implicitly involve the process of class loading. First, for every type a Java virtual machine loads, it implicitly instantiates a new Class object to represent that type. Second, when the Java virtual machine loads a class that contains CONSTANT_String_info entries in its constant pool, it may instantiate new String objects to represent those constant string literals. The process of transforming a CONSTANT_String_info entry in the method area to a String instance on the heap is part of the process of constant pool resolution. This process is described in detail in Chapter 8, "The Linking Model."

Another way objects can be created implicitly is through the process of evaluating an expression that involves the string concatenation operator. If such an expression is not a compile-time constant, intermediate String and StringBuffer objects will be created in the process of evaluating the expression. Here's an example:

// On CD-ROM in file classlife/ex5/Example5.java
class Example5 {

    public static void main(String[] args) {

        if (args.length < 2) {
            System.out.println("Must enter any two args.");
            return;
        }

        System.out.println(args[0] + args[1]);
    }
}
javac generates these bytecodes for Example5's main() method:
 0 aload_0      // Push the objref from loc var 0 (args)
 1 arraylength  // Pop arrayref, calc array length, push int length
 2 iconst_2     // Push int constant 2
                // Pop 2 ints, compare, branch if (length >= 2) to
 3 if_icmpge 15 // offset 15.
                // Push objref from System.out
 6 getstatic #11 <Field java.io.PrintStream out>
                // Push objref of string literal
 9 ldc #1 <String "Must enter any two args.">
                // Pop objref to String param, objref to System.out,
                // invoke println()
11 invokevirtual #12 <Method void println(java.lang.String)>
14 return       // Return void from main()
                // Push objref from System.out
15 getstatic #11 <Field java.io.PrintStream out>

// The string concatenation operation begins here
                // Allocate mem for new StringBuffer object, and
                // initialize mem to default initial values, push
                // objref to new object
18 new #6 <Class java.lang.StringBuffer>
21 dup          // Duplicate objref to StringBuffer object
22 aload_0      // Push ref from loc var 0 (args)
23 iconst_0     // Push int constant 0
                // Pop int, arrayref, push String at arrayref[int],
24 aaload       // which is args[0]
                // Pop objref, invoke String's class method
                // valueOf(), passing it the objref to the args[0]
                // String object. valueOf() calls toString() on the
                // ref, and returns (and pushes) the result, which
                // happens to be the original args[0] String. In this
                // case, the stack will look precisely the same
                // before and after this instruction is executed.
                // Thus here, the 1.1 javac compiler has
                // over-enthusiastically generated an unnecessary
                // instruction.
25 invokestatic #14 <Method java.lang.String valueOf(
        java.lang.Object)>
                // Pop objref to args[0] String, objref of the
                // StringBuffer object, invoke <init>() method on the
                // StringBuffer object passing the args[0] objref as
                // the only parameter.
28 invokespecial #9 <Method java.lang.StringBuffer(java.lang.String)>
31 aload_0      // Push objref from loc var 0 (args)
32 iconst_1     // Push int constant 1
                // Pop int, arrayref, push String at arrayref[int],
33 aaload       // which is args[1]
                // Pop objref to args[1] String, objref of the
                // StringBuffer object (there's still another objref
                // to this same object on the stack because of the
                // dup instruction above), invoke append() method on
                // StringBuffer object, passing args[1] as the only
                // parameter. append() will return an objref to this
                // StringBuffer object, which will be pushed back
                // onto the stack.
34 invokevirtual #10 <Method java.lang.StringBuffer
append(java.lang.String)>
                // Pop objref to StringBuffer (pushed by append()),
                // invoke toString() on it, which returns the value
                // of the StringBuffer as a String object. Push
                // objref of String object.
37 invokevirtual #13 <Method java.lang.String toString()>
// The string concatenation operation is now complete

                // Pop objref of concatenated String, objref of
                // System.out that was pushed by the getstatic
                // instruction at offset 15. Invoke println() on
                // System.out, passing the concatenated String as
                // the only parameter.
40 invokevirtual #12 <Method void println(java.lang.String)>
43 return       // Return void from main()

The bytecodes for Example5's main() method contain three implicitly generated String objects and one implicitly generated StringBuffer object. References to two of the String objects appear as arguments passed to main() in the args array, which are pushed onto the stack by the aaload instructions at offset 24 and 33. The StringBuffer is created with the new instruction at offset 18 and initialized with the invokespecial instruction at offset 28. The final String, which represents the concatenation of args[0] and args[1], is created by calling toString() on the StringBuffer object via the invokevirtual instruction at offset 37.

When the Java virtual machine creates a new instance of a class, either implicitly or explicitly, it first allocates memory on the heap to hold the object's instance variables. Memory is allocated for all variables declared in the object's class and in all its superclasses, including instance variables that are hidden. As described in Chapter 5, "The Java Virtual Machine," memory for other implementation-dependent components of an object's image on the heap, such as a pointer to class data in the method area, are also likely allocated at this point. As soon as the virtual machine has set aside the heap memory for a new object, it immediately initializes the instance variables to default initial values. These are the same values shown above in Table 7-1 as default initial values for class variables.

Once the virtual machine has allocated memory for the new object and initialized the instance variables to default values, it is ready to give the instance variables their proper initial values. The Java virtual machine uses one of three techniques to do this, depending upon how the object is being created. If the object is being created because of a clone() invocation, the virtual machine copies the values of the instance variables of the object being cloned into the new object. If the object is being deserialized via a readObject() invocation on an ObjectInputStream, the virtual machine initializes non-transient instance variables of the object from values read from the input stream. Otherwise, the virtual machine invokes an instance initialization method on the object. The instance initialization method initializes the object's instance variables to their proper initial values.

The Java compiler generates at least one instance initialization method for every class it compiles. In the Java class file, the instance initialization method is named "<init>." For each constructor in the source code of a class, the Java compiler generates one <init>() method. If the class declares no constructors explicitly, the compiler generates a default no-arg constructor that just invokes the superclass's no-arg constructor. As with any other constructor, the compiler creates an <init>() method in the class file that corresponds to this default constructor.

An <init>() method can contain three kinds of code: an invocation of another <init>() method, code that implements any instance variable initializers, and code for the body of the constructor. If a constructor begins with an explicit invocation of another constructor in the same class (a this() invocation) its corresponding <init>() method will be composed of two parts:

If a constructor does not begin with a this() invocation and the class is not Object, the <init>() method will have three components: If a constructor does not begin with a this() invocation and the class is Object, the first component in the above list is missing. Because Object has no superclass, its <init>() method's can't begin with a superclass <init>() method invocation.

If a constructor begins with an explicit invocation of a superclass constructor ( a super() invocation), its <init>() method will invoke the corresponding superclass <init>() method. For example, if a constructor begins with an explicit invocation of the "super(int, String) constructor," the corresponding <init>() method will begin by invoking the superclass's "<init>(int, String)" method. If a constructor does not begin with an explicit this() or super() invocation, the corresponding <init>() method will invoke the superclass's no-arg <init>() method by default.

Here's an example with three constructors, numbered one through three:

// On CD-ROM in file classlife/ex6/Example6.java
class Example6 {

    private int width = 3;

    // Constructor one:
    // This constructor begins with a this() constructor invocation,
    // which gets compiled to a same-class <init>() method
    // invocation.
    Example6() {
        this(1);
        System.out.println("Example6(), width = " + width);
    }

    // Constructor two:
    // This constructor begins with no explicit invocation of another
    // constructor, so it will get compiled to an <init>() method
    // that begins with an invocation of the superclass's no-arg
    // <init>() method.
    Example6(int width) {
        this.width = width;
        System.out.println("Example6(int), width = " + width);
    }

    // Constructor three:
    // This constructor begins with super(), an explicit invocation
    // of the superclass's no-arg constructor. Its <init>() method
    // will begin with an invocation of the superclass's no-arg
    // <init>() method.
    Example6(String msg) {
        super();
        System.out.println("Example6(String), width = " + width);
        System.out.println(msg);
    }

    public static void main(String[] args) {
        String msg
            = "The Agapanthus is also known as Lily of the Nile.";
        Example6 one = new Example6();
        Example6 two = new Example6(2);
        Example6 three = new Example6(msg);
    }
}

When executed, the Example6 application prints this output:

Example6(int), width = 1
Example6(), width = 1
Example6(int), width = 2
Example6(String), width = 3
The Agapanthus is also known as Lily of the Nile.

The bytecodes for Example6's no-arg <init>() method (the <init>() method that corresponds to constructor one) are:

// The first component, the same-class <init>() invocation, begins
// here:
 0 aload_0      // Push the objref from loc var 0 (this)
 1 iconst_1     // Push int constant 1
                // Pop int and objref, invoke <init>() method on
                // objref (this), passing the int (a 1) as the
                // only parameter.
 2 invokespecial #12 <Method Example6(int)>

// The second component, the body of the constructor, begins
// here:
                // Push objref from System.out
 5 getstatic #16 <Field java.io.PrintStream out>
                // Allocate mem for new StringBuffer object, and
                // initialize mem to default initial values, push
                // objref to new object
 8 new #8 <Class java.lang.StringBuffer>
11 dup          // Duplicate objref to StringBuffer object
                // Push objref to String literal from constant pool
12 ldc #1 <String "Example6(), width = ">
                // Pop objref to literal String, pop objref of the
                // StringBuffer object, invoke <init>() method on the
                // StringBuffer object passing the args[0] objref as
                // the only parameter.
14 invokespecial #14 <Method java.lang.StringBuffer(
        java.lang.String)>
17 aload_0      // Push objref from loc var 0 (this)
                // Pop this reference, Push int value of width field
18 getfield #19 <Field int width>
                // Pop int (width), pop objref (StringBuffer object),
                // invoke append() on StringBuffer object passing the
                // width int as the only parameter. append() will add
                // the string representation of the int to the end of
                // the buffer, and return an objref to the same
                // StringBuffer object.
21 invokevirtual #15 <Method java.lang.StringBuffer append(int)>
                // Pop objref to StringBuffer (pushed by append()),
                // invoke toString() on it, which returns the value
                // of the StringBuffer as a String object. Push
                // objref of String object.
24 invokevirtual #18 <Method java.lang.String toString()>
                // Pop objref of String, pop objref of System.out
                // that was pushed by the getstatic instruction at
                // offset 5. Invoke println() on System.out,
                // passing the String as the only parameter:
                // System.out.println("Example6(), width = "
                //     + width);
27 invokevirtual #17 <Method void println(java.lang.String)>
30 return       // Return void from <init>()

Note that the <init>() method for constructor one begins with an invocation of a same-class <init>() method, then executes the body of the corresponding constructor. Because the constructor begins with a this() invocation, its corresponding <init>() method doesn't contain bytecodes for the instance variable initializer.

The bytecodes for Example6's <init>() method that takes an int parameter (the <init>() method that corresponds to constructor two) is:

// The first component, the superclass <init>() invocation, begins
// here:
 0 aload_0      // Push the objref from loc var 0 (this)
                // Pop objref (this), invoke the superclass's
                // no-arg<init>() method on objref.
 1 invokespecial #11 <Method java.lang.Object()>

// The second component, the instance variable initializers, begins
// here:
 4 aload_0      // Push the objref from loc var 0 (this)
 5 iconst_3     // Push int constant 3
                // Pop int (3), pop objref (this), store 3 into
                // width instance variable of this object
 6 putfield #19 <Field int width>

// The third component, the body of the constructor, begins
// here:
 9 aload_0      // Push the objref from loc var 0 (this)
10 iload_1      // Push int from loc var 1 (int param width)
                // Pop int (param width), pop objref (this), store
                // int param value into width field of this object:
                // this.width = width
11 putfield #19 <Field int width>
                // Push objref from System.out
14 getstatic #16 <Field java.io.PrintStream out>
                // Allocate mem for new StringBuffer object, and
                // initialize mem to default initial values, push
                // objref to new object
17 new #8 <Class java.lang.StringBuffer>
20 dup          // Duplicate objref to StringBuffer object
                // Push objref to String literal from constant pool
21 ldc #3 <String "Example6(int), width = ">
                // Pop objref to literal String, pop objref of the
                // StringBuffer object, invoke <init>() method on the
                // StringBuffer object passing the args[0] objref as
                // the only parameter.
23 invokespecial #14 <Method java.lang.StringBuffer(
        java.lang.String)>
26 iload_1      // Push int from loc var 1 (int param width)
                // Pop int (width), pop objref (StringBuffer object),
                // invoke append() on StringBuffer object passing the
                // width int as the only parameter. append() will add
                // the string representation of the int to the end of
                // the buffer, and return an objref to the same
                // StringBuffer object.
27 invokevirtual #15 <Method java.lang.StringBuffer append(int)>
                // Pop objref to StringBuffer (pushed by append()),
                // invoke toString() on it, which returns the value
                // of the StringBuffer as a String object. Push
                // objref of String object.
30 invokevirtual #18 <Method java.lang.String toString()>
                // Pop objref of String, pop objref of System.out
                // that was pushed by the getstatic instruction at
                // offset 14. Invoke println() on System.out,
                // passing the String as the only parameter:
                // System.out.println("Example6(int), width = "
                //     + width);
33 invokevirtual #17 <Method void println(java.lang.String)>
36 return       // Return void from <init>()

The <init>() method for constructor two has three components. First it has an invocation of the superclass's (Object's) no-arg <init>() method. The compiler generated this invocation by default, because no explicit super() invocation appears as the first statement in the body of constructor two. Following the superclass <init>() invocation is the second component: the bytecodes for width's instance variable initializer. Third, the <init>() method contains the bytecodes for the body of constructor two.

The bytecodes for Example6's <init>() method that takes a String parameter (the <init>() method that corresponds to constructor three) are:

// The first component, the superclass <init>() invocation, begins
// here:
 0 aload_0      // Push the objref from loc var 0 (this)
                // Pop objref (this), invoke the superclass's
                // no-arg<init>() method on objref.
 1 invokespecial #11 <Method java.lang.Object()>

// The second component, the instance variable initializers, begins
// here:
 4 aload_0      // Push the objref from loc var 0 (this)
 5 iconst_3     // Push int constant 3
                // Pop int (3), pop objref (this), store 3 into
                // width instance variable of this object
 6 putfield #19 <Field int width>

// The third component, the body of the constructor, begins
// here:
                // Push objref from System.out
 9 getstatic #16 <Field java.io.PrintStream out>
                // Allocate mem for new StringBuffer object, and
                // initialize mem to default initial values, push
                // objref to new object
12 new #8 <Class java.lang.StringBuffer>
15 dup          // Duplicate objref to StringBuffer object
                // Push objref to String literal from constant pool
16 ldc #2 <String "Example6(String), width = ">
                // Pop objref to literal String, pop objref of the
                // StringBuffer object, invoke <init>() method on the
                // StringBuffer object passing the args[0] objref as
                // the only parameter.
18 invokespecial #14 <Method java.lang.StringBuffer(
        java.lang.String)>
21 aload_0      // Push objref from loc var 0 (this)
                // Pop this reference, Push int value of width field
22 getfield #19 <Field int width>
                // Pop int (width), pop objref (StringBuffer object),
                // invoke append() on StringBuffer object passing the
                // width int as the only parameter. append() will add
                // the string representation of the int to the end of
                // the buffer, and return an objref to the same
                // StringBuffer object.
25 invokevirtual #15 <Method java.lang.StringBuffer append(int)>
                // Pop objref to StringBuffer (pushed by append()),
                // invoke toString() on it, which returns the value
                // of the StringBuffer as a String object. Push
                // objref of String object.
28 invokevirtual #18 <Method java.lang.String toString()>
                // Pop objref of String, pop objref of System.out
                // that was pushed by the getstatic instruction at
                // offset 9. Invoke println() on System.out,
                // passing the String as the only parameter:
                // System.out.println("Example6(String), width = "
                //     + width);
31 invokevirtual #17 <Method void println(java.lang.String)>
                // Push objref from System.out
34 getstatic #16 <Field java.io.PrintStream out>
37 aload_1      // Push objref from loc var 1 (param msg)
                // Pop objref of String, pop objref of System.out
                // that was pushed by the getstatic instruction at
                // offset 37. Invoke println() on System.out,
                // passing the String as the only parameter:
                // System.out.println(msg);
38 invokevirtual #17 <Method void println(java.lang.String)>
41 return       // Return void from <init>()

The <init>() method for constructor three has the same three components as the <init>() method for constructor two: a superclass <init>() invocation, the bytecodes for width's initializer, and the bytecodes for the constructor body. One difference between constructor two and three is that constructor two does not begin with an explicit this() or super() invocation. As a result, the compiler places an invocation of the superclass's no-arg <init>() method in constructor two's <init>() method. By contrast, constructor three begins with an explicit super() invocation, which the compiler converts into the corresponding superclass <init>() invocation in constructor three's <init>() method.

For every class except Object, an <init>() method must begin with an invocation of another <init>() method belonging either to the same class or to the direct superclass. <init>() methods are not allowed to catch exceptions thrown by the <init>() method they invoke. If a subclass <init>() method invokes a superclass <init>() method that completes abruptly, for example, the subclass <init>() method must also complete abruptly.

Garbage Collection and Finalization of Objects

As mentioned in earlier chapters, implementations of the Java virtual machine must have some kind of automatic storage management strategy for the heap, most likely a garbage collector. Applications can allocate memory for objects via the explicit and implicit ways described earlier in this chapter, but cannot explicitly free that memory. When an object becomes unreferenced by the application, the virtual machine may reclaim (garbage collect) that memory. Implementations can decide when to garbage collect unreferenced objects--even whether to garbage collect them at all. Java virtual machine implementations are not required to free memory occupied by unreferenced objects.

If a class declares a method named finalize() that returns void, the garbage collector will execute that method (called a "finalizer") once on an instance of that class, before it frees the memory space occupied by that instance. Here's an example of a class that declares a finalizer:

// On CD-ROM in file classlife/ex7/Finale.java
class Finale {
    protected void finalize() {
        System.out.println("A Finale object was finalized.");
        //...
    }
    //...
}

Because a finalizer is a regular Java method, it may be invoked directly by the application. Such a direct invocation will not affect the automatic invocation of the finalizer by the garbage collector. The garbage collector may invoke an object's finalizer at most once, sometime after the object becomes unreferenced and before the memory it occupies is reused. If the object becomes referenced again (resurrected) as a result of executing the finalizer code, and then becomes unreferenced again later, the garbage collector must not automatically invoke the finalizer a second time.

Any exceptions thrown by the finalize() method during its automatic invocation by the garbage collector are ignored. The garbage collector may invoke finalize() methods in any order, using any thread, or even concurrently via multiple threads. Finalization is described in more detail in Chapter 9, "Garbage Collection."

<<  Page 5 of 6  >>


Sponsored Links



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