Representation of Java Classes

In this section, we will look at both the compiled and runtime representation of a Java class. The compiled representation is the binary data outputted by a compiler. The runtime representation is the representation derived from the compiled version, which is used during runtime.

Compiled Representation

A Java class file stores information about its corresponding class. It stores information such as the type of the class, the type of its superclass, the fields, and the bytecodes of its methods. The following is a C-like structure that describes the general layout of a class file. The types u2 and u4 represent two- and four-byte values, respectively.

ClassFile {     u4 magic;     u2 minor_version;     u2 major_version;     u2 constant_pool_count;     cp_info constant_pool[];     u2 access_flags;     u2 this_class;     u2 super_class;     u2 interfaces_count;     u2 interfaces[];     u2 fields_count;     field_info fields[];     u2 methods_count;     method_info methods[];     u2 attributes_count;     attribute_info attributes[]; }

The item labeled magic stores the value 0xCAFEBABE, which is used to check whether the file is a Java class file. The items minor_version and major_version are used for keeping track of the version of the file to ensure that the compiled representation is compatible with a specific version of the VM. For example, if an opcode specific to newer VMs is used in the class file, an older VM that does not understand the opcode should know that the class file is incompatible.

The constant_pool table contains many different types of information. The information includes string constants and structures that are used to represent references to classes, methods, and fields of other classes. The constant pool table is in many ways similar to a symbol table used by C/C++ compilers. C/C++ files are compiled to intermediate object files, which are then linked together to generate an executable or library file. Each C/C++ file is compiled to an object file that uses a symbol table to keep track of its unresolved symbols. During the link stage, symbolic references are resolved and replaced by actual offsets. Because Java files are linked during runtime, as opposed to at compile time, symbolic references are needed during runtime. Therefore, they are stored in class files.

The access_flags item is a mask that indicates whether the class is public, final, and so forth. this_class and super_class are indices into the constant_pool table where information on the fully qualified name of the corresponding classes can be found. Similarly, the interfaces array stores indices into the constant_pool table where information on the direct interfaces of the class can be found.

The fields and methods tables represent the information about the fields and methods of the class. Each element in the fields table contains the following information:

field_info {      u2 access_flags;      u2 name_index;      u2 descriptor_index;      u2 attributes_count;      attribute_info attributes[attributes_count]; }

The access_flags contains masks that indicate whether the field is private, static, and so forth. The name_index indicates where the name of the field can be found in the constant pool table. The descriptor_index is a string that describes the type of the field and is also stored in the constant pool table. If a field is constant, an entry in its attributes table provides information such as the length of the value and the index at which the value can be found.

Each element in the methods table contains the following information:

method_info {      u2 access_flags;      u2 name_index;      u2 descriptor_index;      u2 attributes_count;      attribute_info attributes[attributes_count]; }

The descriptor of a method is a string that describes the type of its arguments as well as its return type. For example, "(I)V" describes a method that takes an int and returns void. The descriptor_index stores the index in the constant pool table where the descriptor of the method can be found. There are many different types of attribute_info of which some correspond to methods, some to classes, and others to fields. The attributes table of a method can indicate, for example, whether a method is deprecated and what exceptions it can throw. More important, the attributes table of a method contains a code_attribute element that stores the bytecodes of a method and looks like the following:

code_attribute {      u2 attribute_name_index;      u4 attribute_length;      u2 max_stack;      u2 max_locals;      u4 code_length;      u1 code[code_length];      u2 exception_table_length;      exc_info    exception_table[];      u2 attributes_count;      attribute_info attributes[attributes_count]; }

The max_stack item indicates the maximum depth of the operand stack required for executing the method. max_locals indicates the number of local variables in the method, which is used to allocate the local variables array when a frame is allocated. The code array stores the bytecodes of the method. The elements in the attributes table of a code_attribute do not have to be populated.

Runtime Representation

A Java class file must be represented during runtime so that it can be used by the VM as well as by a Java program. The VM has to understand classes for many tasks. For example, when the new operator is used, the VM has to know how many bytes of memory must be allocated to represent an object of a specific type. The VM also has to understand different Java types so that it can perform runtime type checking or even access the members of a class or invoke its methods.

There are also times when a Java program must understand Java classes during runtime. For example, the following code segment uses a string to retrieve an arbitrary class and make an instance of it. The output of the segment is MonsterEntity’s toString.

class MonsterEntity{     public String toString(){        return "MonsterEntity’s toString";     }    }   class Sample{          public static void main(String args[]){           Object object = null;         String name = "MonsterEntity";                  try{             Class entityType = Class.forName(name);             object = entityType.newInstance();         }catch (Exception e){}         System.out.println(object.toString());     } }

As far as a Java program is concerned, Java classes are represented as instances of the java.lang.Class class. The Java primitive types such as boolean, int, and float are also represented as instances of the Class class. The same holds for arrays and even the void keyword. A Class object can contain information such as the name, superclass, fields, and methods of a class. Such information is useful for obtaining information about a class during runtime, a process known as reflection.

The Java VM specifications do not give a specific internal representation that must be used by every VM implementation. This is because the specifications try not to impose unnecessary restrictions so that the implementers of a VM can make their design decisions based on what is appropriate for the target platform. The internal representation is typically a C Struct. Some VMs, such as the Sun HotSpot VMs, use multiple internal representations to track different information that corresponds to a class. By using different data structures, the more complicated VMs can track various data, such as the optimized version of a class, more efficiently. On the other hand, simpler VMs such as the KVM keep track of only a small set of data that is necessary. The KVM is an extremely lean implementation of the Java virtual machine, which has been designed for use in devices that have little available memory. In general, the internal runtime representation of a class is in many ways similar to the compiled representation. For example, it has a constant pool table, which is used to resolve unresolved symbolic references. The runtime version of the constant pool table of a class may store other information as well. For example, it may store information that can allow for direct (or nonsymbolic) access to methods and fields of other objects. The internal representation also needs to keep track of the bytecodes of the methods of a class.

The Loading Process

A class loader is responsible for taking the compiled representation of a class and constructing an instance of java.lang.Class. Most applications simply use the class loader that is already included with the VM, which is also known as the bootstrap loader or system class loader. It is possible to write a custom class loader by extending the java.lang.ClassLoader class. A custom class loader can be used to load a class from a source other than a class file. For example, if an application wants to load a class that is stored in memory as an array of bytes, it must use a custom class loader.

When a class is being loaded, it must be verified and prepared. The verification of a class is extensive. The verification process includes checking the entries of the constant pool table to ensure that they are proper. Some entries in the table refer to data at some other index of the table. Because, for example, a symbolic reference to a field needs to indicate the name of the class as well as the name and type of the field to which it refers, it contains additional indices that specify where such information can be found. The verification process will make sure that the indices are valid and refer to data of the appropriate type. In addition to verifying the integrity of the constant pool table, every single bytecode of the methods of a class is examined to make sure that it has legal operands. This may seem odd because such verifications are the job of the compiler that generated the bytecodes. Even though the compiler does perform these tests, they must be performed again when a class is loaded. These checks are important for the VM because it needs to prevent incorrect bytecodes from being executed. The VM does not assume that some Java compiler has outputted correct bytecodes. This rechecking is also important because a program may choose to generate code during runtime and ask the VM to execute it. Examples of other checks include making sure jump instructions jump to valid locations, array accesses are valid, and symbolic links are legitimate. As you can guess, these verifications directly affect the startup time of an application.

After a class has been loaded and verified, an implementation of a VM may choose to resolve its symbolic references. Sun HotSpot VMs do not resolve symbolic references at load time. They put off the resolution of the references as long as possible, a process known as late binding. Let’s consider the case where class A calls a method in class B. When the VM invokes the method of class B, it first makes sure that the class has been loaded and then looks for the corresponding method in class B. If class B has not been loaded already, the VM loads and verifies it. The other option would have been to resolve all symbols when a class is loaded. Such an approach would cause all the classes used by an application to get loaded when the VM launches. This approach would, of course, substantially increase the startup time of the application. However, loading and verification would not be necessary once the application is ready to run. When a symbolic reference is resolved, a VM may choose to store a direct reference to the method so that it does not have to resolve the symbolic reference every time it needs to invoke a method. In fact, this is what Sun HotSpot VMs do. After a reference is resolved, the direct reference is stored in the constant pool table, and the bytecodes of the invoking method is changed to take advantage of the direct reference instead of using the symbolic reference.

As a side note, if the need to run more than one Java application at the same time arises, it is possible to run all of them in a single instance of the VM. To do so, the VM must guarantee that the classes of the different applications (that may have the same name) are not mistaken for one another. Because of this, the VM recognizes classes by their name along with the class loader that loaded the class.



Practical Java Game Programming
Practical Java Game Programming (Charles River Media Game Development)
ISBN: 1584503262
EAN: 2147483647
Year: 2003
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net