package rocks.inspectit.server.cache; import java.sql.Timestamp; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import rocks.inspectit.shared.all.cmr.cache.IObjectSizes; import rocks.inspectit.shared.all.communication.Sizeable; import rocks.inspectit.shared.all.util.UnderlyingSystemInfo; /** * This is an abstract class that holds general calculations and object sizes that are equal in both * 32-bit and 64-bit VM. Architecture specific calculations need to be done in implementing classes. * * @author Ivan Senic * */ public abstract class AbstractObjectSizes implements IObjectSizes { /** * General sizes of primitive types. * <p> * Boolean info: Although the Java Virtual Machine defines a boolean type, it only provides very * limited support for it. There are no Java Virtual Machine instructions solely dedicated to * operations on boolean values. Instead, expressions in the Java programming language that * operate on boolean values are compiled to use values of the Java Virtual Machine int data * type. */ public static final long BOOLEAN_SIZE = 1, CHAR_SIZE = 2, SHORT_SIZE = 2, INT_SIZE = 4, FLOAT_SIZE = 4, LONG_SIZE = 8, DOUBLE_SIZE = 8; /** * Default capacity of array list. */ private static final int ARRAY_LIST_INITIAL_CAPACITY = 10; /** * Default capacity of {@link HashMap} and {@link ConcurrentHashMap}. */ private static final int MAP_INITIAL_CAPACITY = 16; /** * If we need to align between classes to 8 bytes. Only needed when compressed oops are not on * on 64bit. */ private static final boolean ALLIGN_CLASS_CALCULATION = UnderlyingSystemInfo.IS_64BIT && !UnderlyingSystemInfo.IS_COMPRESSED_OOPS; /** * The percentage of size expansion for each object. For security reasons. Default is 20%. */ private float objectSecurityExpansionRate = 0.2f; /** * Returns the size of reference in bytes. * * @return Size of reference. */ @Override public abstract long getReferenceSize(); /** * {@inheritDoc} */ @Override public long getSizeOf(Sizeable sizeable) { if (null == sizeable) { return 0; } long size = sizeable.getObjectSize(this, ALLIGN_CLASS_CALCULATION); return ALLIGN_CLASS_CALCULATION ? size : alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOf(String str) { if (null == str) { return 0; } long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(1, 0, 2, 0, 0, 0); size += this.getSizeOfPrimitiveArray(str.length(), CHAR_SIZE); return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOf(String... strings) { Set<Integer> identityHashCodeSet = new HashSet<>(); long size = 0L; for (String str : strings) { if (null == str) { continue; } if (identityHashCodeSet.add(System.identityHashCode(str))) { size += getSizeOf(str); } } return size; } /** * {@inheritDoc} */ @Override public long getSizeOf(Timestamp timestamp) { if (null == timestamp) { return 0; } // 72 is the number of bytes for instance of GregorianCalendar // inside Timestamp. However, I can not check if this is null or not. // In our objects I never found it to be instantiated, so I don't include it. long size = this.getSizeOfObjectHeader(); // java.sql.Timestamp size += this.getPrimitiveTypesSize(0, 0, 1, 0, 0, 0); // java.util.Date size += this.getPrimitiveTypesSize(1, 0, 0, 0, 1, 0); return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOf(List<?> arrayList) { return this.getSizeOf(arrayList, ARRAY_LIST_INITIAL_CAPACITY); } /** * {@inheritDoc} */ @Override public long getSizeOf(List<?> arrayList, int initialCapacity) { if (null == arrayList) { return 0; } int capacity = getArrayCapacity(arrayList.size(), initialCapacity); long size = alignTo8Bytes(this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(1, 0, 2, 0, 0, 0)); size += this.getSizeOfArray(capacity); return alignTo8Bytes(size); } /** * Returns the capacity of the array list from its size. Note that this calculation will be * correct only if the array list in initialized with default capacity of * {@value #ARRAY_LIST_INITIAL_CAPACITY}. * * @param size * Array List size. * @param initialCapacity * Initial capacity of Array list. * @return Capacity of the array that holds elements. */ protected int getArrayCapacity(int size, int initialCapacity) { // from JDK1.7.0_40 the empty list has 0 initial capacity // capacity goes to initial when first element is added if (0 == size) { return 0; } while (initialCapacity < size) { if ((initialCapacity == 0) || (initialCapacity == 1)) { initialCapacity = initialCapacity + 1; } else { initialCapacity = initialCapacity + (initialCapacity >> 1); } } return initialCapacity; } /** * {@inheritDoc} */ @Override public long getSizeOfCustomWeakReference() { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(4, 0, 0, 0, 1, 0); return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfHashSet(int hashSetSize) { return this.getSizeOfHashSet(hashSetSize, MAP_INITIAL_CAPACITY); } /** * {@inheritDoc} */ @Override public long getSizeOfHashSet(int hashSetSize, int initialCapacity) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); size += this.getSizeOfHashMap(hashSetSize, initialCapacity); // One object is used as the value in the map for all entries. This object is shared between // all HashSet instances, but we have to calculate it for each instance. if (hashSetSize > 0) { size += this.getSizeOfObjectObject(); } return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfHashMap(int hashMapSize) { return this.getSizeOfHashMap(hashMapSize, MAP_INITIAL_CAPACITY); } /** * {@inheritDoc} */ @Override public long getSizeOfHashMap(int hashMapSize, int initialCapacity) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(4, 0, 4, 1, 0, 0); int mapCapacity = this.getHashMapCapacityFromSize(hashMapSize, initialCapacity); // size of the map array for the entries size += this.getSizeOfArray(mapCapacity); // size of the entries size += hashMapSize * this.getSizeOfHashMapEntry(); // To each hash map I add 16 bytes because keySet, entrySet and values fields, that can each // hold 16 bytes // These fields are null until these sets are requested by user. // Thus I add for one size += getSizeOfHashMapKeyEntrySet(); return alignTo8Bytes(size); } /** * Returns size of HashMap's inner Key or Entry set classes. * * @return Returns size of HashMap's inner Key or Entry set classes. */ @Override public long getSizeOfHashMapKeyEntrySet() { // since these are inner classes, one reference to enclosing class is needed long size = this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfConcurrentHashMap(int mapSize, int concurrencyLevel) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(6, 0, 3, 0, 0, 0); // array of segments based on capacity size += this.getSizeOfArray(concurrencyLevel); // approximate capacity of each segment int segmentCapacity = getSegmentCapacityFromSize(mapSize / concurrencyLevel, MAP_INITIAL_CAPACITY / concurrencyLevel); // get number of segments int segments = getNumberOfConcurrentSegments(mapSize, concurrencyLevel); // size of each segment based on the capacity, times number of segments size += segments * this.getSizeOfConcurrentSeqment(segmentCapacity); // and for each object in the map there is the reference to the HashEntry in Segment that we // need to add // size += mapSize * alignTo8Bytes(this.getReferenceSize()); size += mapSize * this.getSizeOfHashMapEntry(); return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfNonBlockingHashMapLong(int mapSize) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(5, 1, 0, 0, 1, 0); size = alignTo8Bytes(size); // we have in addition Counter, no-key Object and CHM table in the NonBlockingHashMapLong // need to add them to the size count size += this.getSizeOfObjectObject(); size += this.getSizeOfHighScaleLibCounter(); size += this.getSizeOfHighScaleLibCHM(mapSize); return size; } /** * Returns size of the CHM object used in the high scale lib NonBlockingHashMapLong. * * @param mapSize * Size of map. * @return Size in bytes. */ private long getSizeOfHighScaleLibCHM(int mapSize) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(6, 0, 0, 0, 3, 0); size = alignTo8Bytes(size); // two counters in addition size += getSizeOfHighScaleLibCounter() << 1; // min 16, or next power of two int tablesSize = (mapSize <= MAP_INITIAL_CAPACITY) ? MAP_INITIAL_CAPACITY : 1 << (32 - Integer.numberOfLeadingZeros(mapSize - 1)); // long table[] size += this.getSizeOfPrimitiveArray(tablesSize, LONG_SIZE); // object table[] size += this.getSizeOfPrimitiveArray(tablesSize, getReferenceSize()); return size; } /** * Returns size of the Counter object used in the high scale lib NonBlockingHashMapLong. * * @return Size in bytes. */ private long getSizeOfHighScaleLibCounter() { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(1, 0, 0, 0, 0, 0); size = alignTo8Bytes(size); size += this.getSizeOfHighScaleLibCAT(); return size; } /** * Returns size of the CAT object used in the high scale lib Counter. * * @return Size in bytes. */ private long getSizeOfHighScaleLibCAT() { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(2, 0, 0, 0, 4, 0); size = alignTo8Bytes(size); // always has an array of 4 longs attached size += this.getSizeOfPrimitiveArray(4, LONG_SIZE); return size; } /** * Returns number of segments in the concurrent hash map. * * @param mapSize * Size of map * @param concurrencyLevel * Initial concurrency level. * @return Number of segments. */ protected int getNumberOfConcurrentSegments(int mapSize, int concurrencyLevel) { // if map is empty there is only one segment created no matter what return mapSize == 0 ? 1 : concurrencyLevel; } /** * {@inheritDoc} */ @Override public long alignTo8Bytes(long size) { long d = size % 8; if (d == 0) { return size; } else { return (size + 8) - d; } } /** * {@inheritDoc} */ @Override public long getSizeOfObjectObject() { return alignTo8Bytes(this.getSizeOfObjectHeader()); } /** * {@inheritDoc} */ @Override public long getSizeOfLongObject() { long size = this.getSizeOfObjectHeader() + LONG_SIZE; return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfIntegerObject() { long size = this.getSizeOfObjectHeader() + INT_SIZE; return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfShortObject() { long size = this.getSizeOfObjectHeader() + SHORT_SIZE; return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfCharacterObject() { long size = this.getSizeOfObjectHeader() + CHAR_SIZE; return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getSizeOfBooleanObject() { long size = this.getSizeOfObjectHeader() + BOOLEAN_SIZE; return alignTo8Bytes(size); } /** * {@inheritDoc} */ @Override public long getPrimitiveTypesSize(int referenceCount, int booleanCount, int intCount, int floatCount, int longCount, int doubleCount) { // note that the size of the booleans must be aligned to the int size // thus 1 boolean is in 4 bytes, but are also 2, 3 and 4 booleans in an object packed to int long booleanSize = 0; if (booleanCount > 0) { booleanSize = ((booleanCount * BOOLEAN_SIZE) + INT_SIZE) - ((booleanCount * BOOLEAN_SIZE) % INT_SIZE); } return booleanSize + (referenceCount * getReferenceSize()) + (intCount * INT_SIZE) + (floatCount * FLOAT_SIZE) + (longCount * LONG_SIZE) + (doubleCount * DOUBLE_SIZE); } /** * {@inheritDoc} */ @Override public float getObjectSecurityExpansionRate() { return objectSecurityExpansionRate; } /** * {@inheritDoc} */ @Override public void setObjectSecurityExpansionRate(float objectSecurityExpansionRate) { this.objectSecurityExpansionRate = objectSecurityExpansionRate; } /** * Calculates the size of the array with out objects in the array - <b> Can only be used on * non-primitive arrays </b>. * * @param arraySize * Size of array (length). * @return Size in bytes. */ @Override public long getSizeOfArray(int arraySize) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(arraySize, 0, 1, 0, 0, 0); return alignTo8Bytes(size); } /** * Calculates the size of the primitive array with the primitives in the array. * * @param arraySize * Size of array (length). * @param primitiveSize * Size in bytes of the primitive type in array * @return Size in bytes. */ @Override public long getSizeOfPrimitiveArray(int arraySize, long primitiveSize) { long size = this.getSizeOfObjectHeader() + INT_SIZE; if (ALLIGN_CLASS_CALCULATION) { size = alignTo8Bytes(size); } size += arraySize * primitiveSize; return alignTo8Bytes(size); } /** * Calculates the size of the {@link ConcurrentHashMap} segment. * * @param seqmentCapacity * Capacity that segment has. * * @return Size in bytes. */ protected long getSizeOfConcurrentSeqment(int seqmentCapacity) { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(2, 0, 3, 1, 0, 0); // plus the sync in the reentrant lock size += alignTo8Bytes(this.getSizeOfObjectHeader() + this.getPrimitiveTypesSize(3, 0, 1, 0, 0, 0)); // plus just the empty array because we don't know how many objects segment has, this is // calculated additionally in concurrent map size += this.getSizeOfArray(seqmentCapacity); return alignTo8Bytes(size); } /** * Returns the size of a HashMap entry. Not that the key and value objects are not in this size. * If HashSet is used the HashMapEntry value object will be a simple Object, thus this size has * to be added to the HashSet. * * @return Returns the size of a HashMap entry. Not that the key and value objects are not in * this size. If HashSet is used the HashMapEntry value object will be a simple Object, * thus this size has to be added to the HashSet. */ private long getSizeOfHashMapEntry() { long size = this.getSizeOfObjectHeader(); size += this.getPrimitiveTypesSize(3, 0, 1, 0, 0, 0); return alignTo8Bytes(size); } /** * Returns the capacity of the HashMap from it size. The calculations take the default capacity * of 16 and default load factor of 0.75. * * @param hashMapSize * Size of hash map. * @param initialCapacity * Initial map capacity. * @return Returns the capacity of the HashMap from it size. The calculations take the default * capacity of 16 and default load factor of 0.75. */ public int getHashMapCapacityFromSize(int hashMapSize, int initialCapacity) { // from JDK1.7.0_40 the map has 0 initial capacity // capacity goes to initial when first entry is added if (hashMapSize == 0) { return 0; } int capacity = 1; if (initialCapacity > 0) { capacity = initialCapacity; } float loadFactor = 0.75f; int threshold = (int) (capacity * loadFactor); while (threshold < hashMapSize) { capacity *= 2; threshold = (int) (capacity * loadFactor); } return capacity; } /** * Returns the concurrent hash map segment capacity from its size and initial capacity. * * @param seqmentSize * Number of elements in the segment. * @param initialCapacity * Initial capacity. * @return Size in bytes. */ protected int getSegmentCapacityFromSize(int seqmentSize, int initialCapacity) { int capacity = initialCapacity; float loadFactor = 0.75f; int threshold = (int) (capacity * loadFactor); while ((threshold + 1) <= seqmentSize) { capacity *= 2; threshold = (int) (capacity * loadFactor); } return capacity; } }