Sponsored Link •
|
Advertisement
|
Inside a Java virtual machine instance, information about loaded types is stored in a logical area of memory called the method area. When the Java virtual machine loads a type, it uses a class loader to locate the appropriate class file. The class loader reads in the class file--a linear stream of binary data--and passes it to the virtual machine. The virtual machine extracts information about the type from the binary data and stores the information in the method area. Memory for class (static) variables declared in the class is also taken from the method area.
The manner in which a Java virtual machine implementation represents type information internally is a decision of the implementation designer. For example, multi-byte quantities in class files are stored in big- endian (most significant byte first) order. When the data is imported into the method area, however, a virtual machine can store the data in any manner. If an implementation sits on top of a little-endian processor, the designers may decide to store multi-byte values in the method area in little-endian order.
The virtual machine will search through and use the type information stored in the method area as it executes the application it is hosting. Designers must attempt to devise data structures that will facilitate speedy execution of the Java application, but must also think of compactness. If designing an implementation that will operate under low memory constraints, designers may decide to trade off some execution speed in favor of compactness. If designing an implementation that will run on a virtual memory system, on the other hand, designers may decide to store redundant information in the method area to facilitate execution speed. (If the underlying host doesn't offer virtual memory, but does offer a hard disk, designers could create their own virtual memory system as part of their implementation.) Designers can choose whatever data structures and organization they feel optimize their implementations performance, in the context of its requirements.
All threads share the same method area, so access to the method area's data structures must be designed
to be thread-safe. If two threads are attempting to find a class named Lava
, for
example, and Lava
has not yet been loaded, only one thread should be allowed to load
it while the other one waits.
The size of the method area need not be fixed. As the Java application runs, the virtual machine can expand and contract the method area to fit the application's needs. Also, the memory of the method area need not be contiguous. It could be allocated on a heap--even on the virtual machine's own heap. Implementations may allow users or programmers to specify an initial size for the method area, as well as a maximum or minimum size.
The method area can also be garbage collected. Because Java programs can be dynamically extended via user-defined class loaders, classes can become "unreferenced" by the application. If a class becomes unreferenced, a Java virtual machine can unload the class (garbage collect it) to keep the memory occupied by the method area at a minimum. The unloading of classes--including the conditions under which a class can become "unreferenced"--is described in Chapter 7, "The Lifetime of a Type."
For each type it loads, a Java virtual machine must store the following kinds of information in the method area:
java.lang.Object
, neither of which have a superclass)
public
,
abstract
, final
)
Inside the Java class file and Java virtual machine, type names are always stored as fully qualified
names. In Java source code, a fully qualified name is the name of a type's package, plus a dot, plus
the type's simple name. For example, the fully qualified name of class
Object
in package java.lang
is
java.lang.Object
. In class files, the dots are replaced by slashes, as in
java/lang/Object
. In the method area, fully qualified names can be represented in
whatever form and data structures a designer chooses.
In addition to the basic type information listed previously, the virtual machine must also store for each loaded type:
ClassLoader
Class
For each type it loads, a Java virtual machine must store a constant pool. A constant pool is an ordered set of constants used by the type, including literals (string, integer, and floating point constants) and symbolic references to types, fields, and methods. Entries in the constant pool are referenced by index, much like the elements of an array. Because it holds symbolic references to all types, fields, and methods used by a type, the constant pool plays a central role in the dynamic linking of Java programs. The constant pool is described in more detail later in this chapter and in Chapter 6, "The Java Class File."
For each field declared in the type, the following information must be stored in the method area. In addition to the information for each field, the order in which the fields are declared by the class or interface must also be recorded. Here's the list for fields:
public
,
private
, protected
, static
,
final
, volatile
, transient
)
For each method declared in the type, the following information must be stored in the method area. As with fields, the order in which the methods are declared by the class or interface must be recorded as well as the data. Here's the list:
void
)
public
,
private
, protected
, static
,
final
, synchronized
, native
,
abstract
)
Class variables are shared among all instances of a class and can be accessed even in the absence of any instance. These variables are associated with the class--not with instances of the class--so they are logically part of the class data in the method area. Before a Java virtual machine uses a class, it must allocate memory from the method area for each non-final class variable declared in the class.
Constants (class variables declared final) are not treated in the same way as non-final class variables. Every type that uses a final class variable gets a copy of the constant value in its own constant pool. As part of the constant pool, final class variables are stored in the method area--just like non-final class variables. But whereas non-final class variables are stored as part of the data for the type that declares them, final class variables are stored as part of the data for any type that uses them. This special treatment of constants is explained in more detail in Chapter 6, "The Java Class File."
ClassLoader
For each type it loads, a Java virtual machine must keep track of whether or not the type was loaded via the bootstrap class loader or a user-defined class loader. For those types loaded via a user-defined class loader, the virtual machine must store a reference to the user-defined class loader that loaded the type. This information is stored as part of the type's data in the method area.
The virtual machine uses this information during dynamic linking. When one type refers to another type, the virtual machine requests the referenced type from the same class loader that loaded the referencing type. This process of dynamic linking is also central to the way the virtual machine forms separate name spaces. To be able to properly perform dynamic linking and maintain multiple name spaces, the virtual machine needs to know what class loader loaded each type in its method area. The details of dynamic linking and name spaces are given in Chapter 8, "The Linking Model."
Class
An instance of class java.lang.Class
is created by the Java virtual machine
for every type it loads. The virtual machine must in some way associate a reference to the
Class
instance for a type with the type's data in the method area.
Your Java programs can obtain and use references to Class
objects. One static
method in class Class
, allows you to get a reference to the Class
instance for any loaded class:
// A method declared in class java.lang.Class: public static Class forName(String className);
If you invoke forName("java.lang.Object")
, for example, you will get a
reference to the Class
object that represents
java.lang.Object
. If you invoke
forName("java.util.Enumeration")
, you will get a reference to the
Class
object that represents the Enumeration
interface from
the java.util
package. You can use forName()
to get a
Class
reference for any loaded type from any package, so long as the type can be (or
already has been) loaded into the current name space. If the virtual machine is unable to load the requested
type into the current name space, forName()
will throw
ClassNotFoundException
.
An alternative way to get a Class
reference is to invoke
getClass()
on any object reference. This method is inherited by every object from
class Object
itself:
// A method declared in class java.lang.Object: public final Class getClass();
If you have a reference to an object of class java.lang.Integer
, for example,
you could get the Class
object for java.lang.Integer
simply by invoking getClass()
on your reference to the
Integer
object.
Given a reference to a Class
object, you can find out information about the type
by invoking methods declared in class Class
. If you look at these methods, you will
quickly realize that class Class
gives the running application access to the information
stored in the method area. Here are some of the methods declared in class Class
:
// Some of the methods declared in class java.lang.Class: public String getName(); public Class getSuperClass(); public boolean isInterface(); public Class[] getInterfaces(); public ClassLoader getClassLoader();
These methods just return information about a loaded type. getName()
returns
the fully qualified name of the type. getSuperClass()
returns the
Class
instance for the type's direct superclass. If the type is class
java.lang.Object
or an interface, none of which have a superclass,
getSuperClass()
returns null
.
isInterface()
returns true
if the Class
object describes an interface, false
if it describes a class.
getInterfaces()
returns an array of Class
objects, one for
each direct superinterface. The superinterfaces appear in the array in the order they are declared as
superinterfaces by the type. If the type has no direct superinterfaces,
getInterfaces()
returns an array of length zero.
getClassLoader()
returns a reference to the ClassLoader
object that loaded this type, or null
if the type was loaded by the bootstrap class
loader. All this information comes straight out of the method area.
The type information stored in the method area must be organized to be quickly accessible. In addition to the raw type information listed previously, implementations may include other data structures that speed up access to the raw data. One example of such a data structure is a method table. For each non-abstract class a Java virtual machine loads, it could generate a method table and include it as part of the class information it stores in the method area. A method table is an array of direct references to all the instance methods that may be invoked on a class instance, including instance methods inherited from superclasses. (A method table isn't helpful in the case of abstract classes or interfaces, because the program will never instantiate these.) A method table allows a virtual machine to quickly locate an instance method invoked on an object. Method tables are described in detail in Chapter 8, "The Linking Model."
As an example of how the Java virtual machine uses the information it stores in the method area, consider these classes:
// On CD-ROM in file jvm/ex2/Lava.java class Lava { private int speed = 5; // 5 kilometers per hour void flow() { } } // On CD-ROM in file jvm/ex2/Volcano.java class Volcano { public static void main(String[] args) { Lava lava = new Lava(); lava.flow(); } }
The following paragraphs describe how an implementation might execute the first instruction in the
bytecodes for the main()
method of the Volcano
application.
Different implementations of the Java virtual machine can operate in very different ways. The following
description illustrates one way--but not the only way--a Java virtual machine could execute the first
instruction of Volcano
's main()
method.
To run the Volcano
application, you give the name
"Volcano
" to a Java virtual machine in an implementation-dependent manner. Given
the name Volcano
, the virtual machine finds and reads in file
Volcano.class
. It extracts the definition of class Volcano
from the binary data in the imported class file and places the information into the method area. The virtual
machine then invokes the main()
method, by interpreting the bytecodes stored in the
method area. As the virtual machine executes main()
, it maintains a pointer to the
constant pool (a data structure in the method area) for the current class (class
Volcano
).
Note that this Java virtual machine has already begun to execute the bytecodes for
main()
in class Volcano
even though it hasn't yet loaded class
Lava
. Like many (probably most) implementations of the Java virtual machine, this
implementation doesn't wait until all classes used by the application are loaded before it begins executing
main()
. It loads classes only as it needs them.
main()
's first instruction tells the Java virtual machine to allocate enough memory
for the class listed in constant pool entry one. The virtual machine uses its pointer into
Volcano
's constant pool to look up entry one and finds a symbolic reference to class
Lava
. It checks the method area to see if Lava
has already been
loaded.
The symbolic reference is just a string giving the class's fully qualified name:
"Lava"
. Here you can see that the method area must be organized so a class can be
located--as quickly as possible--given only the class's fully qualified name. Implementation designers can
choose whatever algorithm and data structures best fit their needs--a hash table, a search tree, anything. This
same mechanism can be used by the static forName()
method of class
Class
, which returns a Class
reference given a fully qualified
name.
When the virtual machine discovers that it hasn't yet loaded a class named "Lava
,"
it proceeds to find and read in file Lava.class
. It extracts the definition of class
Lava
from the imported binary data and places the information into the method area.
The Java virtual machine then replaces the symbolic reference in Volcano
's
constant pool entry one, which is just the string "Lava"
, with a pointer to the class
data for Lava
. If the virtual machine ever has to use Volcano
's
constant pool entry one again, it won't have to go through the relatively slow process of searching through
the method area for class Lava
given only a symbolic reference, the string
"Lava"
. It can just use the pointer to more quickly access the class data for
Lava
. This process of replacing symbolic references with direct references (in this case,
a native pointer) is called constant pool resolution. The symbolic reference is
resolved into a direct reference by searching through the method area until the referenced
entity is found, loading new classes if necessary.
Finally, the virtual machine is ready to actually allocate memory for a new Lava
object. Once again, the virtual machine consults the information stored in the method area. It uses the
pointer (which was just put into Volcano
's constant pool entry one) to the
Lava
data (which was just imported into the method area) to find out how much heap
space is required by a Lava
object.
A Java virtual machine can always determine the amount of memory required to represent an object by looking into the class data stored in the method area. The actual amount of heap space required by a particular object, however, is implementation-dependent. The internal representation of objects inside a Java virtual machine is another decision of implementation designers. Object representation is discussed in more detail later in this chapter.
Once the Java virtual machine has determined the amount of heap space required by a
Lava
object, it allocates that space on the heap and initializes the instance variable
speed
to zero, its default initial value. If class Lava
's superclass,
Object
, has any instance variables, those are also initialized to default initial values.
(The details of initialization of both classes and objects are given in Chapter 7, "The Lifetime of a Type.")
The first instruction of main()
completes by pushing a reference to the new
Lava
object onto the stack. A later instruction will use the reference to invoke Java
code that initializes the speed
variable to its proper initial value, five. Another
instruction will use the reference to invoke the flow()
method on the referenced
Lava
object.
Sponsored Links
|