001 package railo.commons.lang; 002 003 import java.lang.reflect.Array; 004 import java.lang.reflect.Field; 005 import java.lang.reflect.Modifier; 006 import java.util.ArrayList; 007 import java.util.HashMap; 008 import java.util.HashSet; 009 import java.util.IdentityHashMap; 010 import java.util.Iterator; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.Map.Entry; 014 import java.util.Set; 015 016 import railo.commons.io.SystemUtil; 017 import railo.runtime.exp.ExpressionException; 018 import railo.runtime.exp.PageRuntimeException; 019 import railo.runtime.op.Caster; 020 import railo.runtime.type.Sizeable; 021 022 /** 023 * Calculation of object size. 024 */ 025 public class SizeOf { 026 public static final int OBJECT_GRANULARITY_IN_BYTES = 8; 027 public static final int WORD_SIZE = Architecture.getVMArchitecture().getWordSize(); 028 public static final int HEADER_SIZE = 2 * WORD_SIZE; 029 030 public static final int DOUBLE_SIZE = 8; 031 public static final int FLOAT_SIZE = 4; 032 public static final int LONG_SIZE = 8; 033 public static final int INT_SIZE = 4; 034 public static final int SHORT_SIZE = 2; 035 public static final int BYTE_SIZE = 1; 036 public static final int BOOLEAN_SIZE = 1; 037 public static final int CHAR_SIZE = 2; 038 public static final int REF_SIZE = WORD_SIZE; 039 040 private static ThreadLocal _inside=new ThreadLocal(); 041 private static ThreadLocal map=new ThreadLocal(); 042 043 044 private static ThreadLocal<Set<Integer>> done=new ThreadLocal<Set<Integer>>(); 045 046 047 private static boolean inside(boolean inside) { 048 Boolean was=(Boolean) _inside.get(); 049 _inside.set(inside?Boolean.TRUE:Boolean.FALSE); 050 return was!=null && was.booleanValue(); 051 } 052 053 private static Map get(boolean clear) { 054 Map m=(Map) map.get(); 055 if(m==null){ 056 m=new IdentityHashMap(); 057 map.set(m); 058 } 059 else if(clear) m.clear(); 060 061 return m; 062 } 063 064 065 /** 066 * Calculates the size of an object. 067 * @param object the object that we want to have the size calculated. 068 * @return the size of the object or 0 if null. 069 */ 070 071 public static long size2(Object o) { 072 Set<Integer> d = done.get(); 073 boolean inside=true; 074 if(d==null){ 075 inside=false; 076 d=new HashSet<Integer>(); 077 done.set(d); 078 } 079 try{ 080 return size(o,d); 081 } 082 finally{ 083 if(!inside) done.set(null); 084 } 085 } 086 087 private static long size(Object o,Set<Integer> done) { 088 if(o == null) return 0; 089 if(done.contains(o.hashCode())) return 0; 090 done.add(o.hashCode()); 091 092 if(o instanceof Sizeable){ 093 return ((Sizeable)o).sizeOf(); 094 } 095 Class clazz = o.getClass(); 096 long size=0; 097 098 // Native ARRAY 099 // TODO how big is the array itself 100 if (clazz.isArray()) { 101 Class ct = clazz.getComponentType(); 102 103 if(ct.isPrimitive())return primSize(ct)*Array.getLength(o); 104 105 size=REF_SIZE*Array.getLength(o); 106 for (int i=Array.getLength(o)-1; i>=0;i--) { 107 size += size(Array.get(o, i),done); 108 } 109 return size; 110 } 111 112 if(o instanceof Boolean) return REF_SIZE+BOOLEAN_SIZE; 113 if(o instanceof Character) return REF_SIZE+CHAR_SIZE; 114 115 if(o instanceof Number){ 116 if(o instanceof Double || o instanceof Long) return REF_SIZE+LONG_SIZE; 117 if(o instanceof Byte) return REF_SIZE+BYTE_SIZE; 118 if(o instanceof Short) return REF_SIZE+SHORT_SIZE; 119 return REF_SIZE+INT_SIZE;// float,int 120 } 121 if(o instanceof String){ 122 int len=((String)o).length(); 123 return (REF_SIZE*len)+(REF_SIZE*CHAR_SIZE); 124 } 125 126 127 throw new PageRuntimeException(new ExpressionException("can not terminate the size of a object of type ["+Caster.toTypeName(o)+":"+o.getClass().getName()+"]")); 128 } 129 130 131 private static int primSize(Class ct) { 132 if(ct==double.class) return LONG_SIZE; 133 if(ct==long.class) return LONG_SIZE; 134 if(ct==float.class) return INT_SIZE; 135 if(ct==short.class) return SHORT_SIZE; 136 if(ct==int.class) return INT_SIZE; 137 if(ct==byte.class) return BYTE_SIZE; 138 if(ct==boolean.class) return BOOLEAN_SIZE; 139 return CHAR_SIZE; 140 } 141 142 public static long size(Object object) { 143 return size(object, Integer.MAX_VALUE); 144 } 145 146 public static long size(Object object,int maxDepth) { 147 if (object==null)return 0; 148 boolean wasInside=inside(true); 149 Map instances=get(!wasInside); 150 //IdentityHashMap instances = new IdentityHashMap(); 151 Map dictionary = new HashMap(); 152 long size = _size(object, instances, dictionary, maxDepth, Long.MAX_VALUE); 153 inside(wasInside); 154 return size; 155 } 156 157 158 private static long _size(Object object, Map instances,Map dictionary, int maxDepth,long maxSize) { 159 try { 160 return __size(object, instances, dictionary, maxDepth, maxSize); 161 } 162 catch(Throwable t){ 163 t.printStackTrace(); 164 return 0; 165 } 166 } 167 168 private static long __size(Object object, Map instances,Map dictionary, int maxDepth,long maxSize) { 169 if (object==null || instances.containsKey(object) || maxDepth==0 || maxSize < 0) return 0; 170 171 instances.put(object, object); 172 173 if(object instanceof Sizeable)return ((Sizeable)object).sizeOf(); 174 175 if(object instanceof String){ 176 return (SizeOf.CHAR_SIZE*((String)object).length())+SizeOf.REF_SIZE; 177 } 178 179 if(object instanceof Number){ 180 if(object instanceof Double) return SizeOf.DOUBLE_SIZE+SizeOf.REF_SIZE; 181 if(object instanceof Float) return SizeOf.FLOAT_SIZE+SizeOf.REF_SIZE; 182 if(object instanceof Long) return SizeOf.LONG_SIZE+SizeOf.REF_SIZE; 183 if(object instanceof Integer) return SizeOf.INT_SIZE+SizeOf.REF_SIZE; 184 if(object instanceof Short) return SizeOf.SHORT_SIZE+SizeOf.REF_SIZE; 185 if(object instanceof Byte) return SizeOf.BYTE_SIZE+SizeOf.REF_SIZE; 186 } 187 188 189 if(object instanceof Object[]) { 190 Object[] arr=(Object[]) object; 191 long size=SizeOf.REF_SIZE; 192 for(int i=0;i<arr.length;i++){ 193 size+=_size(arr[i], instances, dictionary, maxDepth - 1, maxSize); 194 } 195 return size; 196 } 197 198 if(object instanceof Map) { 199 long size=SizeOf.REF_SIZE; 200 Map.Entry entry; 201 Map map=(Map) object; 202 Iterator it = map.entrySet().iterator(); 203 while(it.hasNext()){ 204 entry=(Entry) it.next(); 205 size+=SizeOf.REF_SIZE; 206 size+=_size(entry.getKey(), instances, dictionary, maxDepth - 1, maxSize); 207 size+=_size(entry.getValue(), instances, dictionary, maxDepth - 1, maxSize); 208 } 209 return size; 210 } 211 if(object instanceof List) { 212 long size=SizeOf.REF_SIZE; 213 List list=(List) object; 214 Iterator it = list.iterator(); 215 while(it.hasNext()){ 216 size+=_size(it.next(), instances, dictionary, maxDepth - 1, maxSize); 217 } 218 return size; 219 } 220 221 222 Class clazz = object.getClass(); 223 224 Meta cmd = Meta.getMetaData(clazz, dictionary); 225 long shallowSize = cmd.calcInstanceSize(object); 226 long size = shallowSize; 227 if (clazz.isArray() && !cmd.getComponentClass().isPrimitive()) { 228 for (int i=Array.getLength(object)-1; i>=0;i--) { 229 size += _size(Array.get(object, i), instances, dictionary, maxDepth - 1, maxSize - size); 230 } 231 } 232 else { 233 List values = cmd.getReferenceFieldValues(object); 234 Iterator it = values.iterator(); 235 while(it.hasNext()) { 236 size += _size(it.next(), instances, dictionary, maxDepth - 1, maxSize - size); 237 } 238 } 239 return size; 240 } 241 242 public static long size(long value) { 243 return SizeOf.LONG_SIZE; 244 } 245 246 247 public static long size(boolean value) { 248 return SizeOf.BOOLEAN_SIZE; 249 } 250 } 251 252 253 class Meta { 254 255 256 /** Class for which this metadata applies */ 257 private Class aClass; 258 259 /** Parent class's metadata */ 260 private Meta superMetaData; 261 262 private boolean isArray; 263 264 /** Only filled if this is an array */ 265 private int componentSize; 266 267 /** Only filled if this class is an array */ 268 private Class componentClass; 269 270 /** Number of bytes reserved for the fields of this class and its parents, 271 * aligned to a word boundary. */ 272 private int fieldSize; 273 274 /** this.fieldSize + super.totalFieldSize */ 275 private int totalFieldSize; 276 277 /** Number of bytes reserved for instances of this class, aligned to a 278 * 8 bytes boundary */ 279 private int instanceSize; 280 281 private List referenceFields = new ArrayList(); 282 283 public static Meta getMetaData(Class aClass) { 284 return getMetaData(aClass, new HashMap()); 285 } 286 287 public static Meta getMetaData(Class aClass, Map dictionary) { 288 if (aClass == null) { 289 throw new IllegalArgumentException("aClass argument cannot be null"); 290 } 291 292 if (aClass.isPrimitive()) { 293 throw new IllegalArgumentException("ClassMetaData not supported for primitive types: " + aClass.getName()); 294 } 295 296 if (dictionary.containsKey(aClass)) { 297 return (Meta) dictionary.get(aClass); 298 } 299 Meta result = new Meta(aClass, dictionary); 300 dictionary.put(aClass, result); 301 return result; 302 } 303 304 private Meta(Class aClass, Map dictionary) { 305 306 Class parentClass = aClass.getSuperclass(); 307 if (parentClass != null) { 308 superMetaData = getMetaData(parentClass, dictionary); 309 } 310 311 this.aClass = aClass; 312 313 if (aClass.isArray()) { 314 processArrayClass(aClass); 315 } else { 316 processRegularClass(aClass); 317 processFields(aClass); 318 } 319 } 320 321 private static int getComponentSize(Class componentClass) { 322 if (componentClass.equals(Double.TYPE) || 323 componentClass.equals(Long.TYPE)) { 324 return SizeOf.LONG_SIZE; 325 } 326 327 if (componentClass.equals(Integer.TYPE) || 328 componentClass.equals(Float.TYPE)) { 329 return SizeOf.INT_SIZE; 330 } 331 332 if (componentClass.equals(Character.TYPE) || 333 componentClass.equals(Short.TYPE)) { 334 return SizeOf.SHORT_SIZE; 335 } 336 337 if (componentClass.equals(Byte.TYPE) || 338 componentClass.equals(Boolean.TYPE)) { 339 return SizeOf.BYTE_SIZE; 340 } 341 342 return SizeOf.REF_SIZE; 343 } 344 345 public int getFieldSize() { 346 return fieldSize; 347 } 348 349 private void processArrayClass(Class aClass) { 350 isArray = true; 351 componentClass = aClass.getComponentType(); 352 componentSize = getComponentSize(componentClass); 353 instanceSize = SizeOf.HEADER_SIZE + SizeOf.WORD_SIZE; 354 } 355 356 private void processFields(Class aClass) { 357 Field[] fields = aClass.getDeclaredFields(); 358 for (int i = 0; i < fields.length; i++) { 359 Field field = fields[i]; 360 int fieldModifier = field.getModifiers(); 361 if (Modifier.isStatic(fieldModifier)) { 362 continue; 363 } 364 Class fieldType = field.getType(); 365 if (fieldType.isPrimitive()) { 366 continue; 367 } 368 field.setAccessible(true); 369 referenceFields.add(field); 370 } 371 } 372 373 private void processRegularClass(Class aClass) { 374 Field[] fields = aClass.getDeclaredFields(); 375 376 int longCount = 0; 377 int intCount = 0; 378 int shortCount = 0; 379 int byteCount = 0; 380 int refCount = 0; 381 382 // Calculate how many fields of each type we have. 383 for (int i = 0; i < fields.length; i++) { 384 Field field = fields[i]; 385 int fieldModifiers = field.getModifiers(); 386 if (Modifier.isStatic(fieldModifiers)) { 387 continue; 388 } 389 390 Class fieldClass = field.getType(); 391 392 if (fieldClass.equals(Double.TYPE) || 393 fieldClass.equals(Long.TYPE)) { 394 longCount++; 395 } else if (fieldClass.equals(Integer.TYPE) || 396 fieldClass.equals(Float.TYPE)) { 397 intCount++; 398 } else if (fieldClass.equals(Character.TYPE) || 399 fieldClass.equals(Short.TYPE)) { 400 shortCount++; 401 }else if (fieldClass.equals(Byte.TYPE) || 402 fieldClass.equals(Boolean.TYPE)) { 403 byteCount++; 404 } else { 405 refCount++; 406 } 407 } 408 409 int localIntCount = intCount; 410 int localShortCount = shortCount; 411 int localByteCount = byteCount; 412 int localRefCount = refCount; 413 414 int parentTotalFieldSize = superMetaData == null ? 0 : superMetaData.getFieldSize(); 415 416 int alignedParentSize = align(parentTotalFieldSize, SizeOf.OBJECT_GRANULARITY_IN_BYTES); 417 int paddingSpace = alignedParentSize - parentTotalFieldSize; 418 // we try to pad with ints, and then shorts, and then bytes, and then refs. 419 while (localIntCount > 0 && paddingSpace >= SizeOf.INT_SIZE) { 420 paddingSpace -= SizeOf.INT_SIZE; 421 localIntCount--; 422 } 423 424 while(localShortCount > 0 && paddingSpace >= SizeOf.SHORT_SIZE) { 425 paddingSpace -= SizeOf.SHORT_SIZE; 426 localShortCount--; 427 } 428 429 while (localByteCount > 0 && paddingSpace >= SizeOf.BYTE_SIZE) { 430 paddingSpace -= SizeOf.BYTE_SIZE; 431 localByteCount--; 432 } 433 434 while (localRefCount > 0 && paddingSpace >= SizeOf.REF_SIZE) { 435 paddingSpace -= SizeOf.REF_SIZE; 436 localRefCount--; 437 } 438 439 int preFieldSize = paddingSpace + 440 longCount * SizeOf.LONG_SIZE + 441 intCount * SizeOf.INT_SIZE + 442 shortCount * SizeOf.SHORT_SIZE + 443 byteCount * SizeOf.BYTE_SIZE + 444 refCount * SizeOf.REF_SIZE; 445 446 fieldSize = align(preFieldSize, SizeOf.REF_SIZE); 447 448 totalFieldSize = parentTotalFieldSize + fieldSize; 449 450 instanceSize = align(SizeOf.HEADER_SIZE + totalFieldSize, SizeOf.OBJECT_GRANULARITY_IN_BYTES); 451 } 452 453 public int calcArraySize(int length) { 454 return align(instanceSize + componentSize * length, SizeOf.OBJECT_GRANULARITY_IN_BYTES); 455 } 456 457 public int calcInstanceSize(Object instance) { 458 if (instance == null) { 459 throw new IllegalArgumentException("Parameter cannot be null"); 460 } 461 if (!instance.getClass().equals(aClass)) { 462 throw new IllegalArgumentException("Parameter not of proper class. Was: " + instance.getClass() + " expected: " + aClass); 463 } 464 465 if (isArray) { 466 int length = Array.getLength(instance); 467 return calcArraySize(length); 468 } 469 return instanceSize; 470 } 471 472 public List getReferenceFieldValues(Object instance) { 473 if (instance == null) { 474 throw new IllegalArgumentException("Parameter cannot be null."); 475 } 476 477 List results; 478 if (superMetaData == null) { 479 results = new ArrayList(); 480 } else { 481 results = superMetaData.getReferenceFieldValues(instance); 482 } 483 Iterator it = referenceFields.iterator(); 484 Field field; 485 while(it.hasNext()) { 486 field=(Field) it.next(); 487 Object value; 488 try { 489 value = field.get(instance); 490 } catch (IllegalAccessException ex) { 491 // Should never happen in practice. 492 throw new IllegalStateException("Unexpected exeption: "+ ex.getMessage()); 493 } 494 if (value != null) { 495 results.add(value); 496 } 497 } 498 return results; 499 } 500 501 public Meta getSuperMetaData() { 502 return superMetaData; 503 } 504 505 public int getInstanceSize() { 506 return instanceSize; 507 } 508 509 public int getTotalFieldSize() { 510 return totalFieldSize; 511 } 512 513 public Class getTheClass() { 514 return aClass; 515 } 516 517 public Class getComponentClass() { 518 return componentClass; 519 } 520 521 public static int align(int size, int granularity) { 522 return size + (granularity - size % granularity) % granularity; 523 } 524 } 525 526 527 class Architecture { 528 529 private static final Architecture ARCH_32_BITS=new Architecture(32, 4); 530 private static final Architecture ARCH_64_BITS=new Architecture(64, 8); 531 private static final Architecture ARCH_UNKNOWN=new Architecture(32, 4); 532 533 private int bits; 534 private int wordSize; 535 536 private Architecture(int bits, int wordSize) { 537 this.bits = bits; 538 this.wordSize = wordSize; 539 } 540 541 public int getBits() { 542 return bits; 543 } 544 545 public int getWordSize() { 546 return wordSize; 547 } 548 549 public static Architecture getVMArchitecture() { 550 if (SystemUtil.getJREArch()==SystemUtil.ARCH_32) 551 return ARCH_32_BITS; 552 else if (SystemUtil.getJREArch()==SystemUtil.ARCH_64) 553 return ARCH_64_BITS; 554 555 return ARCH_UNKNOWN; 556 } 557 }