/sizeof-agent/src/main/java/com/atlassian/levee/sizeof/SizeOf.java

https://bitbucket.org/jkodumal/levee · Java · 148 lines · 109 code · 6 blank · 33 comment · 43 complexity · 1de4cfe83129058decaae18c0e49282d MD5 · raw file

  1. package com.atlassian.levee.sizeof;
  2. import java.lang.instrument.Instrumentation;
  3. import java.lang.reflect.Array;
  4. import java.lang.reflect.Field;
  5. import java.lang.reflect.Modifier;
  6. import java.util.ArrayDeque;
  7. import java.util.IdentityHashMap;
  8. import java.util.logging.Logger;
  9. // Originally from https://github.com/dmlap/jvm-sizeof
  10. public class SizeOf {
  11. private static final Logger LOG = Logger
  12. .getLogger(SizeOf.class.getName());
  13. private static Instrumentation instrumentation;
  14. /**
  15. * Initialization method called by the JVM at startup.
  16. *
  17. * @see http://download.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html
  18. */
  19. public static void premain(String args, Instrumentation inst) {
  20. assert instrumentation == null : "SizeOf should not be re-initialized.";
  21. assert inst != null : "SizeOf must be initialized with non-null instrumentation. Make sure you've configured javaagent correctly";
  22. SizeOf.instrumentation = inst;
  23. }
  24. /**
  25. * Returns an implementation-specific approximation of the amount of storage
  26. * consumed by the specified {@link Object} (in bytes).
  27. *
  28. * @param target
  29. * - the {@link Object} to query
  30. * @return an implementation-specific approximation of the amount of storage
  31. * used
  32. */
  33. public static long sizeof(Object target) {
  34. assert instrumentation != null : "SizeOf has not been initialized. Add it as a javaagent when starting your JVM";
  35. return instrumentation.getObjectSize(target);
  36. }
  37. /**
  38. * Returns an implementation-specific approximation of the amount of storage
  39. * consumed by the specified object and all values referenced by it.
  40. *
  41. * @param target
  42. * - the {@link Object} to query
  43. * @return an approximation of the retained heap usage for the {@link Object}
  44. */
  45. public static long deepsize(Object target) {
  46. long result = sizeof(target);
  47. IdentityHashMap<Object, Void> references = new IdentityHashMap<Object, Void>();
  48. references.put(target, null);
  49. ArrayDeque<Object> unprocessed = new ArrayDeque<Object>();
  50. unprocessed.addFirst(target);
  51. do {
  52. Object node = unprocessed.removeFirst();
  53. Class<?> nodeClass = node.getClass();
  54. if (nodeClass.isArray()) {
  55. if (node.getClass().getComponentType().isPrimitive()) {
  56. continue;
  57. }
  58. int length = Array.getLength(node);
  59. for (int i = 0; i < length; ++i) {
  60. Object elem = Array.get(node, i);
  61. if (elem == null) {
  62. continue;
  63. }
  64. if (references.containsKey(elem)) {
  65. continue;
  66. }
  67. unprocessed.addFirst(elem);
  68. references.put(elem, null);
  69. result += sizeof(elem);
  70. }
  71. continue;
  72. }
  73. while (nodeClass != null) { // traverse up until we hit Object
  74. for (Field field : nodeClass.getDeclaredFields()) {
  75. if (Modifier.isStatic(field.getModifiers())) {
  76. continue;
  77. }
  78. field.setAccessible(true);
  79. try {
  80. Class<?> type = field.getType();
  81. // primitive types
  82. if(type.isPrimitive()) {
  83. continue;
  84. }
  85. // reference types
  86. Object value = field.get(node);
  87. if(value == null) {
  88. continue;
  89. }
  90. if(references.containsKey(value)) {
  91. continue;
  92. }
  93. if (isSharedFlyweight(value)) {
  94. continue;
  95. }
  96. unprocessed.addFirst(value);
  97. references.put(value, null);
  98. result += sizeof(value);
  99. } catch (IllegalArgumentException e) {
  100. throw new SizeOfException("Error determing the size of field "
  101. + field.getName() + " on " + target.getClass().getSimpleName(), e);
  102. } catch (IllegalAccessException e) {
  103. throw new SizeOfException("Error determing the size of field "
  104. + field.getName() + " on " + target.getClass().getSimpleName(), e);
  105. }
  106. }
  107. nodeClass = nodeClass.getSuperclass();
  108. }
  109. } while(!unprocessed.isEmpty());
  110. return result;
  111. }
  112. /**
  113. * Returns true if this is a well-known shared flyweight.
  114. * For example, interned Strings, Booleans and Number objects.
  115. *
  116. * thanks to Dr. Heinz Kabutz
  117. * see http://www.javaspecialists.co.za/archive/Issue142.html
  118. */
  119. private static boolean isSharedFlyweight(Object obj) {
  120. // optimization - all of our flyweights are Comparable
  121. if (obj instanceof Comparable) {
  122. if (obj instanceof Enum) {
  123. return true;
  124. } else if (obj instanceof String) {
  125. return (obj == ((String) obj).intern());
  126. } else if (obj instanceof Boolean) {
  127. return (obj == Boolean.TRUE || obj == Boolean.FALSE);
  128. } else if (obj instanceof Integer) {
  129. return (obj == Integer.valueOf((Integer) obj));
  130. } else if (obj instanceof Short) {
  131. return (obj == Short.valueOf((Short) obj));
  132. } else if (obj instanceof Byte) {
  133. return (obj == Byte.valueOf((Byte) obj));
  134. } else if (obj instanceof Long) {
  135. return (obj == Long.valueOf((Long) obj));
  136. } else if (obj instanceof Character) {
  137. return (obj == Character.valueOf((Character) obj));
  138. }
  139. }
  140. return false;
  141. }
  142. }