package de.proplant.debug; import java.io.OutputStream; import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.HashSet; import java.util.Vector; /** * collection of "strange" procedures * * @author Olaf Diehl */ public class MemoryTools { // ------------------------------------ dump object contents for debugging purposes --------- /** * dump contents for debugging purposes. The given object is scanned by reflection, * so every object including arrays and arrays of primitive types can be dumped. * Object members of classes which are not within a package PACKAGE_HOME.* are suppressed. * There is a special treatment of Vector and HashMap, but not of other JFC container. * * @param out send output to this output stream (e.g System.err) * @param prefix print each line with this prefix * @param obj the object to dump */ public static void debugDump(OutputStream out, String prefix, Object obj) { PrintWriter pout = new PrintWriter(out); pout.println(); list.clear(); try { debugDumpField(pout, prefix, obj.getClass(), obj, "this", 0); } catch (NullPointerException e) { pout.println(prefix + "********** uninitialized element! (" + e.getMessage() + ")"); e.printStackTrace(); } pout.flush(); list.clear(); } /** * dump all fields of the given object. * This method is called by debugDumpField to dump the fields childs, and * it calls debugDumpField to print one field without childs, too. * * @param out send output to this writer * @param prefix print each line with this prefix * @param obj the object to dump * @param arrName in the case of container variables, this is the name of the top * level container (e.g. name of array) */ private static void debugDumpAllFields(PrintWriter out, String prefix, Object obj, String arrName) { if (obj == null) { out.println(prefix + "null"); return; } try { Class c = obj.getClass(); String cname = c.getName(); if (cname.startsWith("java.lang")) { // do nothing for primitive types } else if (c.isArray()) // array: dump each element as done with object fields { Object[] arr = (Object[]) obj; for (int j=0; j superclass " + c.getName() + ":"); Field[] fields = c.getDeclaredFields(); Field.setAccessible(fields, true); for (int i=0; i 0) className = s.getName().substring(pointPos + 1); else className = s.getName(); if (obj == null) { out.println(prefix + modifier + className + cname + " " + varName + " = null"); return; } ObjectKey key = new ObjectKey(obj); // check if de.proplant object has already been dumped if (!c.isPrimitive() && list.contains(key)) { out.println(prefix + modifier + className + cname + " " + varName + " = " + obj + " (already dumped)"); return; } if (! ( c.equals(String.class) || c.equals(Integer.class) || c.equals(Double.class) ) ) list.add(key); if (c.isArray()) // an array { if (c.getComponentType().isPrimitive()) // for arrays of primitives, we have to cast // explicitly and dump childs immediatly... :( { isPrimitive = true; out.println(prefix + modifier + className + " " + varName + "[0.." + (Array.getLength(obj) - 1) + "]"); for (int k=0; k]"); } // simple object else { try { out.println(prefix + modifier + className + " " + varName + " = " + obj.toString()); } catch (Exception e) { out.println(prefix + modifier + className + " " + varName + " = "); } } if (!isPrimitive && dumpChilds) debugDumpAllFields(out, prefix, obj, varName); // dump object fields recursivly } // ------------------------------------ determine object size --------- /** * determine object size including all sub-objects. The given object is scanned by reflection, * so every object including arrays and arrays of primitive types can be counted. * The basic sizes of primitve types are hard-coded (see sizeOf()); Object references are assumed 4 bytes. * CAUTION: the actual memory consumption of the VM may be different, e.g. due to wrong presumptions or memory alignment. * sub-objects referenced by several other objects are counted once even if their equals method is overwritten. * static fields are never counted. * final fields are counted. * WeakReference Objects are counted, but not stepped into * transient fields are counted, but stepped into dependent on flag debugSizeTransient * arrays are presumed to have a 4 byte length field * * @param prefix print each line with this prefix * @param obj the object to size */ public static void debugSize(String prefix, Object obj) { list.clear(); try { size = -4; // substract this debugSizeField(prefix, obj.getClass(), obj, "TEST ", 0); String name = obj.toString(); if (name.length() > 70) name = name.substring(0, 70) + " ..."; System.err.println(prefix + ": Size of " + name + " is " + size); } catch (NullPointerException e) { System.err.println("********** uninitialized element! (" + e.getMessage() + ")"); e.printStackTrace(); } list.clear(); } /** * add all field sizes of the given object. * This method is called by debugSizeField to dump the field childs, and * it calls debugSizeField to print one field without childs, too. * * @param prefix print each line with this prefix * @param obj the object to dump * @param arrName in the case of container variables, this is the name of the top * level container (e.g. name of array) */ private static void debugSizeAllFields(String prefix, Object obj, String arrName) { if (obj == null) { if (DEBUG_MEMSIZE) System.err.println("all: obj null, +4"); size += 4; return; } try { Class c = obj.getClass(); if (c.isPrimitive() || c.equals(WeakReference.class)) { // do nothing for primitive types } else if (c.isArray()) // array: dump each element as done with object fields { Object[] arr = (Object[]) obj; for (int j=0; j 0) className = classForName.getName().substring(pointPos + 1); else className = classForName.getName(); if (obj == null) { if (DEBUG_MEMSIZE) System.err.println(prefix + modifier + className + cname + " " + varName + " = null, +4"); size += 4; return; } ObjectKey key = new ObjectKey(obj); // ensure every object is different from every object // check if PACKAGE_HOME object has already been dumped if (!c.isPrimitive() && list.contains(key)) { if (DEBUG_MEMSIZE) System.err.println(prefix + modifier + className + cname + " " + varName + " (already counted)"); return; // count once } list.add(key); if (c.isArray()) // an array { if (c.getComponentType().isPrimitive()) // for arrays of primitives, we have to cast // explicitly and dump childs immediatly... :( { isPrimitive = true; int elemSize = sizeOf(s); if (DEBUG_MEMSIZE) System.err.println(prefix + modifier + className + cname + " " + varName + " +4 +" + elemSize * Array.getLength(obj)); size += 4 + elemSize * Array.getLength(obj); } else // array of non-primitive type { if (DEBUG_MEMSIZE) System.err.println(prefix + modifier + className + cname + " " + varName + " +4"); size += 4; // object members counted recursivly } } // simple object else { isPrimitive = c.isPrimitive(); if (isPrimitive) { if (DEBUG_MEMSIZE) System.err.println(prefix + modifier + className + cname + " " + varName + " +" + sizeOf(c)); size += sizeOf(c); } else { if (DEBUG_MEMSIZE) System.err.println(prefix + modifier + className + cname + " " + varName + " +" + 4); size += 4; } } if (!isPrimitive && dumpChilds) debugSizeAllFields(prefix, obj, varName); // dump object fields recursivly } private static int sizeOf(Class c) { if (c.equals(Boolean.TYPE)) return 4; else if(c.equals(Character.TYPE)) return 2; else if(c.equals(Byte.TYPE)) return 1; else if(c.equals(Short.TYPE)) return 2; else if(c.equals(Integer.TYPE)) return 4; else if(c.equals(Long.TYPE)) return 8; else if(c.equals(Float.TYPE)) return 4; else if(c.equals(Double.TYPE)) return 8; else return 4; // object reference } /** * rely on object identy instead on equals method */ static private class ObjectKey { public ObjectKey(Object obj) { this.obj = obj; } public final boolean equals(Object object) { return obj == ((ObjectKey) object).obj; } public final int hashCode() { return obj == null ? 0 : obj.hashCode(); } private Object obj; } /** * traverse recursivly through an object tree, looking for objects of a specific class. If found, print * reference path to the object. There is no shorter reference path than the printed one. * * @param out send output to this output stream (e.g System.err) * @param root root object to search from * @param aLookFor look for objects of this class type */ public static void debugReferencePath(OutputStream out, Object root, Class aLookFor) { pout = new PrintWriter(out); pout.println(); lookFor = aLookFor; list.clear(); try { objs = new Vector(); fieldNames = new Vector(); paths = new Vector(); objs.add(root); fieldNames.add("this"); paths.add(new Path(null, root, "this")); debugCheckList(); } catch (NullPointerException e) { pout.println("********** uninitialized element! (" + e.getMessage() + ")"); } pout.flush(); list.clear(); } /** * bredth first search of all objects * * 1. check all objects given in argument vectors * 2. collect objects of next level * 3. call debugCheckList again */ private static void debugCheckList() { // 1. check all objects boolean checkAll[] = new boolean[objs.size()]; // flag if we should continue on this object (e.g. is found first time) for (int i = 0; i < objs.size(); ++i) // print each object field checkAll[i] = debugCheckField(objs.get(i), (String) fieldNames.get(i), (Path) paths.get(i)); // 2. collect next level moreObjs = new Vector(32768); moreFieldNames = new Vector(32768); morePaths = new Vector(32768); try { for (int i = 0; i < objs.size(); ++i) // print each object field { if (! checkAll[i]) continue; // primitive | already checked | null | ... Object field = objs.get(i); String fieldName = (String) fieldNames.get(i); Path path = (Path) paths.get(i); if (field.getClass().isArray()) { if (field.getClass().getComponentType().isPrimitive()) { } else { Object[] arr = (Object[]) field; for (int j = 0; j < arr.length; ++j) { moreObjs.add(arr[j]); String nextName = fieldName + "[" + j + "]"; moreFieldNames.add(nextName); morePaths.add(new Path(path, arr[j], nextName)); } } } else { // scan all super classes unless they are not our own for (Class c = field.getClass(); c != null; c = c.getSuperclass()) { Field[] someFields = c.getDeclaredFields(); Field.setAccessible(someFields, true); for (int j = 0; j < someFields.length; ++j) // print each object field { moreObjs.add(someFields[j].get(field)); moreFieldNames.add(someFields[j].getName()); morePaths.add(new Path(path, someFields[j].get(field), someFields[j].getName())); } } } } } catch (Exception e) { pout.println("unexpected exception in Tools.debugDump: " + e.getMessage()); e.printStackTrace(); } // 3. recursive call if (moreObjs.size() > 0) { objs = moreObjs; fieldNames = moreFieldNames; paths = morePaths; debugCheckList(); } } /** * check one field if it is of requested type * * @param field field to check * @param path current path from root to field */ private static boolean debugCheckField(Object field, String fieldName, Path path) { if (field == null) return false; try { // check if de.proplant object has already been dumped if (list.contains(field)) return false; } catch (Exception e) { return false; } if (field instanceof java.lang.ref.WeakReference) return false; list.add(field); if (lookFor.isAssignableFrom(field.getClass()) ) { pout.println("Object found:"); pout.println(path.toString()); } return true; } private static class Path { public Path(Path path, Object node, String nodeName) { this.path = path; this.node = node; this.nodeName = nodeName; } public String toString() { String res = ""; if (path != null) res = path.toString(); String nodeVal = node.toString(); if (nodeVal.length() > 80) nodeVal = nodeVal.substring(0, 80); res += " " + getClassName(node) + " " + nodeName + ": " + nodeVal + "\n"; return res; } private Path path; private Object node; private String nodeName; } private static String getClassName(Object obj) { Class c = obj.getClass(); Class s; String cname = ""; for (s = c; s.isArray(); s = s.getComponentType()) cname = cname + "[]"; return s.getName() + cname; } /** * sample / test code * @param args unused */ public static void main(String[] args) { TestObject testObj = new TestObject(); debugDump(System.err, "TEST ", testObj); System.err.println("-----------------------"); debugSize("TEST ", testObj); System.err.println("-----------------------"); debugReferencePath(System.err, testObj, StringBuffer.class); } private static class TestObject { public TestObject() { vec.add(str); vec.add("foo"); vecEqual.add(str); vecEqual.add("foo"); vecClone = (Vector) vec.clone(); Vector v = new Vector(); v.add(new StringBuffer()); map.put("key", "val"); map.put("key2", v); } int i; double d; static String s = "static"; final String f = "final"; String str = "hello, world"; Vector vec = new Vector(); Vector vecClone = new Vector(); Vector vecEqual = new Vector(); Object[] obj = new Object[] {s, f, s}; HashMap map = new HashMap(); WeakReference wr = new WeakReference(vec); String[] sarr = new String[] {"one", "two", "three"}; transient String[] tarr = new String[] {"one", "two", "three"}; } /** avoid cyclic dump of objects in debugDump */ static HashSet list = new HashSet(8193); /** debugReference path related fields */ private static PrintWriter pout; private static Class lookFor; private static Vector objs; private static Vector fieldNames; private static Vector paths; private static Vector moreObjs; private static Vector moreFieldNames; private static Vector morePaths; /** object size */ private static int size; /** maximum number of elements in an container which are dumped by debugDump */ static final int DUMP_MAX_COUNT = 20; // limit for dumping arrays / Collections static boolean debugDumpFinals = false; // flag if finals are dumped static boolean debugDumpTransients = false; // flag if transients are dumped static boolean debugDumpWeakReferences = false; // flag if weak references are dumped static boolean debugSizeTransient = true; // flag if transient fields are traversed, too private static final String PACKAGE_HOME = "de.proplant"; // debugDump only; dump only below this package private static final boolean DEBUG_MEMSIZE = true; // debug/test output of debugSize }