- 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