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