import java.io.IOException; import java.io.OutputStream; import java.lang.instrument.*; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; /** * A simple implementation of C++ keyword sizeof for Java 5+ * * @author Nicola Santi * @author Marco Rosi * * @see java#lang#instrument#Instrument * * @version 0.6 */ public class SizeOf { private static OutputStream out = System.out; /** * Instance of java.lang.instrument.Instrument injected by the Java VM * @see premain(String options, Instrumentation inst) */ private static Instrumentation inst; private static long MIN_CLASS_SIZE_TO_LOG = 1024 * 1024; private static boolean SKIP_STATIC_FIELD = false; private static boolean SKIP_FINAL_FIELD = false; private static boolean SKIP_FLYWEIGHT_FIELD = false; private static boolean debug = false; /** * Callback method used by the Java VM to inject the java.lang.instrument.Instrument * instance */ public static void premain(String options, Instrumentation inst) { SizeOf.inst = inst; System.out.println("JAVAGENT: call premain instrumentation for class SizeOf"); } /** * Calls java.lang.instrument.Instrument.getObjectSize(object). * * @param object the object to size * @return an implementation-specific approximation of the amount of storage consumed * by the specified object. * * @see java#lang#instrument#Instrument#Instrumentation#getObjectSize(Object objectToSize) */ public static long sizeOf(Object object) { if (inst == null) throw new IllegalStateException("Instrumentation is null"); if (SKIP_FLYWEIGHT_FIELD && isSharedFlyweight(object)) return 0; return inst.getObjectSize(object); } private static String[] unit = { "b", "Kb", "Mb" }; /** * Format size in a human readable format * * @param size * @return a string representation of the size argument followed by * b for byte, Kb for kilobyte or Mb for megabyte */ public static String humanReadable(long size) { int i; double dSize = size; for (i = 0; i < 3; ++i) { if (dSize < 1024) break; dSize /= 1024; } return dSize + unit[i]; } /** * Compute an implementation-specific approximation of the amount of storage consumed * by objectToSize and by all the objects reachable from it * * @param objectToSize * @return an implementation-specific approximation of the amount of storage consumed * by objectToSize and by all the objects reachable from it */ public static long deepSizeOf(Object objectToSize) { //Set doneObj = new HashSet(); Map doneObj = new IdentityHashMap(); return deepSizeOf(objectToSize, doneObj, objectToSize.getClass(), 0); } /** * @deprecated use deepSizeOf */ public static long iterativeSizeOf(Object objectToSize) throws IllegalArgumentException, IllegalAccessException, IOException { return deepSizeOf(objectToSize); } private static String indent(int depth) { StringBuilder builder = new StringBuilder(); for (int i = 0; i < depth; i++) builder.append(" "); return builder.toString(); } private static long deepSizeOf(Object o, Map doneObj, Class dsect, int depth) { if (o == null) { if (debug) print("null\n"); return 0; } long size = 0; boolean root = dsect == o.getClass(); if (doneObj.containsKey(o) && root) { if (debug) print("\n%s{ yet computed }\n", indent(depth)); return 0; } if (debug) print("\n%s{ %s\n", indent(depth), o.getClass().getName()); if(root) { doneObj.put(o,null); size = sizeOf(o); } if (dsect.getName().equals("java.lang.Object")) return size; //Applet3D.tracein(o.getClass()); //Applet3D.trace(size); if (o instanceof Object[]) { int i = 0; for (Object obj : (Object[]) o) { size += 4; if (obj == null) { //size += 4; continue; } if (debug) print("%s [%d] = ", indent(depth), i++); size += deepSizeOf(obj, doneObj, obj.getClass(), depth + 1); //Applet3D.trace(size); } } else { //Field[] fields = o.getClass().getSuperclass().getDeclaredFields(); Field[] fields = dsect.getDeclaredFields(); for (Field field : fields) { //System.out.println("FIELD = " + field); if(field.toString().equals("Composite Object3D.parent")) continue; field.setAccessible(true); Object obj; try { obj = field.get(o); if(obj == null) { //size += 4; continue; } //Applet3D.trace(":" + (obj==null?"NULL":"OK")); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } if (isComputable(field)) { if (debug) print("%s %s = ", indent(depth), field.getName()); //Applet3D.trace(field); size += deepSizeOf(obj, doneObj, obj.getClass(), depth + 1); } else { //if (debug) // print("%s %s = %s\n", indent(depth), field.getName(), obj); } } // Base class //Applet3D.trace("BASE CLASS = " + dsect.getSuperclass()); size += deepSizeOf(o, doneObj, dsect.getSuperclass(), depth); } if (debug) print("%s} size = %s\n", indent(depth), humanReadable(size)); //if (MIN_CLASS_SIZE_TO_LOG > 0 && size >= MIN_CLASS_SIZE_TO_LOG) // print("Found big object: %s%s@%s size: %s\n", indent(depth), o.getClass().getName(), System.identityHashCode(o), humanReadable(size)); //Applet3D.traceout(o.getClass(), size); return size; } /** * Return true if the specified class is a primitive type */ private static boolean isAPrimitiveType(Class clazz) { if (clazz == java.lang.Boolean.TYPE) return true; if (clazz == java.lang.Character.TYPE) return true; if (clazz == java.lang.Byte.TYPE) return true; if (clazz == java.lang.Short.TYPE) return true; if (clazz == java.lang.Integer.TYPE) return true; if (clazz == java.lang.Long.TYPE) return true; if (clazz == java.lang.Float.TYPE) return true; if (clazz == java.lang.Double.TYPE) return true; if (clazz == java.lang.Void.TYPE) return true; return false; } /** * @param field * @param obj * @return true if the field must be computed */ private static boolean isComputable(Field field) { int modificatori = field.getModifiers(); if (isAPrimitiveType(field.getType()) || Modifier.isTransient(modificatori)) return false; else if (SKIP_STATIC_FIELD && Modifier.isStatic(modificatori)) return false; else if (SKIP_FINAL_FIELD && Modifier.isFinal(modificatori)) return false; else return true; } /** * Returns true if this is a well-known shared flyweight. * For example, interned Strings, Booleans and Number objects. * * thanks to Dr. Heinz Kabutz * see http://www.javaspecialists.co.za/archive/Issue142.html */ private static boolean isSharedFlyweight(Object obj) { // optimization - all of our flyweights are Comparable if (obj instanceof Comparable) { if (obj instanceof Enum) { return true; } else if (obj instanceof String) { return (obj == ((String) obj).intern()); } else if (obj instanceof Boolean) { return (obj == Boolean.TRUE || obj == Boolean.FALSE); } else if (obj instanceof Integer) { return (obj == Integer.valueOf((Integer) obj)); } else if (obj instanceof Short) { return (obj == Short.valueOf((Short) obj)); } else if (obj instanceof Byte) { return (obj == Byte.valueOf((Byte) obj)); } else if (obj instanceof Long) { return (obj == Long.valueOf((Long) obj)); } else if (obj instanceof Character) { return (obj == Character.valueOf((Character) obj)); } } return false; } /** * The objects processed by deepSizeOf are logged to the log output stream if * their size (in byte) is greater than this value. * The default value is 1024*1024 (1 megabyte), 0 turn off logging */ public static void setMinSizeToLog(long min_class_size_to_log) { MIN_CLASS_SIZE_TO_LOG = min_class_size_to_log; } /** * If true deepSizeOf() doesn't compute the final fields of an object. * Default value is false. */ public static void skipFinalField(boolean skip_final_field) { SKIP_FINAL_FIELD = skip_final_field; } /** * If true deepSizeOf() doesn't compute the static fields of an object. * Default value is false. */ public static void skipStaticField(boolean skip_static_field) { SKIP_STATIC_FIELD = skip_static_field; } /** * If true flyweight objects has a size of 0. * Default value is false. */ public static void skipFlyweightObject(boolean skip) { SKIP_FLYWEIGHT_FIELD = skip; } private static void print(String s) { try { out.write(s.getBytes()); } catch (IOException e) { throw new RuntimeException(e); } } private static void print(String s, Object... args) { try { out.write(String.format(s, args).getBytes()); } catch (IOException e) { throw new RuntimeException(e); } } /** * Sets the OutputStream to use for logging. * The default OutputStream is System.out * @param o */ public static void setLogOutputStream(OutputStream o) { if (o == null) throw new IllegalArgumentException("Can't use a null OutputStream"); out = o; } /** * Turn on debugging information */ public static void turnOnDebug() { debug = true; } /** * Turn off debugging information */ public static void turnOffDebug() { debug = true; } }