Examples Introduction This chapter contains snippets of code demonstrating how to call the API. The appendices contain the unedited samples. Opening Images Image instances are obtained using the javax.tools.diagnostics.FactoryRegistry class. See for a complete example. Programs using the API can obtain an Image like so: Opening an Image FactoryRegistry uses javax.imageio.spi.ServiceRegistry as a registry of all API implementations known to the JVM. Implementors should ensure that FactoryRegistry is able to see their ImageFactory by placing their implementation in a jar file that contains a file called META-INF/javax.tools.diagnostics.image.ImageFactory that contains a line of text that is the name of the ImageFactory implementation, such as com.example.dump.ImageFactoryImpl Determining Snapshot Cause Snapshots can be generated for a variety of reasons. The API can report that a snapshot has been generated because the JVM received a POSIX type signal, whether it was synchronous or asynchronous. For example, if a JNI library causes a SIGSEGV when running, this might be detectable through the API. In most cases, it should be possible through ImageAddressSpace.getCurrentProcess() and ImageProcess.getCurrentThread() to determine which process and which thread caused the snapshot. If a snapshot was generated for a reason other than a POSIX signal being received, then the reason has to be derived through knowledge of the JVM implementation. For example, if an option was passed to the JVM to generate a snapshot on entry to a particular method, all of the stack traces could be searched to determine if that method was present, and therefore probably causing the snapshot. Likewise, detecting a call to abort() in the native stack would suggest that the snapshot was caused by a synchronous SIGABRT. The following examples are extracts from the example program in . The examples implement the ImageAnalyzer interface, they just need to implement the analyze(Image) method. CauseAnalyzer Class declaration The containing ImageAddressSpace instances are searched for a current process. It is probable that there will be one address space and one process. If getCurrentProcess() returns null, there was no current process. Because List is returned, we can use a for-each loop. Find Current Process Once found, the process can be queried for signal information and thread information. If a signal was raised, ImageProcess.getSignalName() will not be null. The example reports the signal name and number to the user. Reporting signal information The ImageProcess can report the command line. This is the command name and arguments that were used to start the process. The command and arguments are returned in a single string, separated by spaces. The process ID returned by getID() in a String. This is implementation specific, and so could be in any format, whether that be hexadecimal, decimal or some other arbitrary string. Process ID and commandline The program determines which thread caused the snapshot to be generated. If the current thread isn't null, the thread is identified by ID and its properties (implementation dependent). Thread identification Next, the thread's stack frames are printed out. The code relies on java.lang.Object.toString() being implemented correctly. It is expected that the most recent frame will be returned first. ImageThread stack trace This section of code relies on the relationship between JavaThread instances and ImageThread instances to determine which Javatm thread caused the snapshot. As the relationship is one-way, from Javatm to Image, all of the JavaThread instances have to be queried. Because JavaThread.getImageThread() might be null, java.lang.Object.equals is executed against the ImageThread which we know to be not null. Once the JavaThread is found, its name can be printed. JavaThread/ImageThread correlation Identifying Java<superscript>tm</superscript> VM This example demonstrates how the JVM that generated a snapshot might be identified. The following information is reported using the image and java APIs: hostname of the machine the snapshot was generated on. The process ID. The command line. The executable that was running the Javatm program (e.g. "/usr/bin/java"). The command line. The loaded native libraries. The version reported by the JVM. The options passed to the JVM. The complete listing is in . Like the previous example, this implements ImageAnalyzer. There is some functionality that is also present in the previous example - it is not repeated here. Declaration of WhatAnalyzer class The hostname of the machine where the snapshot was generated is printed out. This isn't information that is necessarily available in most core dump formats, instead this would normally be recorded by the program. As it is important to get out as much information as possible, calls to the API are made with try/catch blocks around each individual method. This is necessary as exceptions should be expected to be raised under most circumstances. The stack traces for DataUnavailable are not usually reported as this is not an error condition. Instead, a message is inserted to indicate that the information is not known. Getting the hostname "; e.printStackTrace(); } System.out.println("Snapshot was generated on " + hostname); ]]> Here the executable that started the process is reported. This is the executable that launched the JVM - typically this is the "java" program. Alternatives include "javac" and "appletviewer". This is the name of the executable the operating system loaded into memory when creating the process. Executable name This code prints out the names of the libraries that were loaded by the process. This should include any JNI libraries that were configured. Note that it is also possible to determine where in memory these libraries have been loaded into memory using the getSections() method. This can be used to identify where a thread might have crashed. Process libraries The following code reports the Javatm VM version. This is implementation dependent, but is expected to contain more than just the version of Javatm that is supported, but actually identify which implementation of the JVM it is. Java<superscript>tm</superscript> VM Version The following code reports the options that were passed to the JVM when it was created. The options are generated and passed on by the executable the launches Java. Some of these might be passed on the command line (such as by the "java" executable), but might also include options taken in from configuration files, as well as being generated by the launcher itself. Java<superscript>tm</superscript> VM Options Retrieving Object Fields This example demonstrates how object instance fields and array elements are accessed using the API. The complete listing is in . The analyzeRuntime method walks over the heaps within the JVM. While Javatm programmers will be used to the concept of the heap, the API allows a number of heaps to be accessed in a single JVM. It is expected the different heaps will have different garbage collection policies and that each heap will be identified with a descriptive name through JavaHeap.getName(). The number of heaps and their names is implementation specific, but there must be at least one in a running JVM. Iterate over heaps This section of code retrieves each object from a heap. For API implementations backed by a core file, the objects will probably be retrieved in order from lowest address in memory to the highest, but there is no relationship between JavaObject list indexes and the results of JavaObject.getID() that can be relied upon by callers of the API. The JavaObject retrieved has to be tested to see if it is an array or an ordinary object as they are handled differently. Iterate over Objects This method takes a JavaObject and prints out the values of all of the instance fields (not the static fields). To identify each object, its ID is used. This is turned into a hex string using the pointerToHexString(ImagePointer) that is included in this example. Print object fields Each object in an instance of a class, so here the JavaClass is retrieved. This is equivalient to the following in Java: Class java.lang.Object.getClass(); Implementors should ensure that the API returns the equivalent JavaClass. Get the object's type A class will only report its fields, the superclasses must be retrieved in order to retrieve their fields. This while loop retrieves each superclass until the superclass is null, which will be returned by the java.lang.Object class. The class name is printed out, which should match what java.lang.Class.getName() would return, except for "." characters being replaced by "/". Iterate up class hierarchy This code retrieves each field from a JavaClass. This is equivalent to the following method in Javatm reflection: Field[] java.lang.Class.getDeclaredFields() This should return all fields, even synthetic fields. The DiagnosticException is a superclass of CorruptDataException and DataUnavailable. Print out each field Here the next superclass is retrieved. This will return null if the class has no superclass, such as java.lang.Object. The loop is terminated by the break if the superclass couldn't be retrieved. get next superclass This method demonstrates how to print out an instance field. Note that the JavaObject is passed as it must passed on to the JavaField for it to retrieve the value of the field in that instance. Print fields class It is not worth printing out the class fields for each instance of the class on the heap, so the field is tested to see if it is static. The following method call retrieves the modifiers (public, static, protected, etc.) from the JavaField and then uses reflection to test for static being set. Callers of the API should not assume that JavaField.getModifiers() only returns the bits defined in java.lang.reflect.Modifier - always test with the appropriate bitmasks or use the functions provided in Modifier. Testing JavaField.getModifiers() There are a number of methods provided by JavaField to retrieve the field value. The most generic is JavaField.get(JavaObject) which returns an Object. Getting the value of a field Object references that were null in the running program are also returned as null by the API. JavaField.get() returns null "; ]]> As JavaField.get(Object) can return any type, primitive fields values are returned in instances of Number or Character. For instance, an int would be returned as an instance of java.lang.Integer. This can't be confused with fields that are references to java.lang.Integer instances as they would be represented by JavaObject. Boxed numbers JavaField.get() is the means by which references to other objects are also retrieved. This program just retrieves the referred object's class name and its ID. It is important to remember that the signature of the field is expected to be an appropriate type for the objects that can be retrieved from it. A field signature would be either the same type as an object retrieved from it, an interface or super-interface, or a super class. Retrieving an object reference The following code tests the object type to see if it is a Javatm String instance. The JavaClass representing java.lang.String could be cached and compared against the objects classes, but instead we compare against the name of the object's class. The method JavaField.getString() is used to retrieve the JavaObject as an instance of java.lang.String in the running JVM. Retrieving a string field This method deals only with arrays, which are treated differently from ordinary objects when retrieving their contents. Note that arrays don't have a field called "length". Method for printing out array contents All instances of JavaObject have a JavaClass with a name. For arrays, this follows the JNI conventions. An integer array would be called "[I", whereas an array of strings would be called "[Ljava/lang/String;". All objects have classes Each array describes the number of elements it contains. It is important to call getArraySize() and not getSize() as the latter returns the size of the object on the heap. Get number of array elements An array's class should be able to report the type of its elements. This call is used to determine the type of array to receive the contents of the array. Getting the type of the array elements Arrays elements are not accessed on an individual basis. Instead, their contents are copied to real arrays. This code demonstrates that there are JavaClass for primitive types, in the same way there is in reflection. These names are used to create primitive arrays of the correct type. Javatm reflection functions in the same way. Creating array of correct type If an array is not an array of primitives, it must be an array of objects. As there is no means of converting a JavaObject into a "real" object, an array of JavaObject instance is returned. Multidimensional arrays are returned as arrays of JavaObject instances that are themselves arrays. The example code shows how a array to receive object arrays is allocated. Array of JavaObjects as destination The method JavaObject.arraycopy is used to copy array elements in the same way as java.lang.System.arraycopy(). Implementors and those writing applications using this method call should take care as arrays can be extremely large, potentially larger than the JVM's heap size. If a fraction of an array is asked for, that is all that should be allowed in memory. Copying array contents This code prints out the contents of the array elements. The java.lang.Array.get() method is used to retrieve elements from the array in a generic fashion. If null is retrieved, that is printed, otherwise if it is an object the type and address of the object is printed and failing that it must be an autoboxed primitive that can be printed out using its toString(). The CorruptDataException is caught within the loop to allow the printing to continue even if some of the elements can't be located. Printing out array elements