package railo.commons.lang; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import railo.commons.io.SystemUtil; import railo.runtime.exp.ExpressionException; import railo.runtime.exp.PageRuntimeException; import railo.runtime.op.Caster; import railo.runtime.type.Sizeable; /** * Calculation of object size. */ public class SizeOf { public static final int OBJECT_GRANULARITY_IN_BYTES = 8; public static final int WORD_SIZE = Architecture.getVMArchitecture().getWordSize(); public static final int HEADER_SIZE = 2 * WORD_SIZE; public static final int DOUBLE_SIZE = 8; public static final int FLOAT_SIZE = 4; public static final int LONG_SIZE = 8; public static final int INT_SIZE = 4; public static final int SHORT_SIZE = 2; public static final int BYTE_SIZE = 1; public static final int BOOLEAN_SIZE = 1; public static final int CHAR_SIZE = 2; public static final int REF_SIZE = WORD_SIZE; private static ThreadLocal _inside=new ThreadLocal(); private static ThreadLocal map=new ThreadLocal(); private static ThreadLocal<Set<Integer>> done=new ThreadLocal<Set<Integer>>(); private static boolean inside(boolean inside) { Boolean was=(Boolean) _inside.get(); _inside.set(inside?Boolean.TRUE:Boolean.FALSE); return was!=null && was.booleanValue(); } private static Map get(boolean clear) { Map m=(Map) map.get(); if(m==null){ m=new IdentityHashMap(); map.set(m); } else if(clear) m.clear(); return m; } /** * Calculates the size of an object. * @param object the object that we want to have the size calculated. * @return the size of the object or 0 if null. */ public static long size2(Object o) { Set<Integer> d = done.get(); boolean inside=true; if(d==null){ inside=false; d=new HashSet<Integer>(); done.set(d); } try{ return size(o,d); } finally{ if(!inside) done.set(null); } } private static long size(Object o,Set<Integer> done) { if(o == null) return 0; if(done.contains(o.hashCode())) return 0; done.add(o.hashCode()); if(o instanceof Sizeable){ return ((Sizeable)o).sizeOf(); } Class clazz = o.getClass(); long size=0; // Native ARRAY // TODO how big is the array itself if (clazz.isArray()) { Class ct = clazz.getComponentType(); if(ct.isPrimitive())return primSize(ct)*Array.getLength(o); size=REF_SIZE*Array.getLength(o); for (int i=Array.getLength(o)-1; i>=0;i--) { size += size(Array.get(o, i),done); } return size; } if(o instanceof Boolean) return REF_SIZE+BOOLEAN_SIZE; if(o instanceof Character) return REF_SIZE+CHAR_SIZE; if(o instanceof Number){ if(o instanceof Double || o instanceof Long) return REF_SIZE+LONG_SIZE; if(o instanceof Byte) return REF_SIZE+BYTE_SIZE; if(o instanceof Short) return REF_SIZE+SHORT_SIZE; return REF_SIZE+INT_SIZE;// float,int } if(o instanceof String){ int len=((String)o).length(); return (REF_SIZE*len)+(REF_SIZE*CHAR_SIZE); } throw new PageRuntimeException(new ExpressionException("can not terminate the size of a object of type ["+Caster.toTypeName(o)+":"+o.getClass().getName()+"]")); } private static int primSize(Class ct) { if(ct==double.class) return LONG_SIZE; if(ct==long.class) return LONG_SIZE; if(ct==float.class) return INT_SIZE; if(ct==short.class) return SHORT_SIZE; if(ct==int.class) return INT_SIZE; if(ct==byte.class) return BYTE_SIZE; if(ct==boolean.class) return BOOLEAN_SIZE; return CHAR_SIZE; } public static long size(Object object) { return size(object, Integer.MAX_VALUE); } public static long size(Object object,int maxDepth) { if (object==null)return 0; boolean wasInside=inside(true); Map instances=get(!wasInside); //IdentityHashMap instances = new IdentityHashMap(); Map dictionary = new HashMap(); long size = _size(object, instances, dictionary, maxDepth, Long.MAX_VALUE); inside(wasInside); return size; } private static long _size(Object object, Map instances,Map dictionary, int maxDepth,long maxSize) { try { return __size(object, instances, dictionary, maxDepth, maxSize); } catch(Throwable t){ t.printStackTrace(); return 0; } } private static long __size(Object object, Map instances,Map dictionary, int maxDepth,long maxSize) { if (object==null || instances.containsKey(object) || maxDepth==0 || maxSize < 0) return 0; instances.put(object, object); if(object instanceof Sizeable)return ((Sizeable)object).sizeOf(); if(object instanceof String){ return (SizeOf.CHAR_SIZE*((String)object).length())+SizeOf.REF_SIZE; } if(object instanceof Number){ if(object instanceof Double) return SizeOf.DOUBLE_SIZE+SizeOf.REF_SIZE; if(object instanceof Float) return SizeOf.FLOAT_SIZE+SizeOf.REF_SIZE; if(object instanceof Long) return SizeOf.LONG_SIZE+SizeOf.REF_SIZE; if(object instanceof Integer) return SizeOf.INT_SIZE+SizeOf.REF_SIZE; if(object instanceof Short) return SizeOf.SHORT_SIZE+SizeOf.REF_SIZE; if(object instanceof Byte) return SizeOf.BYTE_SIZE+SizeOf.REF_SIZE; } if(object instanceof Object[]) { Object[] arr=(Object[]) object; long size=SizeOf.REF_SIZE; for(int i=0;i<arr.length;i++){ size+=_size(arr[i], instances, dictionary, maxDepth - 1, maxSize); } return size; } if(object instanceof Map) { long size=SizeOf.REF_SIZE; Map.Entry entry; Map map=(Map) object; Iterator it = map.entrySet().iterator(); while(it.hasNext()){ entry=(Entry) it.next(); size+=SizeOf.REF_SIZE; size+=_size(entry.getKey(), instances, dictionary, maxDepth - 1, maxSize); size+=_size(entry.getValue(), instances, dictionary, maxDepth - 1, maxSize); } return size; } if(object instanceof List) { long size=SizeOf.REF_SIZE; List list=(List) object; Iterator it = list.iterator(); while(it.hasNext()){ size+=_size(it.next(), instances, dictionary, maxDepth - 1, maxSize); } return size; } Class clazz = object.getClass(); Meta cmd = Meta.getMetaData(clazz, dictionary); long shallowSize = cmd.calcInstanceSize(object); long size = shallowSize; if (clazz.isArray() && !cmd.getComponentClass().isPrimitive()) { for (int i=Array.getLength(object)-1; i>=0;i--) { size += _size(Array.get(object, i), instances, dictionary, maxDepth - 1, maxSize - size); } } else { List values = cmd.getReferenceFieldValues(object); Iterator it = values.iterator(); while(it.hasNext()) { size += _size(it.next(), instances, dictionary, maxDepth - 1, maxSize - size); } } return size; } public static long size(long value) { return SizeOf.LONG_SIZE; } public static long size(boolean value) { return SizeOf.BOOLEAN_SIZE; } } class Meta { /** Class for which this metadata applies */ private Class aClass; /** Parent class's metadata */ private Meta superMetaData; private boolean isArray; /** Only filled if this is an array */ private int componentSize; /** Only filled if this class is an array */ private Class componentClass; /** Number of bytes reserved for the fields of this class and its parents, * aligned to a word boundary. */ private int fieldSize; /** this.fieldSize + super.totalFieldSize */ private int totalFieldSize; /** Number of bytes reserved for instances of this class, aligned to a * 8 bytes boundary */ private int instanceSize; private List referenceFields = new ArrayList(); public static Meta getMetaData(Class aClass) { return getMetaData(aClass, new HashMap()); } public static Meta getMetaData(Class aClass, Map dictionary) { if (aClass == null) { throw new IllegalArgumentException("aClass argument cannot be null"); } if (aClass.isPrimitive()) { throw new IllegalArgumentException("ClassMetaData not supported for primitive types: " + aClass.getName()); } if (dictionary.containsKey(aClass)) { return (Meta) dictionary.get(aClass); } Meta result = new Meta(aClass, dictionary); dictionary.put(aClass, result); return result; } private Meta(Class aClass, Map dictionary) { Class parentClass = aClass.getSuperclass(); if (parentClass != null) { superMetaData = getMetaData(parentClass, dictionary); } this.aClass = aClass; if (aClass.isArray()) { processArrayClass(aClass); } else { processRegularClass(aClass); processFields(aClass); } } private static int getComponentSize(Class componentClass) { if (componentClass.equals(Double.TYPE) || componentClass.equals(Long.TYPE)) { return SizeOf.LONG_SIZE; } if (componentClass.equals(Integer.TYPE) || componentClass.equals(Float.TYPE)) { return SizeOf.INT_SIZE; } if (componentClass.equals(Character.TYPE) || componentClass.equals(Short.TYPE)) { return SizeOf.SHORT_SIZE; } if (componentClass.equals(Byte.TYPE) || componentClass.equals(Boolean.TYPE)) { return SizeOf.BYTE_SIZE; } return SizeOf.REF_SIZE; } public int getFieldSize() { return fieldSize; } private void processArrayClass(Class aClass) { isArray = true; componentClass = aClass.getComponentType(); componentSize = getComponentSize(componentClass); instanceSize = SizeOf.HEADER_SIZE + SizeOf.WORD_SIZE; } private void processFields(Class aClass) { Field[] fields = aClass.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; int fieldModifier = field.getModifiers(); if (Modifier.isStatic(fieldModifier)) { continue; } Class fieldType = field.getType(); if (fieldType.isPrimitive()) { continue; } field.setAccessible(true); referenceFields.add(field); } } private void processRegularClass(Class aClass) { Field[] fields = aClass.getDeclaredFields(); int longCount = 0; int intCount = 0; int shortCount = 0; int byteCount = 0; int refCount = 0; // Calculate how many fields of each type we have. for (int i = 0; i < fields.length; i++) { Field field = fields[i]; int fieldModifiers = field.getModifiers(); if (Modifier.isStatic(fieldModifiers)) { continue; } Class fieldClass = field.getType(); if (fieldClass.equals(Double.TYPE) || fieldClass.equals(Long.TYPE)) { longCount++; } else if (fieldClass.equals(Integer.TYPE) || fieldClass.equals(Float.TYPE)) { intCount++; } else if (fieldClass.equals(Character.TYPE) || fieldClass.equals(Short.TYPE)) { shortCount++; }else if (fieldClass.equals(Byte.TYPE) || fieldClass.equals(Boolean.TYPE)) { byteCount++; } else { refCount++; } } int localIntCount = intCount; int localShortCount = shortCount; int localByteCount = byteCount; int localRefCount = refCount; int parentTotalFieldSize = superMetaData == null ? 0 : superMetaData.getFieldSize(); int alignedParentSize = align(parentTotalFieldSize, SizeOf.OBJECT_GRANULARITY_IN_BYTES); int paddingSpace = alignedParentSize - parentTotalFieldSize; // we try to pad with ints, and then shorts, and then bytes, and then refs. while (localIntCount > 0 && paddingSpace >= SizeOf.INT_SIZE) { paddingSpace -= SizeOf.INT_SIZE; localIntCount--; } while(localShortCount > 0 && paddingSpace >= SizeOf.SHORT_SIZE) { paddingSpace -= SizeOf.SHORT_SIZE; localShortCount--; } while (localByteCount > 0 && paddingSpace >= SizeOf.BYTE_SIZE) { paddingSpace -= SizeOf.BYTE_SIZE; localByteCount--; } while (localRefCount > 0 && paddingSpace >= SizeOf.REF_SIZE) { paddingSpace -= SizeOf.REF_SIZE; localRefCount--; } int preFieldSize = paddingSpace + longCount * SizeOf.LONG_SIZE + intCount * SizeOf.INT_SIZE + shortCount * SizeOf.SHORT_SIZE + byteCount * SizeOf.BYTE_SIZE + refCount * SizeOf.REF_SIZE; fieldSize = align(preFieldSize, SizeOf.REF_SIZE); totalFieldSize = parentTotalFieldSize + fieldSize; instanceSize = align(SizeOf.HEADER_SIZE + totalFieldSize, SizeOf.OBJECT_GRANULARITY_IN_BYTES); } public int calcArraySize(int length) { return align(instanceSize + componentSize * length, SizeOf.OBJECT_GRANULARITY_IN_BYTES); } public int calcInstanceSize(Object instance) { if (instance == null) { throw new IllegalArgumentException("Parameter cannot be null"); } if (!instance.getClass().equals(aClass)) { throw new IllegalArgumentException("Parameter not of proper class. Was: " + instance.getClass() + " expected: " + aClass); } if (isArray) { int length = Array.getLength(instance); return calcArraySize(length); } return instanceSize; } public List getReferenceFieldValues(Object instance) { if (instance == null) { throw new IllegalArgumentException("Parameter cannot be null."); } List results; if (superMetaData == null) { results = new ArrayList(); } else { results = superMetaData.getReferenceFieldValues(instance); } Iterator it = referenceFields.iterator(); Field field; while(it.hasNext()) { field=(Field) it.next(); Object value; try { value = field.get(instance); } catch (IllegalAccessException ex) { // Should never happen in practice. throw new IllegalStateException("Unexpected exeption: "+ ex.getMessage()); } if (value != null) { results.add(value); } } return results; } public Meta getSuperMetaData() { return superMetaData; } public int getInstanceSize() { return instanceSize; } public int getTotalFieldSize() { return totalFieldSize; } public Class getTheClass() { return aClass; } public Class getComponentClass() { return componentClass; } public static int align(int size, int granularity) { return size + (granularity - size % granularity) % granularity; } } class Architecture { private static final Architecture ARCH_32_BITS=new Architecture(32, 4); private static final Architecture ARCH_64_BITS=new Architecture(64, 8); private static final Architecture ARCH_UNKNOWN=new Architecture(32, 4); private int bits; private int wordSize; private Architecture(int bits, int wordSize) { this.bits = bits; this.wordSize = wordSize; } public int getBits() { return bits; } public int getWordSize() { return wordSize; } public static Architecture getVMArchitecture() { if (SystemUtil.getJREArch()==SystemUtil.ARCH_32) return ARCH_32_BITS; else if (SystemUtil.getJREArch()==SystemUtil.ARCH_64) return ARCH_64_BITS; return ARCH_UNKNOWN; } }