The Artima Developer Community
Sponsored Link

Chapter 8 of Inside the Java Virtual Machine
The Linking Model
by Bill Venners

<<  Page 5 of 20  >>

Advertisement

Resolution of CONSTANT_Class_info Entries

Of all the types of constant pool entries, the most complicated to resolve is CONSTANT_Class_info. This type of entry is used to represent symbolic references to classes (including array classes) and interfaces. Several instructions, such as new and anewarray, refer directly to CONSTANT_Class_info entries. Other instructions, such as putfield or invokevirtual, refer indirectly to CONSTANT_Class_info entries through other types of entry. For example, the putfield instruction refers to a CONSTANT_Fieldref_info entry. The class_index item of a CONSTANT_Fieldref_info gives the constant pool index of a CONSTANT_Class_info entry.

The details of resolving a CONSTANT_Class_info entry vary depending on whether or not the type is an array and whether the referencing type (the one that contains in its constant pool the CONSTANT_Class_info entry being resolved) was loaded via the bootstrap class loader or a user-defined class loader.

Array Classes

A CONSTANT_Class_info entry refers to an array class if its name_index refers to a CONSTANT_Utf8_info string that begins with a left bracket, as in "[I." As described in Chapter 6, "The Java Class File," internal array names contain one left bracket for each dimension, followed by a component type. If the component type begins with an "L," as in "Ljava.lang.Integer;," the array is an array of references. Otherwise, the component type is a primitive type, such as "I" for int or "D" for double, and the array is an array of primitive types.

The end product of the resolution of a symbolic reference to an array class is a Class instance that represents the array class. If the current class loader has already been recorded as an initiating loader for the array class being resolved, that same class is used. Otherwise, the virtual machine performs the following steps: If the component type of the array is a reference type (the array is an array of references), the virtual machine resolves the component type using the current class loader. For example, if resolving an array class with the name "[[Ljava.lang.Integer;," the virtual machine would make certain class java.lang.Integer is loaded into the namespace of the current class loader. After resolving the component type if the array is an array of references, or immediately, if the array is an array of primitive types, the virtual machine creates a new array class of the indicated component type and number of dimensions and instantiates a Class instance to represent the type. For an array of references, the array class is marked as having been defined by the defining class loader of the component type. For an array of primitive types, the array class is marked as having been defined by the bootstrap class loader.

Non-Array Classes and Interfaces

A CONSTANT_Class_info entry whose name_index refers to a CONSTANT_Utf8_info string that doesn't begin with a left bracket is a symbolic reference to a non-array class or an interface. Resolution of this kind of symbolic reference is a multiple step process.

The Java virtual machine performs the same basic steps, described below as Steps 1a and 1b, to resolve any symbolic reference (any CONSTANT_Class_info entry) to a non-array class or interface. In Step 1a, the type is loaded. In Step 1b, access permission to the type is checked. The precise way in which the virtual machine performs Step 1a depends on whether the referencing type was loaded via the bootstrap class loader or a user-defined class loader.

Also described in this section are Steps 2a through 2d, which describe the linking and initialization of the newly resolved type. These steps are not part of the resolution of the symbolic reference to the type that becomes linked and initialized. Resolution of a symbolic reference to a non-array class or interface involves only Steps 1a and 1b, the (potential) loading of the type and the checking of its access permission. However, whenever the resolution process of a symbolic reference to a type is being triggered by the first active use of the type, linking and initialization of the type will immediately follow the resolution of the symbolic reference to that type. Because Java virtual machine implementations are allowed to perform early resolution, however, resolution of references to types may occur much earlier than the linking and initialization of those types. As mentioned in Chapter 7, the "Lifetime of a Type," initialization (here, Step 2d) occurs on the first active use of the type. Before a type can be initialized, it must be linked (Steps 2a through 2c), and before it can be linked, it must be loaded (Step 1a). [D] Step 1a. Load the Type and any Supertypes

The fundamental activity required by the resolution of a non-array class or interface is making sure the type is loaded into the current namespace. As a first step, the virtual machine must determine whether or not the referenced type has already been loaded into the current namespace. To make that determination, the virtual machine must find out whether the current class loader has been marked as an initiating loader for a type with the desired fully qualified name (the type name given in the symbolic reference being resolved). For each class loader, the Java virtual machine maintains a list of the names of all the types for which the class loader has served as an initiating class loader. Each of these lists forms a namespace inside the Java virtual machine. The virtual machine uses these lists during resolution to determine whether a class has already been loaded by a particular class loader. If the virtual machine discovers the desired fully qualified name is already mentioned in the current namespace, it will just use the already-loaded type, which is defined by a chunk of type data in the method area and represented by an associated Class instance on the heap. By first checking whether the current namespace already includes the desired fully qualified name, the virtual machine helps ensure that only one type with a given name is loaded by any single class loader.

If a type with the desired fully qualified name hasn't yet been loaded into the current namespace, the virtual machine passes the fully qualified name to the current class loader. The Java virtual machine always asks the current class loader, the defining loader of the referencing type whose runtime constant pool contains the CONSTANT_Class_info entry being resolved, to attempt to load the referenced type. If the referencing type was defined by the bootstrap class loader, the virtual machine asks the bootstrap class loader to load the referenced type. Otherwise, the referencing type was defined by a user- defined class loader, and the virtual machine asks the same user-defined class loader to load the referenced type.

If the current class loader is the bootstrap class loader, the virtual machine asks it in an implementation dependent way to load the type. If the current class loader is a user-defined class loader, the Java virtual machine makes the load request by invoking the user-defined class loader's loadClass() method, passing in parameter name the fully qualified name of the desired type.

When either the bootstrap class loader or a user-defined class loader is asked to load a type, the class loader has two choices: It can attempt to load the type by itself, or it can delegate the job to some other class loader. A user-defined class loader can ask either another user-defined class loader or the bootstrap class loader to attempt to load the type. The bootstrap class loader can ask a user-defined class loader to attempt to load the type.

To delegate to a user-defined class loader, a class loader (whether bootstrap or user-defined) invokes loadClass() on that class loader, passing in the fully qualified name of the desired type. To delegate to the bootstrap class loader, a user-defined class loader invokes findSystemClass(), a static method from java.lang.ClassLoader, passing in the fully qualified name of the desired type. A class loader that has been delegated to can also decide whether or not to attempt to load the type itself, or to delegate the job to yet another class loader. Eventually, some class loader will decide that the buck stops with it, and rather than delegate, attempt to actually load the type itself. If this class loader is successful at loading the type, it will be marked as the defining class loader for the type. All of the class loaders involved in the process-- the defining class loader and all the class loaders that delegated -- will be marked as initiating loaders of the type.

Given the existence of the parent-delegation model described earlier in this chapter, if a user-defined class loader delegates, the class loader to which it delegates will often be its parent in the parent-delegation model. The parent will, in-turn, delegate to its parent, which will delegate to its parent, and so on. The delegation process continues all the way up to the end-point of the delegation process, which is the class loader that, rather than delegating, decides to try and load the type itself. Most often, this end-point class loader will be the bootstrap class loader. When a parent class loader attempts to load the type but fails, control returns to the child class loader. In the parent-delegation model, the child class loader, upon learning that its parent (and grandparent, great grandparent, and so on) was unable to load the type, attempts to load the type itself. If a class loader in the middle of the delegation chain is the class loader that first has success loading the type, that class loader will be marked as the defining class loader. The defining class loader and all the class loaders before it in the parent-delegation chain will be marked as initiating class loaders. However, its parent, grandparent, great grandparent, and so on, none of whom were successful in their attempts to load the type, will not be marked as initiating class loaders of the type.

If the loadClass() method of a user-defined class loader is able to locate or produce an array of bytes that purportedly defines the type in the Java class file format, loadClass() must invoke defineClass(), passing the fully qualified name of the desired type and a reference to the byte array. Invoking defineClass() will cause the virtual machine to attempt to parse the binary data into internal data structures in the method area. At this point the virtual machine will perform pass one of verification, as described in Chapter 3, "Security," which ensures the passed array of bytes adhere to the basic structure of the Java class file format. The Java virtual machine uses the passed fully qualified name to verify that the desired type name is actually declared as the name of the type in the passed array of bytes.

Once the referenced type is loaded in, the virtual machine peers into its binary data. If the type is a class and not java.lang.Object, the virtual machine determines from the class's data the fully qualified name of the class's direct superclass. The virtual machine then checks to see if the superclass has been loaded into the current namespace. If not, it loads the superclass. Once that class comes in, the virtual machine can again peer into its binary data to find its superclass. This process repeats all the way up to Object.

When the virtual machine loads a superclass, it is really just resolving yet another symbolic reference. To determine what the fully qualified name of a class's superclass is, the virtual machine looks at the super_class field of the class file. This field gives a constant pool index of a CONSTANT_Class_info entry that serves as a symbolic reference to the class's superclass. When the virtual machine load the superclass, it does so as Step 1a of the process of resolving the symbolic reference to the superclass. Thus, as part of Step 1a of the resolution process for CONSTANT_Class_info entries, the virtual machine recursively applies the resolution process for CONSTANT_Class_info entries on each superclass all the way up to Object.

On the way back down from Object, the virtual machine will again peer into the type data for each type it loaded to see if the type directly implements any interfaces. If so, it will make sure those interfaces are also loaded. For each interface the virtual machine loads, the virtual machine peers into its type data to see if it directly extends any other interfaces. If so, the virtual machine makes sure those superinterfaces are loaded.

When the virtual machine loads superinterfaces, it is once again resolving more CONSTANT_Class_info entries. The indexes of all the constant pool entries that serve as symbolic references to the interfaces directly implemented or extended by the type being loaded are stored in the interfaces component of the class file. When the virtual machine loads superinterfaces, it is resolving the CONSTANT_Class_info entries specified in the interfaces component, applying the resolution process for CONSTANT_Class_info entries recursively.

When the virtual machine applies the recursive resolution process to superclasses and superinterfaces, it uses the defining class loader of the referencing subtype. The virtual machine makes its request in the usual way, by invoking loadClass() on the referencing subtype's defining class loader, passing in the fully qualified name of the desired direct superclass or direct superinterface.

Once a type has been loaded into the current namespace, and by recursion, all the type's superclasses and superinterfaces have also been successfully loaded, the virtual machine instantiates the new Class instance to represent the type. If the bytes defining the type were located or produced by a user-defined class loader and passed to defineClass(), defineClass() will at that point return the new Class instance. Alternatively, if a user-defined class loader delegated to the bootstrap class loader with a findSystemClass() invocation, findSystemClass() will at that point return the Class instance. Upon receiving the Class instance from either defineClass() or findSystemClass(), the loadClass() method returns the Class instance to its caller. If a user-defined class loader delegates to another user- defined class loader, therefore, it receives the Class instance from the delegated-to user-defined class loader when its loadClass() method returns. Upon receiving the Class instance from the delegated-to class loader, the delegated-from class loader returns it from its own loadClass() method.

Through Step 1a, the Java virtual machine makes sure a type is loaded, and if the type is a class, that all its superclasses are loaded, and whether the type is a class or an interface, that all of its superinterfaces are loaded. During this step, these types are not linked and initialized--just loaded.

During Step 1a, the virtual machine may throw the following errors:

Step 1b. Check Access Permission

After loading is complete, the virtual machine checks for access permission. If the referencing type does not have permission to access the referenced type, the virtual machine throws an IllegalAccessError. Step 1b is another activity that is logically part of verification, but that is performed at some other time than the official verification phase. The check for access permission will always take place after Step 1a, ensuring a type referenced from a symbolic reference is loaded into the current namespace, as part of resolving that symbolic reference. Once this check is complete, Step 1b--and the entire process of resolving the CONSTANT_Class_info entry--is complete.

If an error occurred in Steps 1a or 1b, the resolution of the symbolic reference to the type fails. But if all went well up until the access permission check of Step 1b, the type is still usable in general, just not usable by the referencing type. If an error occurred before the access permission check, however, the type is unusable and must be marked as such or discarded. [D] Step 2. Link and Initialize the Type and any Superclasses

At this point, the type being referred to by the CONSTANT_Class_info entry being resolved has been loaded, but not necessarily linked or initialized. In addition, all the type's superclasses and superinterfaces have been loaded, but not necessarily linked or initialized. Some of the supertypes may be initialized at this point, because they may have been initialized during earlier resolutions.

As described in Chapter 7, "The Lifetime of a Class," superclasses must be initialized before subclasses. If the virtual machine is resolving a reference to a class (not an interface) because of an active use of that class, it must make sure that the superclasses have been initialized, starting with Object and proceeding down the inheritance hierarchy to the referenced class. (Note that this is the opposite order in which they were loaded in Step 1a.) If a type hasn't yet been linked, it must be linked before it is initialized. Note that only superclasses must be initialized, not superinterfaces.

Step 2a. Verify the Type

Step 2 begins with the official verification phase of linking, described in Chapter 7, "The Lifetime of a Class." As mentioned in Chapter 7, the process of verification may require that the virtual machine load new types to ensure the bytecodes are adhering to the semantics of the Java language. For example, if a reference to an instance of a particular class is assigned to a variable with a declared type of a different class, the virtual machine would have to load both types to make sure one is a subclass of the other. These classes would at this point be loaded and possibly linked, but definitely not initialized.

If during the verification process the Java virtual machine uncovers trouble, it throws VerifyError.

Step 2b. Prepare the Type

After the official verification phase is complete, the type must be prepared. As described in Chapter 7, "The Lifetime of a Class," during preparation the virtual machine allocates memory for class variables and implementation-dependent data structures such as method tables.

Optional Step 2c. Resolve the Type

At this point, the type has been loaded, verified and prepared. As described in Chapter 7, "The Lifetime of a Class," a Java virtual machine implementation may optionally resolve the type at this point. Keep in mind that at this stage in the resolution process, Steps 1a, 2a, and 2b have been performed on a referenced type to resolve a CONSTANT_Class_info entry in the constant pool of a referencing type. Step 2c is the resolution of symbolic references contained in the referenced type, not the referencing type. (And by the way, Step 2b is not mentioned in the previous discussion because Step 2b has nothing to do with the referenced type's loading, linking, and initialization process. Step 2b is actually part of pass four of the verification step of the linking phase of the referencing type, the type that contains the symbolic reference to the referenced type.)

For example, if the virtual machine is resolving a symbolic reference from class Cat to class Mouse, the virtual machine performs Steps 1a, 2a, and 2b on class Mouse. At this stage of resolving the symbolic reference to Mouse contained in the constant pool of Cat, the virtual machine could optionally (as Step 2c) resolve all the symbolic references contained in the constant pool for Mouse. If Mouse's constant pool contains a symbolic reference to class Cheese, for example, the virtual machine could load and optionally link (but not initialize) Cheese at this time. The virtual machine mustn't attempt to initialize Cheese here because Cheese is not being actively used. (Of course, Cheese may in fact have already been actively used elsewhere, so it could have been already be loaded into this namespace, linked, and initialized.)

As mentioned earlier in this chapter, if an implementation does perform Step 2c at this point in the resolution process (early resolution), it must not report any errors until the symbolic references are actually used by the running program. For example, if during the resolution of Mouse's constant pool, the virtual machine can't find class Cheese, it won't throw a NoClassDefFound error until (and unless) Cheese is actually used by the program.

Step 2d. Initialize the Type

At this point, the type has been loaded, verified, prepared and optionally resolved. At long last, the type is ready for initialization. As defined in Chapter 7, "The Lifetime of a Class," initialization consists of two steps. The initialization of the type's superclasses in top down order, if the type has any superclasses, and the execution of the type's class initialization method, if it has one. Step 2d just consists of executing the class initialization method, if one exists. Because Steps 2d is performed for all the referenced type's superclasses, from the top down, Step 2d will occur for superclasses before it occurs for subclasses.

If the class initialization method completes abruptly by throwing some exception that isn't a subclass of Error, the virtual machine throws ExceptionInInitializerError with the thrown exception as a parameter to the constructor. Otherwise, if the thrown exception is already a subclass of Error, that error is thrown. If the virtual machine can't create a new ExceptionInInitializerError because there isn't enough memory, it throws an OutOfMemoryError.

<<  Page 5 of 20  >>


Sponsored Links



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