/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.util.perf.objectsize;
import gw.util.perf.InvocationCounter;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Stack;
public class ObjectSizeUtil {
public static boolean VERBOSE = true;
private static final String INDENT = " ";
private static final IObjectSizeFilter DEFAULT_FILTER = new DefaultObjectSizeFilter();
private static DecimalFormat decimalFormat = new DecimalFormat("##.#");
private static final Map primitiveSizes = new IdentityHashMap() {
{
put(boolean.class, new Integer(1));
put(byte.class, new Integer(1));
put(char.class, new Integer(2));
put(short.class, new Integer(2));
put(int.class, new Integer(4));
put(float.class, new Integer(4));
put(double.class, new Integer(8));
put(long.class, new Integer(8));
}
};
public static int getFieldSize(Class clazz) {
Integer i = (Integer) primitiveSizes.get(clazz);
return i != null ? i.intValue() : getPointerSize();
}
public static int getPointerSize() {
return 4;
}
/**
* Calculates full size of object iterating over its hierarchy graph.
*
* @param obj object to calculate size of
* @param filter the filter used to ignore fields or objects
* @param maxObjects the max numbers of objects to traverse
* @return object size
* @throws Exception
*/
public static ObjectSize deepSizeOf(Object obj, IObjectSizeFilter filter, int maxObjects) {
Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
Stack<ObjectEntry> stack = new Stack<ObjectEntry>();
InvocationCounter sizeHistogram = new InvocationCounter(false);
long result = internalSizeOf(new ObjectEntry(obj, "", ""), stack, visited, filter, "");
sizeHistogram.recordInvocation(obj.getClass().getName(), (int)result);
int n = 1;
while (!stack.isEmpty()) {
ObjectEntry entry = stack.pop();
long size = internalSizeOf(entry, stack, visited, filter, entry.indent);
result += size;
n++;
sizeHistogram.recordInvocation(entry.object.getClass().getName(), (int)size);
if (n >= maxObjects) {
return new ObjectSize(result, false);
}
}
visited.clear();
if (VERBOSE) {
System.out.println();
System.out.println("-------------------------------------------------");
sizeHistogram.print();
}
return new ObjectSize(result, true);
}
public static ObjectSize deepSizeOf(Object obj) {
try {
return deepSizeOf(obj, DEFAULT_FILTER, Integer.MAX_VALUE);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static boolean skipObject(Object obj, Map<Object, Object> visited,IObjectSizeFilter filter) {
if (obj instanceof String) {
// skip interned string
if (obj == ((String) obj).intern()) {
return true;
}
}
return obj == null || visited.containsKey(obj) || filter.skipObject(obj);
}
private static boolean skipField(Field field, IObjectSizeFilter filter) {
return filter.skipField(field);
}
private static long getArrayShallowSize(Object obj) {
long result = 16;
int length = Array.getLength(obj);
if (length != 0) {
Class<?> arrayElementClazz = obj.getClass().getComponentType();
if (arrayElementClazz.isPrimitive()) {
result += length * getFieldSize(arrayElementClazz);
} else {
result += length * getPointerSize();
}
}
return result;
}
private static long internalSizeOf(ObjectEntry entry, Stack<ObjectEntry> stack, Map<Object, Object> visited, IObjectSizeFilter filter, String indent) {
Object obj = entry.object;
if (skipObject(obj, visited, filter)) {
return 0;
}
visited.put(obj, null);
long result = 0;
Class<?> clazz = obj.getClass();
if (clazz.isArray()) {
result += getArrayShallowSize(obj);
// process all array elements but skip primitive type array
if (clazz.getName().length() != 2) {
int length = Array.getLength(obj);
for (int i = length - 1; i >= 0; i--) {
Object o = Array.get(obj, i);
if (!skipObject(o, visited, filter)) {
stack.add(new ObjectEntry(o, indent + INDENT, "[" + i + "]: "));
}
}
}
} else {
result = 8;
// process all fields of the object
while (clazz != null) {
Field[] fields = clazz.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
if (!Modifier.isStatic(fields[i].getModifiers())) {
Field field = fields[i];
result += getFieldSize(field.getType());
if (!field.getType().isPrimitive() && !skipField(field, filter)) {
field.setAccessible(true);
try {
// objects to be estimated are put to stack
Object objectToAdd = field.get(obj);
if (!skipObject(objectToAdd, visited, filter)) {
stack.add(new ObjectEntry(objectToAdd, indent + INDENT, field.getName() + ": "));
}
} catch (IllegalAccessException ex) {
assert false;
}
}
}
}
clazz = clazz.getSuperclass();
}
}
result = roundUpToNearestEightBytes(result);
if (VERBOSE ) {
String extra = "";
if (obj instanceof HashMap) {
try {
Method m = obj.getClass().getDeclaredMethod("capacity");
m.setAccessible(true);
int capacity = (Integer) m.invoke(obj);
extra = " (" + decimalFormat.format(100.0 * ((HashMap)obj).size()/capacity) + "%) ";
} catch (Exception e) {
throw new RuntimeException(e);
}
}
if (obj instanceof ArrayList) {
try {
Field f = obj.getClass().getDeclaredField("elementData");
f.setAccessible(true);
int capacity = Array.getLength(f.get(obj));
if (capacity == 0) {
extra = " (empty) ";
} else {
extra = " (" + decimalFormat.format(100.0 * ((ArrayList)obj).size()/capacity) + "%) ";
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
System.out.println(indent + entry.info + obj.getClass().getName() + extra + " - " + result);
}
return result;
}
private static long roundUpToNearestEightBytes(long result) {
if ((result % 8) != 0) {
result += 8 - (result % 8);
}
return result;
}
private static class ObjectEntry {
public Object object;
public String indent;
public String info;
public ObjectEntry(Object object, String indent, String info) {
this.object = object;
this.indent = indent;
this.info = info;
}
}
public static void main(String[] args) throws Exception {
String[] s = new String[11*1024*1024];
System.out.println(ObjectSizeUtil.deepSizeOf(s));
}
}