Design Principles
Introduction
This chapter describes the principles that are common across the whole API.
Lists
Methods use java.util.List to return multiple objects.
All lists are immutable and unsynchronised.
For example, an ImageProcess may contain multiple instances of ManagedRuntime.
To retrieve the ManagedRuntime instances, a call would be made to:
List<ManagedRuntime> ImageProcess.getRuntimes()
.
Lists are sometimes used in the API to access a larger number of objects than would be used in most
Java applications.
For this reason, use of the
java.util.List.toArray()
method is discouraged in situations where there would be a large number of array elements.
For example:
JavaObject[] heapAsArray(JavaHeap heap) {
return heap.getObjects().toArray(new JavaObject[0]);
}
would return an array with all of the objects in the heap, perhaps numbering in the hundreds of millions.
This should be considered when implementing or calling the API.
Type names
Type names and signatures use the same format as JNI. Please see the JavaDoc for each
method for the exact formats.
The class java.util.Map.entry would be formatted like so:
java/util/Map$Entry
An multidimensional array java.util.Map.entry[][] is formatted like:
[[Ljava/util/Map$Entry;
A primitive array class for int[][]
is formatted like so:
[[I
Memory and Identification
Address space
Memory in the API consists of a collection of flat (i.e. not segmented) address spaces represented by the
ImageAddressSpace class. Implementations do not have to report any memory as being present
in the snapshot.
Memory sections
The ImageSection interface is used to describe arbitrary areas of memory by returning
a pointer (represented by a ImagePointer instance) and a size along with a name.
ImageAddressSpace instances use them to describe what memory is mapped in a snapshot.
They are also used to describe the memory layout of the entities that
the API interfaces represent. This is normally done with methods such as List<ImageSection> getSections()
.
The actual ImageSection instances returned are implementation specific and
it is acceptable for there to be none. It is expected that they will be used for:
determining the memory occupancy of items. For example, the heap size could be derived from JavaHeap.getSections()
.
accessing structures in memory. For example, JavaObject.getSections()
will return
all of the ImageSection instance representing the memory a
JavaObject occupies. With knowledge
of how an object is laid out on the heap, it would be possible to retrieve more information than
is retrieved presented by the API.
Addresses and Identification
The API uses the ImagePointer interface to identify objects returned by the API.
ImagePointer represents an address in memory, and enables programs to access memory
at that address and at offsets from that address. Given that not all implementations of the API allow access to
memory, the addresses returned could be entirely artificial.
When an ImagePointer is used as an address of an object from the API, it is up to
the implementation to decide what it is actually pointing at. It is important though that objects of the same
type have unique addresses. For example, JavaObject instances much each return an
ImagePointer different from all other, but there may be instances of JavaClass
that share the same address.
ImagePointer allows access to memory using Java types, corrected for endianness.
This means that only twos-complement values can be returned, apart from the char
which is unsigned in
Java. There is no conversion from the native platform's floating point formats. Floating point values are assumed to
be stored as Java floating point types.
Package Separation
While aspects of the image
API are used by the runtime.java
API, the reverse
will never occur. The image
API should, in principle, be implementable standalone.
For example, the following is allowed as it refers to the image package:
ImageThread JavaThread.getImageThread()
But the converse is not:
JavaThread ImageThread.getJavaThread()
Error Handling
Implementations of the API should present data as accurate and complete as reasonably possible under any circumstances. The purpose of the
API includes presenting the state of a running process, a Java Virtual Machine, when it encountered
abnormal conditions. The most extreme of these situations is when the native code implementing the JVM
itself has crashed. As such, there will be situations where information cannot be retrieved or may be incorrect.
This should be regarded as normal. Both implementors of the API and those calling the API should code
anticipating errors to be normal rather than exceptional conditions.
Data is retrieved from the API from two types of methods. Those returning multiple items using
generisised Lists and those returning items directly.
The lists that are returned are expected to return all items that they can.
When implementing lists, implementors should take care to:
ensure that lists have a finite number of items. For example, a corrupted linked list
may be corrupt or terminated incorrectly. The API implementation should detect this and
terminate the list.
process as little of the items being returned as possible. Better to continue reading the
collection of items rather than fail on one item that is slightly wrong. For example,
if the objects are being retrieved using the list from the following method call:
List<JavaObject> JavaHeap.getObjects()
... then if one object fails to identify its type properly, it
is expected that the list would return the JavaObject. Calls
to that JavaObject would fail appropriately, such as to
JavaClass JavaObject.getJavaClass()
.
Errors are reported on methods returning single items (i.e. not lists) using exceptions.
There are two exceptions, both subclasses of DiagnosticException.
The exceptions are:
MemoryAccessException. This is thrown when an attempt has been
made to access memory that is not present in the snapshot.
CorruptDataException. This is thrown when the data used to
form a response to the method call is incorrect.
Optional and missing data
There are circumstances where information cannot be supplied.
Methods that throw the exception DataUnavailable will do so if the
information is either not presentable by the implmentation of the API, or if it is not available
for that particular snapshot.
There are circumstances where null
is returned by a method. These circumstances will
be explicitly documented in the Javadoc.
DataUnavailable is thrown when the API cannot return
the requested data. null
is returned when the data was never there to be returned.
Methods that return java.util.List instances will always do so under all
circumstances.
Faked objects
There are many possible implementations of a JavaTM Virtual Machine each of which can have various and different optimisations.
Mapping a particular implementation to this API may require the creation of synthetic objects for entities which do not
actually exist in the diagnostic artifact.
An example of this would be array classes. These classes
are never loaded by a Javatm Virtual Machine, they are constructed as and when they are necessary.
It is conceivable that there would be no actual entities that could directly correspond with JavaClass
instances. In circumstances like these the API implementor would have to create a JavaClass
for the array class, as that is the only means the API has for identifying that objects type.
Faked objects should be implemented carefully. For instance, if a faked JavaMethod is created,
then the class it declares itself as belonging to should report it, otherwise inconsistencies
can arise that could cause calling programs to fail.
There should be no collisions between real and faked objects.
Implementations should not be misleading. If a faked object has been created, then related fack objects should be kept to a minimum.
For instance a faked JavaObject should not return ImageSection instances.
Object identities
All implementations should override the java.lang.Object.equals(Object)
method when objects are not permanently cached
and may need to be recreated.
All API's should use equals to test object identity.
The quantity of objects held within a diagnostic artifact normally means that it is impractical to keep an in-memory
instance for everything. Therefore the API does not require that repeated calls to return a specific object will in fact return the same instance.
The API allows for recreation of already requested objects
The behaviour of equals against objects from different snapshots is not
defined.