protected int estimate(Object obj, HashMap<Class<?>, ObjectEstimator> parent,
        IdentityHashMap<Object, Boolean> uniqueObjects) {
      // TODO: maybe use stack of est+obj pairs instead of recursion.
      if (fields == null) {
        return directSize;
      }
      int referencedSize = 0;
      for (FieldAndType e : fields) {
        Object fieldObj;
        try {
          fieldObj = e.field.get(obj);
        } catch (IllegalAccessException ex) {
          throw new AssertionError("IAE: " + ex.getMessage());
        }
        // reference is already accounted for in the directSize.
        if (fieldObj == null)
          continue;
        if (null != uniqueObjects.put(fieldObj, Boolean.TRUE))
          continue;
        switch (e.type) {
        case COLLECTION: {
          Collection<?> c = (Collection<?>) fieldObj;
          ObjectEstimator collEstimator = parent.get(fieldObj.getClass());
          if (collEstimator == null) {
            // We have no estimator for this type... assume low overhead and hope for the best.
            LOG.trace("Approximate estimation for collection {} from {}", e.field,
                fieldObj.getClass().getName());
            referencedSize += memoryModel.object();
            referencedSize += estimateCollectionElements(parent, c, e.field, uniqueObjects);
            referencedSize += memoryModel.array() + c.size() * memoryModel.ref();
          } else if (collEstimator instanceof CollectionEstimator) {
            referencedSize += memoryModel.object();
            referencedSize += estimateCollectionElements(parent, c, e.field, uniqueObjects);
            referencedSize += ((CollectionEstimator) collEstimator).estimateOverhead(c.size());
          } else {
            // We decided to treat this collection as regular object.
            LOG.trace("Verbose estimation for collection {} from {}", fieldObj.getClass().getName(),
                e.field);
            referencedSize += collEstimator.estimate(c, parent, uniqueObjects);
          }
          break;
        }
        case MAP: {
          Map<?, ?> m = (Map<?, ?>) fieldObj;
          ObjectEstimator collEstimator = parent.get(fieldObj.getClass());
          if (collEstimator == null) {
            // We have no estimator for this type... assume low overhead and hope for the best.
            LOG.trace("Approximate estimation for map {} from {}", fieldObj.getClass().getName(),
                e.field);
            referencedSize += memoryModel.object();
            referencedSize += estimateMapElements(parent, m, e.field, uniqueObjects);
            referencedSize +=
                memoryModel.array() + m.size() * (memoryModel.ref() * 2 + memoryModel.object());
          } else if (collEstimator instanceof CollectionEstimator) {
            referencedSize += memoryModel.object();
            referencedSize += estimateMapElements(parent, m, e.field, uniqueObjects);
            referencedSize += ((CollectionEstimator) collEstimator).estimateOverhead(m.size());
          } else {
            // We decided to treat this map as regular object.
            LOG.trace("Verbose estimation for map {} from {}", fieldObj.getClass().getName(),
                e.field);
            referencedSize += collEstimator.estimate(m, parent, uniqueObjects);
          }
          break;
        }
        case OBJECT_ARRAY: {
          int len = Array.getLength(fieldObj);
          referencedSize += JavaDataModel.alignUp(memoryModel.array() + len * memoryModel.ref(),
              memoryModel.memoryAlign());
          if (len == 0)
            continue;
          referencedSize += estimateArrayElements(parent, e, fieldObj, len, uniqueObjects);
          break;
        }
        case PRIMITIVE_ARRAY: {
          int arraySize = memoryModel.array();
          int len = Array.getLength(fieldObj);
          if (len != 0) {
            int elementSize = getPrimitiveSize(e.field.getType().getComponentType());
            arraySize += elementSize * len;
            arraySize = (int) JavaDataModel.alignUp(arraySize, memoryModel.memoryAlign());
          }
          referencedSize += arraySize;
          break;
        }
        case OTHER: {
          ObjectEstimator fieldEstimator = parent.get(fieldObj.getClass());
          if (fieldEstimator == null) {
            createEstimators(fieldObj.getClass(), parent);
          }
          fieldEstimator = parent.get(fieldObj.getClass());
          if (fieldEstimator == null) {
            throw new AssertionError(
                "Don't know how to measure " + fieldObj.getClass().getName() + " from " + e.field);
          }
          referencedSize += fieldEstimator.estimate(fieldObj, parent, uniqueObjects);
          break;
        }
        default:
          throw new AssertionError("Unknown type " + e.type);
        }
      }
      return directSize + referencedSize;
    }
Creates size estimators for java objects. The estimators attempt to do most of the reflection
work at initialization time, and also take some shortcuts, to minimize the amount of work done
during the actual estimation.
TODO: clean up
storage-api/src/java/org/apache/hadoop/hive/ql/util/IncrementalObjectSizeEstimator.java