An object model dictates how to represent objects in storage; the best object model will maximize efficiency of frequent language operations while minimizing storage overhead. Jikes RVM's object model is defined by
Values in the Java™ programming language are either primitive (e.g.
double, etc.) or they are references (that is, pointers) to objects. Objects are either arrays having elements or scalar objectshaving fields. Objects are logically composed of two primary sections: an object header (described in more detail below) and the object's instance fields (or array elements).
The following non-functional requirements govern the Jikes RVM object model:
- instance field and array accesses should be as fast as possible,
- null-pointer checks should be performed by the hardware if possible,
- method dispatch and other frequent runtime services should be fast,
- other (less frequent) Java operations should not be prohibitively slow, and
- per-object storage overhead (ie object header size) should be as small as possible.
Assuming the reference to an object resides in a register, compiled code can access the object's fields at a fixed displacement in a single instruction. To facilitate array access, the reference to an array points to the first (zeroth) element of an array and the remaining elements are laid out in ascending order. The number of elements in an array, its length, resides just before its first element. Thus, compiled code can access array elements via base + scaled index addressing.
The Java programming language requires that an attempt to access an object through a
null object reference generates a
NullPointerException. In Jikes RVM, references are machine addresses, and
null is represented by address 0. On Linux, accesses to both very low and very high memory can be trapped by the hardware, thus all null checks can be made implicit. However, the AIX™ operating system permits loads from low memory, but accesses to very high memory (at small negative offsets from a null pointer) normally cause hardware interrupts. Therefore on AIX only a subset of pointer dereferences can be protected by an implicit null check.
Logically, every object header contains the following components:
- TIB Pointer: The TIB (Type Information Block) holds information that applies to all objects of a type. The structure of the TIB is defined by
TIBLayoutConstants. A TIB includes the virtual method table, a pointer to an object representing the type, and pointers to a few data structures to facilitate efficient interface invocation and dynamic type checking.
- Hash Code: Each Java object has an identity hash code. This can be read by Object.hashCode or in the case that this method overridden, by System.identityHashCode. The default hash code is usually the location in memory of the object, however, with some garbage collectors objects can move. So the hash code remains the same, space in the object header may be used to hold the original hash code value.
- Lock: Each Java object has an associated lock state. This could be a pointer to a lock object or a direct representation of the lock.
- Array Length: Every array object provides a length field that contains the length (number of elements) of the array.
- Garbage Collection Information: Each Java object has associated information used by the memory management system. Usually this consists of one or two mark bits, but this could also include some combination of a reference count, forwarding pointer, etc.
- Misc Fields: In experimental configurations, the object header can be expanded to add additional fields to every object, typically to support profiling.
An implementation of this abstract header is defined by three files:
JavaHeader, which supports TIB access, default hash codes, and locking;
AllocatorHeader, which supports garbage collection information; and
MiscHeader, which supports adding additional fields to all objects.
Fields tend to be recorded in the Java class file in the order they are declared in the Java source file. We lay out fields in the order they are declared with some exceptions to improve alignment and pack the fields in the object.
Double and long fields benefit from being 8 byte aligned. Every RVMClass records the preferred alignment of the object as a whole. We lay out double and long fields first (and object references if these are 8 bytes long) so that we can avoid making holes in the field layout for alignment. We don't do this for smaller fields as all objects need to be a multiple of 4bytes in size.
When we lay out fields we may create holes to improve alignment. For example, an int following a byte, we'll create a 3 byte hole following the byte to keep the int 4 byte aligned. Holes in the field layout can be 1, 2 or 4 bytes in size. As fields are laid out, holes are used to avoid increasing the size of the object. Sub-classes inherit the hole information of their parent, so holes in the parent object can be reused by their children.