All Downloads are FREE. Search and download functionalities are using the official Maven repository.

openllet.profiler.utils.ObjectProfiler Maven / Gradle / Ivy

There is a newer version: 2.6.5
Show newest version
package openllet.profiler.utils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import openllet.atom.OpenError;

// ----------------------------------------------------------------------------
/**
 * This non-instantiable class presents an API for object sizing and profiling as described in the article. See _individual methods for details.
 * 

* You would need to code your own identity hashmap to port this to earlier Java versions. *

* Security: this implementation uses AccessController.doPrivileged() so it could be granted privileges to access non-public class fields separately from your * main application code. The minimum set of persmissions necessary for this class to function correctly follows: * *

 *      permission java.lang.RuntimePermission "accessDeclaredMembers";
 *      permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
 * 
* * @see IObjectProfileNode * @author (C) Vlad Roubtsov, 2003 */ public abstract class ObjectProfiler { public static final String INPUT_OBJECT_NAME = ""; // root _node name // the following constants are physical sizes (in bytes) and are JVM-dependent: // [the _current values are Ok for most 32-bit JVMs] public static final int OBJECT_SHELL_SIZE = 8; // java.lang.Object shell size in bytes public static final int OBJREF_SIZE = 4; public static final int LONG_FIELD_SIZE = 8; public static final int INT_FIELD_SIZE = 4; public static final int SHORT_FIELD_SIZE = 2; public static final int CHAR_FIELD_SIZE = 2; public static final int BYTE_FIELD_SIZE = 1; public static final int BOOLEAN_FIELD_SIZE = 1; public static final int DOUBLE_FIELD_SIZE = 8; public static final int FLOAT_FIELD_SIZE = 4; // class metadata _cache: private static final Map, ClassMetadata> CLASS_METADATA_CACHE = new WeakHashMap<>(101); /** * Set this to 'true' to make the _node names default to using class names without package prefixing for more compact dumps */ public static final boolean SHORT_TYPE_NAMES = false; /** * If this is 'true', node names will use short class names for common classes in java.lang.* and java.util.*, regardless of {@link #SHORT_TYPE_NAMES} * setting. */ public static final boolean SHORT_COMMON_TYPE_NAMES = true; /** * Estimates the full size of the object graph rooted at 'obj'. Duplicate _data instances are correctly accounted for. The implementation is not recursive. *

* Invariant: sizeof(obj) == profile(obj).size() if 'obj' is not null * * @param obj input object instance to be measured * @return 'obj' size [0 if 'obj' is null'] */ public static int sizeof(final Object obj) { if (obj == null) return 0; final IdentityHashMap visited = new IdentityHashMap<>(); return computeSizeof(obj, visited, CLASS_METADATA_CACHE); } public static int sizeof(final Object obj, final Object... avoid) { if (obj == null) return 0; final IdentityHashMap visited = new IdentityHashMap<>(); for (final Object o : avoid) visited.put(o, o); return computeSizeof(obj, visited, CLASS_METADATA_CACHE); } /** * Estimates the full size of the object graph rooted at 'obj' by pre-populating the "visited" set with the object graph rooted at 'base'. The net effect is * to compute the size of 'obj' by summing over all instance _data contained in 'obj' but not in 'base'. * * @param base graph boundary [may not be null] * @param obj input object instance to be measured * @return 'obj' size [0 if 'obj' is null'] */ public static int sizedelta(final Object base, final Object obj) { if (obj == null) return 0; if (base == null) throw new IllegalArgumentException("null input: base"); final IdentityHashMap visited = new IdentityHashMap<>(); computeSizeof(base, visited, CLASS_METADATA_CACHE); return visited.containsKey(obj) ? 0 : computeSizeof(obj, visited, CLASS_METADATA_CACHE); } /** * Creates a spanning tree representation for instance _data contained in 'obj'. The tree is produced using bread-first traversal over the full object graph * implied by non-null instance and array references originating in 'obj'. * * @see IObjectProfileNode * @param obj input object instance to be profiled [may not be null] * @return the profile tree root _node [never null] */ public static IObjectProfileNode profile(final Object obj) { if (obj == null) throw new IllegalArgumentException("null input: obj"); final IdentityHashMap visited = new IdentityHashMap<>(); final ObjectProfileNode root = createProfileTree(obj, visited, CLASS_METADATA_CACHE); finishProfileTree(root); return root; } // convenience methods: public static String pathName(final IObjectProfileNode[] path) { final StringBuffer s = new StringBuffer(); for (int i = 0; i < path.length; ++i) { if (i != 0) s.append('/'); s.append(path[i].name()); } return s.toString(); } public static String fieldName(final Field field, final boolean shortClassNames) { return typeName(field.getDeclaringClass(), shortClassNames).concat("#").concat(field.getName()); } public static String typeName(final Class clsParam, final boolean shortClassNames) { Class cls = clsParam; int dims = 0; for (; cls.isArray(); ++dims) cls = cls.getComponentType(); String clsName = cls.getName(); if (shortClassNames) { final int lastDot = clsName.lastIndexOf('.'); if (lastDot >= 0) clsName = clsName.substring(lastDot + 1); } else if (SHORT_COMMON_TYPE_NAMES) if (clsName.startsWith("java.lang.")) clsName = clsName.substring(10); else if (clsName.startsWith("java.util.")) clsName = clsName.substring(10); for (int i = 0; i < dims; ++i) clsName = clsName.concat("[]"); return clsName; } /** * Internal class used to _cache class metadata information. */ private static final class ClassMetadata { public final int _primitiveFieldCount; public final int _shellSize; // class shell size public final Field[] _refFields; // cached non-static fields (made accessible) public ClassMetadata(final int primitiveFieldCount, final int shellSize, final Field[] refFields) { _primitiveFieldCount = primitiveFieldCount; _shellSize = shellSize; _refFields = refFields; } } private static final class ClassAccessPrivilegedAction implements PrivilegedExceptionAction { @Override public Object run() throws Exception { return _cls.getDeclaredFields(); } public void setContext(final Class cls) { _cls = cls; } private Class _cls; } private static final class FieldAccessPrivilegedAction implements PrivilegedExceptionAction { @Override public Object run() throws Exception { _field.setAccessible(true); return null; } public void setContext(final Field field) { _field = field; } private Field _field; } private ObjectProfiler() { // prevent instance. } /* * The main worker method for sizeof() and sizedelta(). */ private static int computeSizeof(final Object objParam, final IdentityHashMap visited, final Map /* Class->ClassMetadata */, ClassMetadata> metadataMap) { Object obj = objParam; // this uses _depth-first traversal; the exact graph traversal algorithm // does not matter for computing the total size and this method could be // easily adjusted to do breadth-first instead (addLast() instead of addFirst()), // however, dfs/bfs require max _queue length to be the length of the longest // graph path/width of traversal front correspondingly, so I expect // dfs to use fewer resources than bfs for most Java objects; if (obj == null) return 0; final LinkedList queue = new LinkedList<>(); visited.put(obj, obj); queue.add(obj); int result = 0; final ClassAccessPrivilegedAction caAction = new ClassAccessPrivilegedAction(); final FieldAccessPrivilegedAction faAction = new FieldAccessPrivilegedAction(); while (!queue.isEmpty()) { obj = queue.removeFirst(); final Class objClass = obj.getClass(); if (objClass.isArray()) { final int arrayLength = Array.getLength(obj); final Class componentType = objClass.getComponentType(); result += sizeofArrayShell(arrayLength, componentType); if (!componentType.isPrimitive()) // traverse each array slot: for (int i = 0; i < arrayLength; ++i) { final Object ref = Array.get(obj, i); if ((ref != null) && !visited.containsKey(ref)) { visited.put(ref, ref); queue.addFirst(ref); } } } else // the object is of a non-array type { final ClassMetadata metadata = getClassMetadata(objClass, metadataMap, caAction, faAction); final Field[] fields = metadata._refFields; result += metadata._shellSize; // traverse all non-null ref fields: for (final Field field : fields) { final Object ref; try // to get the field value: { ref = field.get(obj); } catch (final Exception e) { throw new OpenError("cannot get field [" + field.getName() + "] of class [" + field.getDeclaringClass().getName() + "]: " + e.toString()); } if ((ref != null) && !visited.containsKey(ref)) { visited.put(ref, ref); queue.addFirst(ref); } } } } return result; } /** * Performs phase 1 of profile creation: bread-first traversal and node creation. */ private static ObjectProfileNode createProfileTree(final Object objParam, final IdentityHashMap visited, final Map /* Class->ClassMetadata */, ClassMetadata> metadataMap) { Object obj = objParam; final ObjectProfileNode root = new ObjectProfileNode(null, obj, null); final LinkedList queue = new LinkedList<>(); queue.addFirst(root); visited.put(obj, root); final ClassAccessPrivilegedAction caAction = new ClassAccessPrivilegedAction(); final FieldAccessPrivilegedAction faAction = new FieldAccessPrivilegedAction(); while (!queue.isEmpty()) { final ObjectProfileNode node = queue.removeFirst(); obj = node._obj; final Class objClass = obj.getClass(); if (objClass.isArray()) { final int arrayLength = Array.getLength(obj); final Class componentType = objClass.getComponentType(); // add shell pseudo-_node: final AbstractShellProfileNode shell = new ArrayShellProfileNode(node, objClass, arrayLength); shell._size = sizeofArrayShell(arrayLength, componentType); node._shell = shell; node.addFieldRef(shell); if (!componentType.isPrimitive()) // traverse each array slot: for (int i = 0; i < arrayLength; ++i) { final Object ref = Array.get(obj, i); if (ref != null) { ObjectProfileNode child = visited.get(ref); if (child != null) ++child._refcount; else { child = new ObjectProfileNode(node, ref, new ArrayIndexLink(node._link, i)); node.addFieldRef(child); queue.addLast(child); visited.put(ref, child); } } } } else // the object is of a non-array type { final ClassMetadata metadata = getClassMetadata(objClass, metadataMap, caAction, faAction); final Field[] fields = metadata._refFields; // add shell pseudo-_node: final AbstractShellProfileNode shell = new ObjectShellProfileNode(node, metadata._primitiveFieldCount, metadata._refFields.length); shell._size = metadata._shellSize; node._shell = shell; node.addFieldRef(shell); // traverse all non-null ref fields: for (final Field field : fields) { final Object ref; try // to get the field value: { ref = field.get(obj); } catch (final Exception e) { throw new OpenError("cannot get field [" + field.getName() + "] of class [" + field.getDeclaringClass().getName() + "]: " + e.toString()); } if (ref != null) { ObjectProfileNode child = visited.get(ref); if (child != null) ++child._refcount; else { child = new ObjectProfileNode(node, ref, new ClassFieldLink(field)); node.addFieldRef(child); queue.addLast(child); visited.put(ref, child); } } } } } return root; } /** * Performs phase 2 of profile creation: totalling of _node sizes (via non-recursive post-order traversal of the tree created in phase 1) and 'locking down' * of profile nodes into their most compact form. */ private static void finishProfileTree(final ObjectProfileNode nodeParam) { ObjectProfileNode node = nodeParam; final LinkedList queue = new LinkedList<>(); IObjectProfileNode lastFinished = null; while (node != null) { // note that an unfinished non-shell _node has its child count // in m_size and m_children[0] is its shell _node: if ((node._size == 1) || (lastFinished == node._children[1])) { node.finish(); lastFinished = node; } else { queue.addFirst(node); for (int i = 1; i < node._size; ++i) { final IObjectProfileNode child = node._children[i]; queue.addFirst(child); } } if (queue.isEmpty()) return; else node = (ObjectProfileNode) queue.removeFirst(); } } /** * A helper method for manipulating a class metadata cache. */ private static ClassMetadata getClassMetadata(final Class cls, final Map /* Class->ClassMetadata */, ClassMetadata> metadataMap, final ClassAccessPrivilegedAction caAction, final FieldAccessPrivilegedAction faAction) { if (cls == null) return null; ClassMetadata result; synchronized (metadataMap) { result = metadataMap.get(cls); } if (result != null) return result; int primitiveFieldCount = 0; int shellSize = OBJECT_SHELL_SIZE; // java.lang.Object shell final List /* Field */ refFields = new LinkedList<>(); final Field[] declaredFields; try { caAction.setContext(cls); declaredFields = (Field[]) AccessController.doPrivileged(caAction); } catch (final PrivilegedActionException pae) { throw new OpenError("could not access declared fields of class " + cls.getName() + ": " + pae.getException()); } for (final Field declaredField : declaredFields) { final Field field = declaredField; if ((Modifier.STATIC & field.getModifiers()) != 0) continue; final Class fieldType = field.getType(); if (fieldType.isPrimitive()) { // memory alignment ignored: shellSize += sizeofPrimitiveType(fieldType); ++primitiveFieldCount; } else { // prepare for graph traversal later: if (!field.isAccessible()) try { faAction.setContext(field); AccessController.doPrivileged(faAction); } catch (final PrivilegedActionException pae) { throw new OpenError("could not make field " + field + " accessible: " + pae.getException()); } // memory alignment ignored: shellSize += OBJREF_SIZE; refFields.add(field); } } // recurse into superclass: final ClassMetadata superMetadata = getClassMetadata(cls.getSuperclass(), metadataMap, caAction, faAction); if (superMetadata != null) { primitiveFieldCount += superMetadata._primitiveFieldCount; shellSize += superMetadata._shellSize - OBJECT_SHELL_SIZE; refFields.addAll(Arrays.asList(superMetadata._refFields)); } final Field[] _refFields = new Field[refFields.size()]; refFields.toArray(_refFields); result = new ClassMetadata(primitiveFieldCount, shellSize, _refFields); synchronized (metadataMap) { metadataMap.put(cls, result); } return result; } /** * Computes the "shallow" size of an array instance. */ private static int sizeofArrayShell(final int length, final Class componentType) { // this ignores memory alignment issues by design: final int slotSize = componentType.isPrimitive() ? sizeofPrimitiveType(componentType) : OBJREF_SIZE; return OBJECT_SHELL_SIZE + INT_FIELD_SIZE + OBJREF_SIZE + length * slotSize; } /** * Returns the JVM-specific size of a primitive type. */ private static int sizeofPrimitiveType(final Class type) { if (type == int.class) return INT_FIELD_SIZE; else if (type == long.class) return LONG_FIELD_SIZE; else if (type == short.class) return SHORT_FIELD_SIZE; else if (type == byte.class) return BYTE_FIELD_SIZE; else if (type == boolean.class) return BOOLEAN_FIELD_SIZE; else if (type == char.class) return CHAR_FIELD_SIZE; else if (type == double.class) return DOUBLE_FIELD_SIZE; else if (type == float.class) return FLOAT_FIELD_SIZE; else throw new IllegalArgumentException("not primitive: " + type); } }