001: //package com.vladium.utils;
002: package clime.messadmin.providers.sizeof;
003:
004: import java.lang.reflect.Array;
005: import java.lang.reflect.Field;
006: import java.lang.reflect.Modifier;
007: import java.security.AccessController;
008: import java.security.PrivilegedActionException;
009: import java.security.PrivilegedExceptionAction;
010: import java.util.Arrays; //import java.util.IdentityHashMap;
011: import java.util.ArrayList;
012: import java.util.Collections;
013: import java.util.HashMap;
014: import java.util.Iterator;
015: import java.util.LinkedList;
016: import java.util.List;
017: import java.util.Map;
018: import java.util.WeakHashMap;
019:
020: import clime.messadmin.utils.IdentityHashMap;
021:
022: // ----------------------------------------------------------------------------
023: /**
024: * This non-instantiable class presents an API for object sizing
025: * as described in the
026: * <a href="http://www.javaworld.com/javaqa/2003-12/02-qa-1226-sizeof_p.html">article</a>.
027: * See individual methods for details.
028: *
029: * <P>
030: * This implementation is J2SE 1.4+ only. You would need to code your own
031: * identity hashmap to port this to earlier Java versions.
032: *
033: * <P>
034: * Security: this implementation uses AccessController.doPrivileged() so it
035: * could be granted privileges to access non-public class fields separately from
036: * your main application code. The minimum set of permissions necessary for this
037: * class to function correctly follows:
038: *
039: * <pre>
040: * permission java.lang.RuntimePermission "accessDeclaredMembers";
041: * permission java.lang.reflect.ReflectPermission "suppressAccessChecks";
042: * </pre>
043: *
044: * @author (C) <a href="http://www.javaworld.com/columns/jw-qna-index.shtml">Vlad
045: * Roubtsov</a>, 2003
046: */
047: public abstract class ObjectProfiler {
048: // public: ................................................................
049:
050: // the following constants are physical sizes (in bytes) and are JVM-dependent:
051: // [the current values are Ok for most 32-bit JVMs]
052:
053: public static final int OBJECT_SHELL_SIZE = 8; // java.lang.Object shell
054: // size in bytes
055: public static final int OBJREF_SIZE = 4;
056: public static final int LONG_FIELD_SIZE = 8;
057: public static final int INT_FIELD_SIZE = 4;
058: public static final int SHORT_FIELD_SIZE = 2;
059: public static final int CHAR_FIELD_SIZE = 2;
060: public static final int BYTE_FIELD_SIZE = 1;
061: public static final int BOOLEAN_FIELD_SIZE = 1;
062: public static final int DOUBLE_FIELD_SIZE = 8;
063: public static final int FLOAT_FIELD_SIZE = 4;
064:
065: /**
066: * Estimates the full size of the object graph rooted at 'obj'. Duplicate
067: * data instances are correctly accounted for. The implementation is not
068: * recursive.
069: *
070: * @param obj
071: * input object instance to be measured
072: * @return 'obj' size [0 if 'obj' is null']
073: */
074: public static long sizeof(final Object obj) {
075: if (null == obj)
076: return 0;
077:
078: final IdentityHashMap visited = new IdentityHashMap();
079:
080: try {
081: return computeSizeof(obj, visited, CLASS_METADATA_CACHE);
082: } catch (RuntimeException re) {
083: //re.printStackTrace();//DEBUG
084: return -1;
085: } catch (NoClassDefFoundError ncdfe) {
086: // BUG: throws "java.lang.NoClassDefFoundError: org.eclipse.core.resources.IWorkspaceRoot" when run in WSAD 5
087: // see http://www.javaworld.com/javaforums/showflat.php?Cat=&Board=958763&Number=15235&page=0&view=collapsed&sb=5&o=
088: //System.err.println(ncdfe);//DEBUG
089: return -1;
090: }
091: }
092:
093: /**
094: * Estimates the full size of the object graph rooted at 'obj' by
095: * pre-populating the "visited" set with the object graph rooted at 'base'.
096: * The net effect is to compute the size of 'obj' by summing over all
097: * instance data contained in 'obj' but not in 'base'.
098: *
099: * @param base
100: * graph boundary [may not be null]
101: * @param obj
102: * input object instance to be measured
103: * @return 'obj' size [0 if 'obj' is null']
104: */
105: public static long sizedelta(final Object base, final Object obj) {
106: if (null == obj)
107: return 0;
108: if (null == base)
109: throw new IllegalArgumentException("null input: base");
110:
111: final IdentityHashMap visited = new IdentityHashMap();
112:
113: try {
114: computeSizeof(base, visited, CLASS_METADATA_CACHE);
115: return visited.containsKey(obj) ? 0 : computeSizeof(obj,
116: visited, CLASS_METADATA_CACHE);
117: } catch (RuntimeException re) {
118: //re.printStackTrace();//DEBUG
119: return -1;
120: } catch (NoClassDefFoundError ncdfe) {
121: // BUG: throws "java.lang.NoClassDefFoundError: org.eclipse.core.resources.IWorkspaceRoot" when run in WSAD 5
122: // see http://www.javaworld.com/javaforums/showflat.php?Cat=&Board=958763&Number=15235&page=0&view=collapsed&sb=5&o=
123: //System.err.println(ncdfe);//DEBUG
124: return -1;
125: }
126: }
127:
128: // protected: .............................................................
129:
130: // package: ...............................................................
131:
132: // private: ...............................................................
133:
134: /*
135: * Internal class used to cache class metadata information.
136: */
137: private static final class ClassMetadata {
138: ClassMetadata(final int primitiveFieldCount,
139: final int shellSize, final Field[] refFields) {
140: m_primitiveFieldCount = primitiveFieldCount;
141: m_shellSize = shellSize;
142: m_refFields = refFields;
143: }
144:
145: // all fields are inclusive of superclasses:
146:
147: final int m_primitiveFieldCount;
148:
149: final int m_shellSize; // class shell size
150:
151: final Field[] m_refFields; // cached non-static fields (made accessible)
152:
153: } // end of nested class
154:
155: private static final class ClassAccessPrivilegedAction implements
156: PrivilegedExceptionAction {
157: /** {@inheritDoc} */
158: public Object run() throws Exception {
159: return m_cls.getDeclaredFields();
160: }
161:
162: void setContext(final Class cls) {
163: m_cls = cls;
164: }
165:
166: private Class m_cls;
167:
168: } // end of nested class
169:
170: private static final class FieldAccessPrivilegedAction implements
171: PrivilegedExceptionAction {
172: /** {@inheritDoc} */
173: public Object run() throws Exception {
174: m_field.setAccessible(true);
175: return null;
176: }
177:
178: void setContext(final Field field) {
179: m_field = field;
180: }
181:
182: private Field m_field;
183:
184: } // end of nested class
185:
186: private ObjectProfiler() {
187: } // this class is not extendible
188:
189: /*
190: * The main worker method for sizeof() and sizedelta().
191: */
192: private static long computeSizeof(Object obj,
193: final IdentityHashMap visited,
194: final Map /* <Class,ClassMetadata> */metadataMap) {
195: // this uses depth-first traversal; the exact graph traversal algorithm
196: // does not matter for computing the total size and this method could be
197: // easily adjusted to do breadth-first instead (addLast() instead of
198: // addFirst()),
199: // however, dfs/bfs require max queue length to be the length of the
200: // longest
201: // graph path/width of traversal front correspondingly, so I expect
202: // dfs to use fewer resources than bfs for most Java objects;
203:
204: if (null == obj)
205: return 0;
206:
207: final LinkedList queue = new LinkedList();
208:
209: visited.put(obj, obj);
210: queue.add(obj);
211:
212: long result = 0;
213:
214: final ClassAccessPrivilegedAction caAction = new ClassAccessPrivilegedAction();
215: final FieldAccessPrivilegedAction faAction = new FieldAccessPrivilegedAction();
216:
217: while (!queue.isEmpty()) {
218: obj = queue.removeFirst();
219: final Class objClass = obj.getClass();
220:
221: int skippedBytes = skipClassDueToSunSolarisBug(objClass);
222: if (skippedBytes > 0) {
223: result += skippedBytes; // can't do better than that
224: continue;
225: }
226:
227: if (objClass.isArray()) {
228: final int arrayLength = Array.getLength(obj);
229: final Class componentType = objClass.getComponentType();
230:
231: result += sizeofArrayShell(arrayLength, componentType);
232:
233: if (!componentType.isPrimitive()) {
234: // traverse each array slot:
235: for (int i = 0; i < arrayLength; ++i) {
236: final Object ref = Array.get(obj, i);
237:
238: if ((ref != null) && !visited.containsKey(ref)) {
239: visited.put(ref, ref);
240: queue.addFirst(ref);
241: }
242: }
243: }
244: } else { // the object is of a non-array type
245: final ClassMetadata metadata = getClassMetadata(
246: objClass, metadataMap, caAction, faAction);
247: final Field[] fields = metadata.m_refFields;
248:
249: result += metadata.m_shellSize;
250:
251: // traverse all non-null ref fields:
252: for (int f = 0, fLimit = fields.length; f < fLimit; ++f) {
253: final Field field = fields[f];
254:
255: final Object ref;
256: try { // to get the field value:
257: ref = field.get(obj);
258: } catch (Exception e) {
259: throw new RuntimeException("cannot get field ["
260: + field.getName() + "] of class ["
261: + field.getDeclaringClass().getName()
262: + "]: " + e.toString());
263: }
264:
265: if ((ref != null) && !visited.containsKey(ref)) {
266: visited.put(ref, ref);
267: queue.addFirst(ref);
268: }
269: }
270: }
271: }
272:
273: return result;
274: }
275:
276: /*
277: * A helper method for manipulating a class metadata cache.
278: */
279: private static ClassMetadata getClassMetadata(final Class cls,
280: final Map /* <Class,ClassMetadata> */metadataMap,
281: final ClassAccessPrivilegedAction caAction,
282: final FieldAccessPrivilegedAction faAction) {
283: if (null == cls)
284: return null;
285:
286: ClassMetadata result;
287: synchronized (metadataMap) {
288: result = (ClassMetadata) metadataMap.get(cls);
289: }
290: if (result != null)
291: return result;
292:
293: int primitiveFieldCount = 0;
294: int shellSize = OBJECT_SHELL_SIZE; // java.lang.Object shell
295: final List /* Field */refFields = new LinkedList();
296:
297: final Field[] declaredFields;
298: try {
299: caAction.setContext(cls);
300: declaredFields = (Field[]) AccessController
301: .doPrivileged(caAction);
302: } catch (PrivilegedActionException pae) {
303: throw new RuntimeException(
304: "could not access declared fields of class "
305: + cls.getName() + ": " + pae.getException());
306: }
307:
308: for (int f = 0; f < declaredFields.length; ++f) {
309: final Field field = declaredFields[f];
310: if (Modifier.isStatic(field.getModifiers()))
311: continue;
312: /* Can't do that: HashMap data is transient, for example...
313: if (Modifier.isTransient(field.getModifiers())) {
314: shellSize += OBJREF_SIZE;
315: continue;
316: }
317: */
318:
319: final Class fieldType = field.getType();
320: if (fieldType.isPrimitive()) {
321: // memory alignment ignored:
322: shellSize += sizeofPrimitiveType(fieldType);
323: ++primitiveFieldCount;
324: } else {
325: // prepare for graph traversal later:
326: if (!field.isAccessible()) {
327: try {
328: faAction.setContext(field);
329: AccessController.doPrivileged(faAction);
330: } catch (PrivilegedActionException pae) {
331: throw new RuntimeException(
332: "could not make field " + field
333: + " accessible: "
334: + pae.getException());
335: }
336: }
337:
338: // memory alignment ignored:
339: shellSize += OBJREF_SIZE;
340: refFields.add(field);
341: }
342: }
343:
344: // recurse into superclass:
345: final ClassMetadata super Metadata = getClassMetadata(cls
346: .getSuperclass(), metadataMap, caAction, faAction);
347: if (super Metadata != null) {
348: primitiveFieldCount += super Metadata.m_primitiveFieldCount;
349: shellSize += super Metadata.m_shellSize - OBJECT_SHELL_SIZE;
350: refFields.addAll(Arrays.asList(super Metadata.m_refFields));
351: }
352:
353: final Field[] _refFields = new Field[refFields.size()];
354: refFields.toArray(_refFields);
355:
356: result = new ClassMetadata(primitiveFieldCount, shellSize,
357: _refFields);
358: synchronized (metadataMap) {
359: metadataMap.put(cls, result);
360: }
361:
362: return result;
363: }
364:
365: /*
366: * Computes the "shallow" size of an array instance.
367: */
368: private static int sizeofArrayShell(final int length,
369: final Class componentType) {
370: // this ignores memory alignment issues by design:
371:
372: final int slotSize = componentType.isPrimitive() ? sizeofPrimitiveType(componentType)
373: : OBJREF_SIZE;
374:
375: return OBJECT_SHELL_SIZE + INT_FIELD_SIZE + OBJREF_SIZE
376: + length * slotSize;
377: }
378:
379: /*
380: * Returns the JVM-specific size of a primitive type.
381: */
382: private static int sizeofPrimitiveType(final Class type) {
383: if (type == int.class)
384: return INT_FIELD_SIZE;
385: else if (type == long.class)
386: return LONG_FIELD_SIZE;
387: else if (type == short.class)
388: return SHORT_FIELD_SIZE;
389: else if (type == byte.class)
390: return BYTE_FIELD_SIZE;
391: else if (type == boolean.class)
392: return BOOLEAN_FIELD_SIZE;
393: else if (type == char.class)
394: return CHAR_FIELD_SIZE;
395: else if (type == double.class)
396: return DOUBLE_FIELD_SIZE;
397: else if (type == float.class)
398: return FLOAT_FIELD_SIZE;
399: else
400: throw new IllegalArgumentException("not primitive: " + type);
401: }
402:
403: // class metadata cache:
404: private static final Map CLASS_METADATA_CACHE = new WeakHashMap(101);
405:
406: protected static final Class[] sunProblematicClasses;
407: protected static final Map/*<Class, Integer>*/sunProblematicClassesSizes;
408:
409: static {
410: Map classesSizes = new HashMap();
411: classesSizes.put("java.lang.Class", new Integer(
412: OBJECT_SHELL_SIZE));//not really a pb, but since this is shared, so there's no point in going further
413: // 1.3+
414: classesSizes.put("java.lang.Throwable", new Integer(20));
415: // 1.4+
416: classesSizes.put("sun.reflect.UnsafeStaticFieldAccessorImpl",
417: new Integer(OBJECT_SHELL_SIZE));//unknown
418: classesSizes.put(
419: "sun.reflect.UnsafeStaticObjectFieldAccessorImpl",
420: new Integer(OBJECT_SHELL_SIZE));//unknown
421: classesSizes.put(
422: "sun.reflect.UnsafeStaticObjectFieldAccessorImpl",
423: new Integer(OBJECT_SHELL_SIZE));//unknown
424: classesSizes.put(
425: "sun.reflect.UnsafeStaticLongFieldAccessorImpl",
426: new Integer(OBJECT_SHELL_SIZE));//unknown
427: // 1.5+
428: classesSizes.put("java.lang.Enum", new Integer(
429: OBJECT_SHELL_SIZE));//not really a pb, but since this is shared, so there's no point in going further
430: classesSizes.put("sun.reflect.ConstantPool", new Integer(8));
431: sunProblematicClassesSizes = Collections
432: .unmodifiableMap(classesSizes);
433:
434: List classes = new ArrayList(sunProblematicClassesSizes.size());
435: Iterator iter = sunProblematicClassesSizes.entrySet()
436: .iterator();
437: while (iter.hasNext()) {
438: Map.Entry entry = (Map.Entry) iter.next();
439: String className = (String) entry.getKey();
440: try {
441: classes.add(Class.forName(className));
442: } catch (ClassNotFoundException cnfe) {
443: //} catch (ExceptionInInitializerError eiie) {
444: //} catch (NoClassDefFoundError ncdfe) {
445: //} catch (UnsatisfiedLinkError le) {
446: } catch (LinkageError le) {
447: // BEA JRockit 1.4 also throws NoClassDefFoundError and UnsatisfiedLinkError
448: }
449: }
450: sunProblematicClasses = (Class[]) classes.toArray(new Class[0]);
451: }
452:
453: /**
454: * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5012949
455: * Implementation note:
456: * we can compare classes with == since they will always be loaded from the same ClassLoader
457: * (they are "low" in the hierarchy)
458: */
459: private static int skipClassDueToSunSolarisBug(Class clazz) {
460: for (int i = 0; i < sunProblematicClasses.length; ++i) {
461: Class sunPbClass = sunProblematicClasses[i];
462: if (clazz == sunPbClass) {
463: return ((Integer) sunProblematicClassesSizes.get(clazz
464: .getName())).intValue();
465: }
466: }
467: return 0;
468: }
469: } // end of class
470: // ----------------------------------------------------------------------------
|