Sponsored Link •
|
Advertisement
|
CONSTANT_Class_info
EntriesOf 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.
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.
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:
findSystemClass()
invocation) and it is unable to locate or produce the binary
data for the requested type, the virtual machine throwsNoClassDefFoundError
.
findSystemClass()
invocation and the bootstrap class loader it is unable to locate
or produce the binary data for the requested type, the findSystemClass()
method
completes abruptly with a ClassNotFoundError
. Similarly, if a user-defined class
loader delegates to another user-defined class loader via a loadClass()
invocation
and the user-defined class loader it is unable to locate or produce the binary data for the requested type, its
loadClass()
method should complete abruptly with a
ClassNotFoundError
.
ClassFormatError
. Likewise, if a user-defined class
loader is able to locate or produce the binary data and invoke the defineClass()
method, but the defineClass()
method discovers the binary data isn't of the proper
structure, defineClass()
will complete abruptly with a
ClassFormatError
.
UnsupportedClassVersionError
.
CuteKitty.class
is discovered to contain class
HungryTiger
instead of CuteKitty
) , the virtual machine
throws NoClassDefFoundError
.
defineClass()
, but contains a class or
interface whose name already appears in the current class loader's namespace, the
defineClass()
method completes abruptly with a
LinkageError
.
Object
itself, the virtual
machine throws a ClassFormatError
. (Note that this check has to be done here,
during the loading step, because that one piece of information--the symbolic reference to the superclass--is
needed by the virtual machine during this step. During Step 1, the virtual machine must load in all the
superclasses recursively.)
ClassCircularityError
.
IncompatibleClassChangeError
.
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 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
.
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.
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.
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
.
Sponsored Links
|