Object-Creation Profiling Profiling Tools

- 39 - Nevertheless, after re-sorting the section on the time field, rather than the count field, the profile data is useful enough to suffice as a method profiler when you have no better alternative. One problem Ive encountered is the limited size of the list of methods that can be held by the internal profiler. Technically, this limitation is 10,001 entries in the profile table, and there is presumably one entry per method. There are four methods that help you avoid the limitation by profiling only a small section of your code: sun.misc.VM.suspendJavaMonitor sun.misc.VM.resumeJavaMonitor sun.misc.VM.resetJavaMonitor sun.misc.VM.writeJavaMonitorReport These methods also allow you some control over which parts of your application are profiled and when to dump the results.

2.4 Object-Creation Profiling

Unfortunately, the object-creation statistics available from the Sun JDK provide only very rudimentary information. Most profile tool vendors provide much better object-creation statistics, determining object numbers and identifying where particular objects are created in the code. My recommendation is to use a better probably commercial tool than the JDK profiler. The heap-analysis tool search www.java.sun.com for HAT , which can analyze the default profiling mode with Java 2, provides a little more information from the profiler output, but if you are relying on this, profiling object creation will require a lot of effort. To use this tool, you must use the binary output option to the profiling option: java -Xrunhprof:format=b classname I have used an alternate trick when a reasonable profiler is unavailable, cannot be used, or does not provide precisely the detail I need. This technique is to alter the java.lang.Object class to catch most nonarray object-creation calls. This is not a supported feature, but it does seem to work on most systems, because all constructors chain up to the Object classs constructor , and any explicitly created nonarray object calls the constructor in Object as its first execution point after the VM allocates the object on the heap. Objects that are created implicitly with a call to clone or by deserialization do not call the Object classs constructor, and so are missed when using this technique. Under the terms of the license granted by Sun, it is not possible to include or list an altered Object class with this book. But I can show you the simple changes to make to the java.lang.Object class to track object creation. The change requires adding a line in the Object constructor to pass this to some object-creation monitor you are using. java.lang.Object does not have an explicitly defined constructor it uses the default empty constructor, so you need to add one to the source and recompile. For any class other than Object , that is all you need to do. But there is an added problem in that Object does not have a superclass, and the compiler has a problem with this: the compiler cannot handle an explicit super from the Object class, nor the use of this , without an explicit super or this call. In order to get around this restriction, you need to add a second constructor to java.lang.Object : a constructor that does nothing functional but does seem to satisfy the compiler. - 40 - This trick works for the compiler that comes with the JDK; other compilers may be easier or more difficult to satisfy. It is specifically the compiler that has the problem. Generating the bytecodes without the extra constructor is perfectly legal. Recursive calls to the Object constructor present an additional difficulty. You must ensure that when your monitor is called from the constructor, the Object constructor does not recursively call itself as it creates objects for your object-creation monitor. It is equally important to avoid recursive calls to the Object constructor at runtime initialization. The simplest way to handle all this is to have a flag on which objects are conditionally passed to the monitor from the Object constructor, and to have this flag in a simple class with no superclasses, so that classloading does not impose extra calls to superclasses. So essentially, to change java.lang.Object so that it records object creation for each object created, you need to add something like the following two constructors to java.lang.Object : public Object { thistrue; if tuning.profile.ObjectCreationMonitoringFlag.monitoring tuning.profile.ObjectCreationMonitoring.monitorthis; } public Objectboolean b { } This code may seem bizarre, but then this technique uses an unsupported hack. You now need to compile your modified java.lang.Object and any object-monitoring classes I find that compiling the object-monitoring classes separately before compiling the Object class makes things much easier. You then need to run tests with the new Object class [7] first in your boot classpath. The modified Object class must be before the real java.lang.Object in your classpath, otherwise the real one will be found first and used. [7] Different versions of the JDK require their Object classes to be recompiled separately; i.e., you cannot recompile the Object class for JDK 1.1.6 and then run that class with the 1.2 runtime. Once you have set the tuning.profile.ObjectCreationMonitoringFlag.monitoring variable to true , each newly created object is passed to the monitor during the creation call. Actually, the object is passed immediately after it has been created by the runtime system but before any constructors have been executed, except for the Object constructor. You should not set the monitoring variable to true before the core Java classes have loaded: a good place to set it to true is at the start of the application. Unfortunately, this technique does not catch any of the arrays that are created: array objects do not chain through the Object constructor although Object is their superclass, and so do not get monitored. But you typically populate arrays with objects except for data type arrays such as char arrays, and the objects populating the arrays are caught. In addition, objects that are created implicitly with a call to clone or by deserialization do not call the Object classs constructor, and so these objects are also missed when using this technique. Deserialized objects can be included using a similar technique by redefining the ObjectInputStream class. When I use this technique, I normally first get a listing of all the different object types that are created and the numbers of those objects that are created. Then I start to focus on a few objects. If you prefer, you can make the technique more focused by altering other constructors to target a specific hierarchy below Object . Or you could focus on particular classes within a more general - 41 - monitoring class by filtering interesting hierarchies using instanceof . In addition, you can get the stack of the creation call for any object by creating an exception or filling in the stack trace of an existing exception but not throwing the exception. As an example, I will define a monitoring class that provides many of the possibilities you might want to use for analysis. Note that to avoid recursion during the load, I normally keep my actual ObjectCreationMonitoringFlag class very simple, containing only the flag variable, and put everything else in another class with the monitor method, i.e., the following defines the flag class: package tuning.profile; public class ObjectCreationMonitoringFlag { public static boolean monitoring = false; } The next listed class, ObjectCreationMonitoring , provides some of the features you might need in a monitoring class, including those features previously mentioned. It includes a main method that starts up the real application you wish to monitor and three alternative options. These report every object creation as it occurs -v , a tally of object creations -t , or a tally of object-creation stacks -s ; this option can take a long time. If you run JDK 1.2 [8] and have the recompiled Object class in a JAR file with the name hack.jar in the current directory, and also copy the rt.jar and i18n.jar files from under the JDK1.2jrelib JDK1.2\jre\lib directory to the current directory, then as an example you can execute the object- creation monitoring class on Windows like this note that this is one long command line: [8] With JDK 1.3, there is a nicer prepend option to the bootclasspath, which allows you to execute using: java -Xbootclasspath:hack.jar;rt.jar;i18n.jar tuning.profile.ObjectCreationMonitoring -t real class and arguments You might also need to add a -cp option to specify the location of the various non-core class files that are being run, or add to the -classpath list for JDK 1.1. The files listed in the - Xbootclasspath option can be listed with relative or absolute paths; they do not have to be in the current directory. For Unix it looks like this the main difference is the use of ; for Windows and : for Unix: java -Xbootclasspath:hack.jar:rt.jar:i18n.jar tuning.profile.ObjectCreationMonitoring -t real class and arguments For JDK 1.1, the classpath needs to be set instead of the bootclasspath, and the classes.zip file from JDK1.1.xlib needs to be used instead, so the command on Windows looks like: java -classpath hack.jar;classes.zip tuning.profile.ObjectCreationMonitoring -t real class and arguments For Unix it looks like this again, the main difference is the use of ; for Windows and : for Unix: java -classpath hack.jar:classes.zip tuning.profile.ObjectCreationMonitoring -t real class and arguments Using one of these commands to monitor the tuning.profile.ProfileTest class used in the earlier example from Section 2.3.1 results in the following output: - 42 - Starting test The test took 3425 milliseconds java.lang.FloatingDecimal 16000 java.lang.Double 16000 java.lang.StringBuffer 2 java.lang.Long 20000 java.lang.FDBigInt 156022 java.util.Hashtable 1 java.util.HashtableEntry 18 java.lang.String 36002 To recap, that program repeatedly 2000 times appends 8 double s and 10 long s to a StringBuffer and inserts those numbers wrapped as objects into a hash table. The hash table requires 16,000 Double s and 20,000 Long s, but beyond that, all other objects created are overheads due to the conversion algorithms used. Even the String objects are overheads: there is no requirement for the numbers to be converted to String s before they are appended to the Stringbuffer . In Chapter 5 , I show how to convert numbers and avoid creating all these intermediate objects. The resulting code produces faster conversions in every case. Implementing the optimizations mentioned at the end of the section Section 2.3.1 allows the program to avoid the FloatingDecimal class and consequently the FDBigInt class too and also to avoid the object wrappers for the double s and long s. This results in a program that avoids all the temporary FloatingDecimal , Double , Long , FDBigInt , and String objects generated by the original version: over a quarter of a million objects are eliminated from the object-creation profile, leaving just a few dozen objects So the order-of-magnitude improvement in speed attained is now more understandable. The ObjectCreationMonitoring class used is listed here: package tuning.profile; import java.util.; import java.io.; import java.lang.reflect.; public class ObjectCreationMonitoring { private static int MonitoringMode = 0; private static int StackModeCount = -1; public static final int VERBOSE_MODE = 1; public static final int TALLY_MODE = 2; public static final int GET_STACK_MODE = 3; public static void mainString args[] { try { First argument is the option specifying which type of monitoring: verbose; tally; or stack ifargs[0].startsWith-v verbose - prints every objects class as its created MonitoringMode = VERBOSE_MODE; else ifargs[0].startsWith-t tally mode. Tally classes and print results at end MonitoringMode = TALLY_MODE; else ifargs[0].startsWith-s { stack mode. Print stacks of objects as they are created MonitoringMode = GET_STACK_MODE; support a limited number of stacks being generated - 43 - so that the running time can be shortened ifargs[0].length 2 StackModeCount = Integer.parseIntargs[0].substring2; } else throw new IllegalArgumentException First command line argument must be one of -v-t-s; Remaining arguments are the class with the main method, and its arguments String classname = args[1]; String[] argz = new String[args.length-2]; System.arraycopyargs, 2, argz, 0, argz.length; Class clazz = Class.forNameclassname; main has one parameter, a String array. Class[] mainParamType = {args.getClass }; Method main = clazz.getMethodmain, mainParamType; Object[] mainParams = {argz}; start monitoring ObjectCreationMonitoringFlag.monitoring = true; main.invokenull, mainParams; stop monitoring ObjectCreationMonitoringFlag.monitoring = false; if MonitoringMode == TALLY_MODE printTally ; else if MonitoringMode == GET_STACK_MODE printStacks ; } catchException e { e.printStackTrace ; } } public static void monitorObject o { Disable object creation monitoring while we report ObjectCreationMonitoringFlag.monitoring = false; switchMonitoringMode { case 1: justPrinto; break; case 2: tallyo; break; case 3: getStacko; break; default: System.out.println Undefined mode for ObjectCreationMonitoring class; break; } Re-enable object creation monitoring ObjectCreationMonitoringFlag.monitoring = true; } public static void justPrintObject o { System.out.printlno.getClass .getName ; } private static Hashtable Hash = new Hashtable ; public static void tallyObject o { - 44 - You need to print the tally from printTally at the end of the application Integer i = Integer Hash.geto.getClass ; if i == null i = new Integer1; else i = new Integeri.intValue + 1; Hash.puto.getClass , i; } public static void printTally { should really sort the elements in order of the number of objects created, but I will just print them out in any order here. Enumeration e = Hash.keys ; Class c; String s; whilee.hasMoreElements { c = Class e.nextElement ; System.out.prints = c.getName ; for int i = 31-s.length ; i = 0; i-- System.out.print ; System.out.print\t; System.out.printlnHash.getc; } } private static Exception Ex = new Exception ; private static ByteArrayOutputStream MyByteStream = new ByteArrayOutputStream ; private static PrintStream MyPrintStream = new PrintStreamMyByteStream; public static void getStackObject o { if StackModeCount 0 StackModeCount--; else if StackModeCount = -1 return; Ex.fillInStackTrace ; MyPrintStream.flush ; MyByteStream.reset ; MyPrintStream.printCreating object of type ; MyPrintStream.printlno.getClass .getName ; Note that the first two lines of the stack will be getStack and monitor , and these can be ignored. Ex.printStackTraceMyPrintStream; MyPrintStream.flush ; String trace = new StringMyByteStream.toByteArray ; Integer i = Integer Hash.gettrace; if i == null i = new Integer1; else i = new Integeri.intValue + 1; Hash.puttrace, i; } public static void printStacks { Enumeration e = Hash.keys ; String s; whilee.hasMoreElements { s = String e.nextElement ; - 45 - System.out.printFollowing stack contructed ; System.out.printHash.gets; System.out.println times:; System.out.printlns; System.out.println ; } } }

2.5 Monitoring Gross Memory Usage