/*
* Copyright 2013 Christopher Pheby
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jadira.reflection.access.unsafe;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.IdentityHashMap;
import org.jadira.reflection.access.api.ClassAccess;
import org.jadira.reflection.access.api.FieldAccess;
import org.jadira.reflection.core.misc.ClassUtils;
/**
* A set of utility methods for working with sun.misc.Unsafe. Address shallow and deep copying,
* field access and field manipulation.
*/
@SuppressWarnings("restriction")
public final class UnsafeOperations {
private static final int REFERENCE_STACK_LIMIT = 150;
private static final int SIZE_BYTES_BOOLEAN = 1;
private static final int SIZE_BYTES_BYTE = 1;
private static final int SIZE_BYTES_CHAR = 2;
private static final int SIZE_BYTES_SHORT = 2;
private static final int SIZE_BYTES_INT = 4;
private static final int SIZE_BYTES_LONG = 8;
private static final int SIZE_BYTES_FLOAT = 4;
private static final int SIZE_BYTES_DOUBLE = 8;
/**
* The size of a page that an object will be placed in (always 8 bytes currently) (NB for
* HotSpot can be retrieved using ObjectAlignmentInBytes in HotSpotDiagnosticMXBean, but
* as this is always 8 for existing JVMs this is hardcoded).
*/
private static final int SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT = 8;
private static final int MIN_SIZE = 16;
private static final sun.misc.Unsafe THE_UNSAFE;
private static final boolean IS_UNSAFE_AVAILABLE;
private static final UnsafeOperations INSTANCE = new UnsafeOperations();
static {
boolean isUnsafeAvailable = true;
sun.misc.Unsafe theUnsafe = null;
try {
Class.forName("android.os.Process");
isUnsafeAvailable = false;
} catch (ClassNotFoundException e) {
// Ignored
} finally {
if (isUnsafeAvailable) {
try {
Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe");
f.setAccessible(true);
theUnsafe = (sun.misc.Unsafe) f.get(null);
} catch (ClassNotFoundException e) {
isUnsafeAvailable = false;
} catch (IllegalArgumentException e) {
isUnsafeAvailable = false;
} catch (IllegalAccessException e) {
isUnsafeAvailable = false;
} catch (NoSuchFieldException e) {
isUnsafeAvailable = false;
} catch (SecurityException e) {
isUnsafeAvailable = false;
}
}
}
IS_UNSAFE_AVAILABLE = isUnsafeAvailable;
THE_UNSAFE = theUnsafe;
}
private UnsafeOperations() {
}
/**
* Returns the (singleton) UnsafeOperations instance
* @return UnsafeOperations
*/
public static final UnsafeOperations getUnsafeOperations() {
if (isUnsafeAvailable()) {
return INSTANCE;
} else {
throw new IllegalStateException("Unsafe is not available");
}
}
/**
* Check whether the Unsafe API is accessible
* @return True if available
*/
public static boolean isUnsafeAvailable() {
return IS_UNSAFE_AVAILABLE;
}
/**
* Construct and allocate on the heap an instant of the given class, without calling the class constructor
* @param clazz Class to create instant for
* @param <T> Type of the instance to be constructed
* @return The new instance
* @throws IllegalStateException Indicates a problem occurred
*/
public final <T> T allocateInstance(Class<T> clazz) throws IllegalStateException {
try {
@SuppressWarnings("unchecked")
final T result = (T) THE_UNSAFE.allocateInstance(clazz);
return result;
} catch (InstantiationException e) {
throw new IllegalStateException("Cannot allocate instance: " + e.getMessage(), e);
}
}
/**
* Gets an offset for the given field relative to the field base. Any particular field will always have the
* same offset, and no two distinct fields of the same class will ever have the same offset.
* @param f The Field to determine the offset for
* @return The offset represented as a long
*/
public final long getObjectFieldOffset(Field f) {
return THE_UNSAFE.objectFieldOffset(f);
}
/**
* Performs a shallow copy of the given object - a new instance is allocated with the same contents. Any object
* references inside the copy will be the same as the original object.
* @param obj Object to copy
* @param <T> The type being copied
* @return A new instance, identical to the original
*/
public final <T> T shallowCopy(T obj) {
long size = shallowSizeOf(obj);
long address = THE_UNSAFE.allocateMemory(size);
long start = toAddress(obj);
THE_UNSAFE.copyMemory(start, address, size);
@SuppressWarnings("unchecked")
final T result = (T) fromAddress(address);
return result;
}
/**
* Convert the object reference to a memory address represented as a signed long
* @param obj The object
* @return A long representing the address of the object
*/
public final long toAddress(Object obj) {
Object[] array = new Object[] { obj };
long baseOffset = THE_UNSAFE.arrayBaseOffset(Object[].class);
return normalize(THE_UNSAFE.getInt(array, baseOffset));
}
/**
* Returns the object located at the given memory address
* @param address The address (a signed long) for the object
* @return The Object at the given address
*/
public final Object fromAddress(long address) {
Object[] array = new Object[] { null };
long baseOffset = THE_UNSAFE.arrayBaseOffset(Object[].class);
THE_UNSAFE.putLong(array, baseOffset, address);
return array[0];
}
/**
* Copy the value from the given field from the source into the target.
* The field specified must contain a primitive
* @param source The object to copy from
* @param copy The target object
* @param field Field to be copied
*/
public final void copyPrimitiveField(Object source, Object copy, Field field) {
copyPrimitiveAtOffset(source, copy, field.getType(), getObjectFieldOffset(field));
}
/**
* Copies the primitive of the specified type from the given field offset in the source object
* to the same location in the copy
* @param source The object to copy from
* @param copy The target object
* @param type The type of primitive at the given offset - e.g. java.lang.Boolean.TYPE
* @param offset The offset to copy from
*/
public final void copyPrimitiveAtOffset(Object source, Object copy, Class<?> type, long offset) {
if (java.lang.Boolean.TYPE == type) {
boolean origFieldValue = THE_UNSAFE.getBoolean(source, offset);
THE_UNSAFE.putBoolean(copy, offset, origFieldValue);
} else if (java.lang.Byte.TYPE == type) {
byte origFieldValue = THE_UNSAFE.getByte(source, offset);
THE_UNSAFE.putByte(copy, offset, origFieldValue);
} else if (java.lang.Character.TYPE == type) {
char origFieldValue = THE_UNSAFE.getChar(source, offset);
THE_UNSAFE.putChar(copy, offset, origFieldValue);
} else if (java.lang.Short.TYPE == type) {
short origFieldValue = THE_UNSAFE.getShort(source, offset);
THE_UNSAFE.putShort(copy, offset, origFieldValue);
} else if (java.lang.Integer.TYPE == type) {
int origFieldValue = THE_UNSAFE.getInt(source, offset);
THE_UNSAFE.putInt(copy, offset, origFieldValue);
} else if (java.lang.Long.TYPE == type) {
long origFieldValue = THE_UNSAFE.getLong(source, offset);
THE_UNSAFE.putLong(copy, offset, origFieldValue);
} else if (java.lang.Float.TYPE == type) {
float origFieldValue = THE_UNSAFE.getFloat(source, offset);
THE_UNSAFE.putFloat(copy, offset, origFieldValue);
} else if (java.lang.Double.TYPE == type) {
double origFieldValue = THE_UNSAFE.getDouble(source, offset);
THE_UNSAFE.putDouble(copy, offset, origFieldValue);
}
}
/**
* Restores the primitive at the given field to its default value. Default value is defined as the
* value that the field would hold if it was a new, uninitialised value (e.g. false for a boolean).
* @param copy The target object
* @param type The type of primitive at the given offset - e.g. java.lang.Boolean.TYPE
* @param offset The offset to reset to its default value
*/
public final void putPrimitiveDefaultAtOffset(Object copy, Class<?> type, long offset) {
if (java.lang.Boolean.TYPE == type) {
THE_UNSAFE.putBoolean(copy, offset, false);
} else if (java.lang.Byte.TYPE == type) {
THE_UNSAFE.putByte(copy, offset, (byte) 0);
} else if (java.lang.Character.TYPE == type) {
THE_UNSAFE.putChar(copy, offset, '\u0000');
} else if (java.lang.Short.TYPE == type) {
THE_UNSAFE.putShort(copy, offset, (short) 0);
} else if (java.lang.Integer.TYPE == type) {
THE_UNSAFE.putInt(copy, offset, 0);
} else if (java.lang.Long.TYPE == type) {
THE_UNSAFE.putLong(copy, offset, 0L);
} else if (java.lang.Float.TYPE == type) {
THE_UNSAFE.putFloat(copy, offset, 0.0f);
} else if (java.lang.Double.TYPE == type) {
THE_UNSAFE.putDouble(copy, offset, 0.0d);
}
}
/**
* Performs a deep copy of the object. With a deep copy all references from the object are also copied.
* The identity of referenced objects is preserved, so, for example, if the object graph contains two
* references to the same object, the cloned object will preserve this structure.
* @param obj The object to perform a deep copy for.
* @param <T> The type being copied
* @return A deep copy of the original object.
*/
public <T> T deepCopy(final T obj) {
return deepCopy(obj, new IdentityHashMap<Object, Object>(10));
}
/**
* Performs a deep copy of the object. With a deep copy all references from the object are also copied.
* The identity of referenced objects is preserved, so, for example, if the object graph contains two
* references to the same object, the cloned object will preserve this structure.
* @param o The object to perform a deep copy for.
* @param referencesToReuse An identity map of references to reuse - this is further populated as the copy progresses.
* The key is the original object reference - the value is the copied instance for that original.
* @param <T> The type being copied
* @return A deep copy of the original object.
*/
public <T> T deepCopy(final T o, IdentityHashMap<Object, Object> referencesToReuse) {
/**
* To avoid unnecessary recursion and potential stackoverflow errors, we use an internal
* stack
*/
final Deque<WorkItem<?>> stack;
if (referencesToReuse.size() >= REFERENCE_STACK_LIMIT) {
stack = new ArrayDeque<WorkItem<?>>();
} else {
stack = null;
}
Object objectInput;
WorkItem<?> nextWork = null;
while (true) {
Object objectResult;
if (nextWork == null) {
objectInput = o;
} else {
objectInput = getObject(nextWork.getSource(), nextWork.getFieldAccess().fieldOffset());
}
if (objectInput == null) {
objectResult = null;
} else {
if (String.class.isAssignableFrom(objectInput.getClass())) {
objectInput = ((String)objectInput);
}
Class<?> clazz = objectInput.getClass();
if (clazz.isPrimitive() || clazz.isEnum()) {
objectResult = objectInput;
} else if (ClassUtils.isJdkImmutable(clazz) || ClassUtils.isWrapper(clazz)) {
objectResult = objectInput;
} else {
final Object result = referencesToReuse.get(objectInput);
if (result != null) {
objectResult = result;
} else {
if (clazz.isArray()) {
objectResult = deepCopyArray(objectInput, referencesToReuse);
} else {
UnsafeClassAccess<?> classAccess = UnsafeClassAccess.get(clazz);
objectResult = allocateInstance(objectInput.getClass());
referencesToReuse.put(objectInput, objectResult);
ClassAccess<?> classInHierarchy = classAccess;
while (!classInHierarchy.getType().equals(java.lang.Object.class)) {
for (FieldAccess<?> f : classInHierarchy.getDeclaredFieldAccessors()) {
UnsafeFieldAccess<?> uf = (UnsafeFieldAccess<?>)f;
if (f.fieldClass().isPrimitive()) {
copyPrimitiveAtOffset(objectInput, objectResult, f.fieldClass(), uf.fieldOffset());
} else if (stack == null) {
deepCopyObjectAtOffset(objectInput, objectResult, f.fieldClass(), uf.fieldOffset(), referencesToReuse);
} else {
@SuppressWarnings({ "unchecked", "rawtypes" })
final WorkItem item = new WorkItem(objectInput, objectResult, uf);
stack.addFirst(item);
}
}
classInHierarchy = classInHierarchy.getSuperClassAccess();
}
}
}
}
}
if (nextWork == null) {
nextWork = (stack == null ? null : stack.pollFirst());
if (nextWork == null) {
@SuppressWarnings("unchecked")
final T convertedResult = (T) objectResult;
return convertedResult;
}
} else if (nextWork != null) {
if (objectResult == null) {
putNullObject(nextWork.getTarget(), nextWork.getFieldAccess().fieldOffset());
} else {
putObject(nextWork.getTarget(), nextWork.getFieldAccess().fieldOffset(), objectResult);
}
nextWork = (stack == null ? null : stack.pollFirst());
}
}
}
/**
* Copies the object of the specified type from the given field offset in the source object
* to the same location in the copy, visiting the object during the copy so that its fields are also copied
* @param source The object to copy from
* @param copy The target object
* @param fieldClass The declared type of object at the given offset
* @param offset The offset to copy from
*/
public final void deepCopyObjectAtOffset(Object source, Object copy, Class<?> fieldClass, long offset) {
deepCopyObjectAtOffset(source, copy, fieldClass, offset, new IdentityHashMap<Object, Object>(100));
}
/**
* Copies the object of the specified type from the given field offset in the source object
* to the same location in the copy, visiting the object during the copy so that its fields are also copied
* @param source The object to copy from
* @param copy The target object
* @param fieldClass The declared type of object at the given offset
* @param offset The offset to copy from
* @param referencesToReuse An identity map of references to reuse - this is further populated as the copy progresses.
* The key is the original object reference - the value is the copied instance for that original.
*/
public final void deepCopyObjectAtOffset(Object source, Object copy, Class<?> fieldClass, long offset, IdentityHashMap<Object, Object> referencesToReuse) {
Object origFieldValue = THE_UNSAFE.getObject(source, offset);
if (origFieldValue == null) {
putNullObject(copy, offset);
} else {
final Object copyFieldValue = deepCopy(origFieldValue, referencesToReuse);
UnsafeOperations.THE_UNSAFE.putObject(copy, offset, copyFieldValue);
}
}
/**
* Copies the object of the specified type from the given field in the source object
* to the same field in the copy, visiting the object during the copy so that its fields are also copied
* @param source The object to copy from
* @param copy The target object
* @param field Field to be copied
* @param referencesToReuse An identity map of references to reuse - this is further populated as the copy progresses.
* The key is the original object reference - the value is the copied instance for that original.
*/
public final void deepCopyObjectField(Object source, Object copy, Field field, IdentityHashMap<Object, Object> referencesToReuse) {
deepCopyObjectAtOffset(source, copy, field.getType(), getObjectFieldOffset(field), referencesToReuse);
}
/**
* Copies the object of the specified type from the given field in the source object
* to the same field in the copy, visiting the object during the copy so that its fields are also copied
* @param obj The object to copy from
* @param copy The target object
* @param field Field to be copied
*/
public final void deepCopyObjectField(Object obj, Object copy, Field field) {
deepCopyObjectAtOffset(obj, copy, field.getType(), getObjectFieldOffset(field), new IdentityHashMap<Object, Object>(100));
}
/**
* Copies the array of the specified type from the given field offset in the source object
* to the same location in the copy, visiting the array during the copy so that its contents are also copied
* @param source The object to copy from
* @param copy The target object
* @param fieldClass The declared type of array at the given offset
* @param offset The offset to copy from
*/
public final void deepCopyArrayAtOffset(Object source, Object copy, Class<?> fieldClass, long offset) {
deepCopyArrayAtOffset(source, copy, fieldClass, offset, new IdentityHashMap<Object, Object>(100));
}
/**
* Copies the array of the specified type from the given field offset in the source object
* to the same location in the copy, visiting the array during the copy so that its contents are also copied
* @param source The object to copy from
* @param copy The target object
* @param fieldClass The declared type of array at the given offset
* @param offset The offset to copy from
* @param referencesToReuse An identity map of references to reuse - this is further populated as the copy progresses.
* The key is the original object reference - the value is the copied instance for that original.
*/
public final void deepCopyArrayAtOffset(Object source, Object copy, Class<?> fieldClass, long offset, IdentityHashMap<Object, Object> referencesToReuse) {
Object origFieldValue = THE_UNSAFE.getObject(source, offset);
if (origFieldValue == null) {
putNullObject(copy, offset);
} else {
final Object copyFieldValue = deepCopyArray(origFieldValue, referencesToReuse);
UnsafeOperations.THE_UNSAFE.putObject(copy, offset, copyFieldValue);
}
}
/**
* Copies the array of the specified type from the given field in the source object
* to the same field in the copy, visiting the array during the copy so that its contents are also copied
* @param obj The object to copy from
* @param copy The target object
* @param field Field to be copied
* @param referencesToReuse An identity map of references to reuse - this is further populated as the copy progresses.
* The key is the original object reference - the value is the copied instance for that original.
*/
public final void deepCopyArrayField(Object obj, Object copy, Field field, IdentityHashMap<Object, Object> referencesToReuse) {
deepCopyArrayAtOffset(obj, copy, field.getType(), getObjectFieldOffset(field), referencesToReuse);
}
/**
* Copies the array of the specified type from the given field in the source object
* to the same field in the copy, visiting the array during the copy so that its contents are also copied
* @param obj The object to copy from
* @param copy The target object
* @param field Field to be copied
*/
public final void deepCopyArrayField(Object obj, Object copy, Field field) {
deepCopyArrayAtOffset(obj, copy, field.getType(), getObjectFieldOffset(field), new IdentityHashMap<Object, Object>(100));
}
/**
* Performs a deep copy of the array. With a deep copy all references from the array are also copied.
* The identity of referenced objects is preserved, so, for example, if the object graph contains two
* references to the same object, the cloned object will preserve this structure.
* @param arrayOriginal The array to perform a deep copy for.
* @param visited An identity map of references to reuse - this is further populated as the copy progresses.
* The key is the original object reference - the value is the copied instance for that original.
* @return A deep copy of the original array.
*/
public final Object deepCopyArray(Object arrayOriginal, IdentityHashMap<Object, Object> visited) {
if (visited.containsKey(arrayOriginal)) {
return visited.get(arrayOriginal);
}
final Class<?> componentType = arrayOriginal.getClass().getComponentType();
Object result = null;
if (componentType.isPrimitive()) {
if (java.lang.Boolean.TYPE == componentType) {
result = Arrays.copyOf((boolean[]) arrayOriginal, ((boolean[]) arrayOriginal).length);
} else if (java.lang.Byte.TYPE == componentType) {
result = Arrays.copyOf((byte[]) arrayOriginal, ((byte[]) arrayOriginal).length);
} else if (java.lang.Character.TYPE == componentType) {
result = Arrays.copyOf((char[]) arrayOriginal, ((char[]) arrayOriginal).length);
} else if (java.lang.Short.TYPE == componentType) {
result = Arrays.copyOf((short[]) arrayOriginal, ((short[]) arrayOriginal).length);
} else if (java.lang.Integer.TYPE == componentType) {
result = Arrays.copyOf((int[]) arrayOriginal, ((int[]) arrayOriginal).length);
} else if (java.lang.Long.TYPE == componentType) {
result = Arrays.copyOf((long[]) arrayOriginal, ((long[]) arrayOriginal).length);
} else if (java.lang.Float.TYPE == componentType) {
result = Arrays.copyOf((float[]) arrayOriginal, ((float[]) arrayOriginal).length);
} else if (java.lang.Double.TYPE == componentType) {
result = Arrays.copyOf((double[]) arrayOriginal, ((double[]) arrayOriginal).length);
}
}
if (result == null) {
Object[] arrayCopy = Arrays.copyOf((Object[]) arrayOriginal, ((Object[]) arrayOriginal).length);
if (arrayCopy.length > 0) {
if (componentType.isArray()) {
for (int i = 0; i < arrayCopy.length; i++) {
arrayCopy[i] = deepCopyArray(arrayCopy[i], visited);
}
} else {
for (int i = 0; i < arrayCopy.length; i++) {
Object component = deepCopy(arrayCopy[i], visited);
arrayCopy[i] = component;
}
}
}
result = arrayCopy;
}
visited.put(arrayOriginal, result);
return result;
}
/**
* Determines the shallow memory size of an instance of the given class
* @param clazz The class to calculate the shallow size for
* @return Size in bytes
*/
public final long shallowSizeOf(Class<?> clazz) {
return doShallowSizeOfClass(clazz);
}
/**
* Determines the shallow memory size of the given object (object or array)
* @param obj The object instance to calculate the shallow size for
* @return Size in bytes
*/
public final long shallowSizeOf(Object obj) {
if (obj == null) {
return 0;
}
if (obj.getClass().isArray()) {
return doShallowSizeOfArray(obj);
} else {
return doShallowSizeOfClass(obj.getClass());
}
}
private long doShallowSizeOfArray(Object array) {
long size = getSizeOfArrayHeader();
final int length = Array.getLength(array);
if (length > 0) {
Class<?> type = array.getClass().getComponentType();
if (type.isPrimitive()) {
if (java.lang.Boolean.TYPE == type) {
size = size + (length * SIZE_BYTES_BOOLEAN);
} else if (java.lang.Byte.TYPE == type) {
size = size + (length * SIZE_BYTES_BYTE);
} else if (java.lang.Character.TYPE == type) {
size = size + (length * SIZE_BYTES_CHAR);
} else if (java.lang.Short.TYPE == type) {
size = size + (length * SIZE_BYTES_SHORT);
} else if (java.lang.Integer.TYPE == type) {
size = size + (length * SIZE_BYTES_INT);
} else if (java.lang.Long.TYPE == type) {
size = size + (length * SIZE_BYTES_LONG);
} else if (java.lang.Float.TYPE == type) {
size = size + (length * SIZE_BYTES_FLOAT);
} else if (java.lang.Double.TYPE == type) {
size = size + (length * SIZE_BYTES_DOUBLE);
}
} else {
size = size + (length * getSizeOfObjectHeader());
}
}
size = size + SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT - 1L;
return size - (size % SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT);
}
private long doShallowSizeOfClass(Class<?> clazz) {
if (clazz.isArray()) {
throw new IllegalArgumentException("Shallow Size of cannot be calculated for arrays classes as component length is needed");
}
if (clazz.isPrimitive()) {
return getSizeForPrimitive(clazz);
}
if (clazz == Object.class) {
return MIN_SIZE;
}
long size = getSizeOfObjectHeader();
Field[] fields = ClassUtils.collectInstanceFields(clazz);
for (Field f : fields) {
Class<?> fieldClass = f.getType();
final int fieldSize = fieldClass.isPrimitive() ? getSizeForPrimitive(fieldClass) : getSizeOfObjectHeader();
final long offsetPlusSize = getObjectFieldOffset(f) + fieldSize;
if (offsetPlusSize > size) {
size = offsetPlusSize;
}
}
size = size + SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT - 1L;
return size - (size % SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT);
}
/**
* Determines the deep memory size of the given object (object or array), visiting all its references
* @param o The object instance to calculate the deep size for
* @return Size in bytes
*/
public final long deepSizeOf(Object o) {
IdentityHashMap<Object, Boolean> seenObjects = new IdentityHashMap<Object, Boolean>(10);
return doDeepSizeOf(o, seenObjects);
}
private long doDeepSizeOf(Object o, IdentityHashMap<Object, Boolean> seenObjects) {
if (o == null) {
return 0;
}
Class<?> clazz = o.getClass();
if (clazz.isPrimitive()) {
return getSizeForPrimitive(clazz);
}
seenObjects.put(o, Boolean.TRUE);
if (clazz.isArray()) {
long size = doShallowSizeOfArray(o);
if (!clazz.getComponentType().isPrimitive()) {
Object[] array = (Object[]) o;
for (int i = 0; i < array.length; i++) {
Object nextObject = array[i];
if (nextObject != null && !seenObjects.containsKey(nextObject)) {
size = size + doDeepSizeOf(nextObject, seenObjects);
}
}
}
return size;
} else {
if (clazz == Object.class) {
return MIN_SIZE;
}
long size = getSizeOfObjectHeader();
long additionalSize = 0;
Field[] fields = ClassUtils.collectInstanceFields(clazz);
for (Field f : fields) {
long objectFieldOffset = getObjectFieldOffset(f);
Class<?> fieldClass = f.getType();
final int fieldSize = fieldClass.isPrimitive() ? getSizeForPrimitive(fieldClass) : getSizeOfObjectHeader();
final long offsetPlusSize = objectFieldOffset + fieldSize;
if (offsetPlusSize > size) {
size = offsetPlusSize;
}
if (!fieldClass.isPrimitive()) {
Object fieldObject = getObject(o, objectFieldOffset);
if (fieldObject != null && !seenObjects.containsKey(fieldObject)) {
additionalSize += doDeepSizeOf(fieldObject, seenObjects);
}
}
}
size = size + SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT - 1L;
size = size - (size % SIZE_BYTES_PAGE_FOR_OBJECT_ALIGNMENT);
return size + additionalSize;
}
}
/**
* Memory address payload is an unsigned value - we need to normalise the 'sign' to get a
* meaningful value back - when we do this we need to store it into a long
* @param value The value to normalise
* @return The normalised value as a long
*/
private static long normalize(int value) {
if (value >= 0) {
return value;
}
return (~0L >>> 32) & value;
}
/**
* Get the size in bytes of a native pointer - either 4 or 8 (for 32-bit or 64-bit JRE).
* For primitive types, the size is determined by their data type rather than this value.
* @return The address size
*/
public final int getAddressSize() {
return THE_UNSAFE.addressSize();
}
/**
* Retrieve the object at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved object
*/
public final Object getObject(Object parent, long offset) {
return THE_UNSAFE.getObject(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved boolean
*/
public final boolean getBoolean(Object parent, long offset) {
return THE_UNSAFE.getBoolean(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved char
*/
public final char getChar(Object parent, long offset) {
return THE_UNSAFE.getChar(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved short
*/
public final short getShort(Object parent, long offset) {
return THE_UNSAFE.getShort(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved int
*/
public final int getInt(Object parent, long offset) {
return THE_UNSAFE.getInt(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved long
*/
public final long getLong(Object parent, long offset) {
return THE_UNSAFE.getLong(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved float
*/
public final float getFloat(Object parent, long offset) {
return THE_UNSAFE.getFloat(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved double
*/
public final double getDouble(Object parent, long offset) {
return THE_UNSAFE.getDouble(parent, offset);
}
/**
* Retrieve the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @return The retrieved byte
*/
public final byte getByte(Object parent, long offset) {
return THE_UNSAFE.getByte(parent, offset);
}
/**
* Write a null value to the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putNullObject(Object parent, long offset) {
THE_UNSAFE.putObject(parent, offset, null);
}
/**
* Resets to false the boolean value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultBoolean(Object parent, long offset) {
THE_UNSAFE.putBoolean(parent, offset, false);
}
/**
* Resets to u0000 the char value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultChar(Object parent, long offset) {
THE_UNSAFE.putChar(parent, offset, '\u0000');
}
/**
* Resets to 0 the short value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultShort(Object parent, long offset) {
THE_UNSAFE.putShort(parent, offset, (short)0);
}
/**
* Resets to 0 the int value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultInt(Object parent, long offset) {
THE_UNSAFE.putInt(parent, offset, 0);
}
/**
* Resets to 0 the long value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultLong(Object parent, long offset) {
THE_UNSAFE.putLong(parent, offset, 0L);
}
/**
* Resets to 0.0f the float value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultFloat(Object parent, long offset) {
THE_UNSAFE.putFloat(parent, offset, 0.0F);
}
/**
* Resets to 0.0d the double value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultDouble(Object parent, long offset) {
THE_UNSAFE.putDouble(parent, offset, 0.0D);
}
/**
* Resets to 0 the byte value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
*/
public final void putDefaultByte(Object parent, long offset) {
THE_UNSAFE.putByte(parent, offset, (byte)0);
}
/**
* Puts the object's reference at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value Object to be put
*/
public final void putObject(Object parent, long offset, Object value) {
THE_UNSAFE.putObject(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value boolean to be put
*/
public final void putBoolean(Object parent, long offset, boolean value) {
THE_UNSAFE.putBoolean(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value char to be put
*/
public final void putChar(Object parent, long offset, char value) {
THE_UNSAFE.putChar(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value short to be put
*/
public final void putShort(Object parent, long offset, short value) {
THE_UNSAFE.putShort(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value int to be put
*/
public final void putInt(Object parent, long offset, int value) {
THE_UNSAFE.putInt(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value long to be put
*/
public final void putLong(Object parent, long offset, long value) {
THE_UNSAFE.putLong(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value float to be put
*/
public final void putFloat(Object parent, long offset, float value) {
THE_UNSAFE.putFloat(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value double to be put
*/
public final void putDouble(Object parent, long offset, double value) {
THE_UNSAFE.putDouble(parent, offset, value);
}
/**
* Puts the value at the given offset of the supplied parent object
* @param parent The Object's parent
* @param offset The offset
* @param value byte to be put
*/
public final void putByte(Object parent, long offset, byte value) {
THE_UNSAFE.putByte(parent, offset, value);
}
private class WorkItem<P> {
private final Object source;
private final Object target;
private final UnsafeFieldAccess<P> fieldAccess;
public WorkItem(Object source, Object target, UnsafeFieldAccess<P> fieldAccess) {
this.source = source;
this.target = target;
this.fieldAccess = fieldAccess;
}
public Object getSource() {
return source;
}
public Object getTarget() {
return target;
}
public UnsafeFieldAccess<P> getFieldAccess() {
return fieldAccess;
}
}
@SuppressWarnings("unused")
private static final class SingleFieldHolder {
public int field;
}
private final int getSizeOfArrayHeader() {
return THE_UNSAFE.arrayBaseOffset(byte[].class);
}
private final int getSizeForPrimitive(Class<?> clazz) {
if (java.lang.Boolean.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_BOOLEAN;
} else if (java.lang.Byte.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_BYTE;
} else if (java.lang.Character.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_CHAR;
} else if (java.lang.Short.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_SHORT;
} else if (java.lang.Integer.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_INT;
} else if (java.lang.Long.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_LONG;
} else if (java.lang.Float.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_FLOAT;
} else if (java.lang.Double.TYPE.isAssignableFrom(clazz)) {
return SIZE_BYTES_DOUBLE;
}
throw new IllegalArgumentException("Class " + clazz.getName() + " is not primitive");
}
private final int getSizeOfObjectHeader() {
try {
return (int) THE_UNSAFE.objectFieldOffset(SingleFieldHolder.class.getDeclaredField("field"));
} catch (NoSuchFieldException e) {
throw new IllegalStateException("Cannot determine size of object header", e);
} catch (SecurityException e) {
throw new IllegalStateException("Cannot determine size of object header", e);
}
}
}