Store instance fields and types with class objects.

Rather than duplicating them for every class instance.

Test: m ahat-test

Change-Id: Ifddf49918ca8532332928ab09ed9983d6ad8858f
diff --git a/tools/ahat/src/ObjectHandler.java b/tools/ahat/src/ObjectHandler.java
index 8262910..f4926aa 100644
--- a/tools/ahat/src/ObjectHandler.java
+++ b/tools/ahat/src/ObjectHandler.java
@@ -152,7 +152,7 @@
    *                   ignored otherwise.
    */
   private static void printFields(Doc doc, Query query, String id, boolean diff,
-      List<FieldValue> current, List<FieldValue> baseline) {
+      Iterable<FieldValue> current, Iterable<FieldValue> baseline) {
 
     if (!diff) {
       // To avoid having to special case when diff is disabled, always diff
diff --git a/tools/ahat/src/heapdump/AhatArrayInstance.java b/tools/ahat/src/heapdump/AhatArrayInstance.java
index 4b18bf7..8d23276 100644
--- a/tools/ahat/src/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/heapdump/AhatArrayInstance.java
@@ -128,7 +128,7 @@
   }
 
   @Override
-  ReferenceIterator getReferences() {
+  Iterable<Reference> getReferences() {
     // The list of references will be empty if this is a primitive array.
     List<Reference> refs = Collections.emptyList();
     if (!mValues.isEmpty()) {
@@ -153,7 +153,7 @@
         };
       }
     }
-    return new ReferenceIterator(refs);
+    return new SkipNullsIterator(refs);
   }
 
   @Override public boolean isArrayInstance() {
diff --git a/tools/ahat/src/heapdump/AhatClassInstance.java b/tools/ahat/src/heapdump/AhatClassInstance.java
index 2115923..f7d8431 100644
--- a/tools/ahat/src/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/heapdump/AhatClassInstance.java
@@ -19,12 +19,16 @@
 import com.android.tools.perflib.heap.ClassInstance;
 import com.android.tools.perflib.heap.Instance;
 import java.awt.image.BufferedImage;
-import java.util.AbstractList;
-import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.NoSuchElementException;
 
 public class AhatClassInstance extends AhatInstance {
-  private FieldValue[] mFieldValues;
+  // Instance fields of the object. These are stored in order of the instance
+  // field descriptors from the class object, starting with this class first,
+  // followed by the super class, and so on. We store the values separate from
+  // the field types and names to save memory.
+  private Value[] mFields;
 
   public AhatClassInstance(long id) {
     super(id);
@@ -35,18 +39,14 @@
 
     ClassInstance classInst = (ClassInstance)inst;
     List<ClassInstance.FieldValue> fieldValues = classInst.getValues();
-    mFieldValues = new FieldValue[fieldValues.size()];
-    for (int i = 0; i < mFieldValues.length; i++) {
-      ClassInstance.FieldValue field = fieldValues.get(i);
-      String name = field.getField().getName();
-      String type = field.getField().getType().toString();
-      Value value = snapshot.getValue(field.getValue());
-      mFieldValues[i] = new FieldValue(name, type, value);
+    mFields = new Value[fieldValues.size()];
+    for (int i = 0; i < mFields.length; i++) {
+      mFields[i] = snapshot.getValue(fieldValues.get(i).getValue());
     }
   }
 
   @Override public Value getField(String fieldName) {
-    for (FieldValue field : mFieldValues) {
+    for (FieldValue field : getInstanceFields()) {
       if (fieldName.equals(field.name)) {
         return field.value;
       }
@@ -90,36 +90,21 @@
   /**
    * Returns the list of class instance fields for this instance.
    */
-  public List<FieldValue> getInstanceFields() {
-    return Arrays.asList(mFieldValues);
+  public Iterable<FieldValue> getInstanceFields() {
+    return new InstanceFieldIterator(mFields, getClassObj());
   }
 
   @Override
-  ReferenceIterator getReferences() {
-    List<Reference> refs = new AbstractList<Reference>() {
-      @Override
-      public int size() {
-        return mFieldValues.length;
-      }
-
-      @Override
-      public Reference get(int index) {
-        FieldValue field = mFieldValues[index];
-        Value value = field.value;
-        if (value != null && value.isAhatInstance()) {
-          boolean strong = !field.name.equals("referent")
-                        || !isInstanceOfClass("java.lang.ref.Reference");
-          AhatInstance ref = value.asAhatInstance();
-          return new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
-        }
-        return null;
-      }
-    };
-    return new ReferenceIterator(refs);
+  Iterable<Reference> getReferences() {
+    if (isInstanceOfClass("java.lang.ref.Reference")) {
+      return new WeakReferentReferenceIterator();
+    }
+    return new StrongReferenceIterator();
   }
 
   /**
-   * Returns true if this is an instance of a class with the given name.
+   * Returns true if this is an instance of a (subclass of a) class with the
+   * given name.
    */
   private boolean isInstanceOfClass(String className) {
     AhatClassObj cls = getClassObj();
@@ -262,4 +247,131 @@
     bitmap.setRGB(0, 0, info.width, info.height, abgr, 0, info.width);
     return bitmap;
   }
+
+  private static class InstanceFieldIterator implements Iterable<FieldValue>,
+                                                        Iterator<FieldValue> {
+    // The complete list of instance field values to iterate over, including
+    // superclass field values.
+    private Value[] mValues;
+    private int mValueIndex;
+
+    // The list of field descriptors specific to the current class in the
+    // class hierarchy, not including superclass field descriptors.
+    // mFields and mFieldIndex are reset each time we walk up to the next
+    // superclass in the call hierarchy.
+    private Field[] mFields;
+    private int mFieldIndex;
+    private AhatClassObj mNextClassObj;
+
+    public InstanceFieldIterator(Value[] values, AhatClassObj classObj) {
+      mValues = values;
+      mFields = classObj.getInstanceFields();
+      mValueIndex = 0;
+      mFieldIndex = 0;
+      mNextClassObj = classObj.getSuperClassObj();
+    }
+
+    @Override
+    public boolean hasNext() {
+      // If we have reached the end of the fields in the current class,
+      // continue walking up the class hierarchy to get superclass fields as
+      // well.
+      while (mFieldIndex == mFields.length && mNextClassObj != null) {
+        mFields = mNextClassObj.getInstanceFields();
+        mFieldIndex = 0;
+        mNextClassObj = mNextClassObj.getSuperClassObj();
+      }
+      return mFieldIndex < mFields.length;
+    }
+
+    @Override
+    public FieldValue next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      Field field = mFields[mFieldIndex++];
+      Value value = mValues[mValueIndex++];
+      return new FieldValue(field.name, field.type, value);
+    }
+
+    @Override
+    public Iterator<FieldValue> iterator() {
+      return this;
+    }
+  }
+
+  /**
+   * A Reference iterator that iterates over the fields of this instance
+   * assuming all field references are strong references.
+   */
+  private class StrongReferenceIterator implements Iterable<Reference>,
+                                                   Iterator<Reference> {
+    private Iterator<FieldValue> mIter = getInstanceFields().iterator();
+    private Reference mNext = null;
+
+    @Override
+    public boolean hasNext() {
+      while (mNext == null && mIter.hasNext()) {
+        FieldValue field = mIter.next();
+        if (field.value != null && field.value.isAhatInstance()) {
+          AhatInstance ref = field.value.asAhatInstance();
+          mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, true);
+        }
+      }
+      return mNext != null;
+    }
+
+    @Override
+    public Reference next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      Reference next = mNext;
+      mNext = null;
+      return next;
+    }
+
+    @Override
+    public Iterator<Reference> iterator() {
+      return this;
+    }
+  }
+
+  /**
+   * A Reference iterator that iterates over the fields of a subclass of
+   * java.lang.ref.Reference, where the 'referent' field is considered weak.
+   */
+  private class WeakReferentReferenceIterator implements Iterable<Reference>,
+                                                         Iterator<Reference> {
+    private Iterator<FieldValue> mIter = getInstanceFields().iterator();
+    private Reference mNext = null;
+
+    @Override
+    public boolean hasNext() {
+      while (mNext == null && mIter.hasNext()) {
+        FieldValue field = mIter.next();
+        if (field.value != null && field.value.isAhatInstance()) {
+          boolean strong = !field.name.equals("referent");
+          AhatInstance ref = field.value.asAhatInstance();
+          mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
+        }
+      }
+      return mNext != null;
+    }
+
+    @Override
+    public Reference next() {
+      if (!hasNext()) {
+        throw new NoSuchElementException();
+      }
+      Reference next = mNext;
+      mNext = null;
+      return next;
+    }
+
+    @Override
+    public Iterator<Reference> iterator() {
+      return this;
+    }
+  }
 }
diff --git a/tools/ahat/src/heapdump/AhatClassObj.java b/tools/ahat/src/heapdump/AhatClassObj.java
index 052d7a8..08c7097 100644
--- a/tools/ahat/src/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/heapdump/AhatClassObj.java
@@ -17,7 +17,6 @@
 package com.android.ahat.heapdump;
 
 import com.android.tools.perflib.heap.ClassObj;
-import com.android.tools.perflib.heap.Field;
 import com.android.tools.perflib.heap.Instance;
 import java.util.AbstractList;
 import java.util.Arrays;
@@ -30,6 +29,7 @@
   private AhatClassObj mSuperClassObj;
   private AhatInstance mClassLoader;
   private FieldValue[] mStaticFieldValues;
+  private Field[] mInstanceFields;
 
   public AhatClassObj(long id) {
     super(id);
@@ -51,15 +51,22 @@
       mClassLoader = snapshot.findInstance(loader.getId());
     }
 
-    Collection<Map.Entry<Field, Object>> fieldValues = classObj.getStaticFieldValues().entrySet();
+    Collection<Map.Entry<com.android.tools.perflib.heap.Field, Object>> fieldValues
+      = classObj.getStaticFieldValues().entrySet();
     mStaticFieldValues = new FieldValue[fieldValues.size()];
     int index = 0;
-    for (Map.Entry<Field, Object> field : fieldValues) {
+    for (Map.Entry<com.android.tools.perflib.heap.Field, Object> field : fieldValues) {
       String name = field.getKey().getName();
       String type = field.getKey().getType().toString();
       Value value = snapshot.getValue(field.getValue());
       mStaticFieldValues[index++] = new FieldValue(name, type, value);
     }
+
+    com.android.tools.perflib.heap.Field[] fields = classObj.getFields();
+    mInstanceFields = new Field[fields.length];
+    for (int i = 0; i < fields.length; i++) {
+      mInstanceFields[i] = new Field(fields[i].getName(), fields[i].getType().toString());
+    }
   }
 
   /**
@@ -90,8 +97,15 @@
     return Arrays.asList(mStaticFieldValues);
   }
 
+  /**
+   * Returns the fields of instances of this class.
+   */
+  public Field[] getInstanceFields() {
+    return mInstanceFields;
+  }
+
   @Override
-  ReferenceIterator getReferences() {
+  Iterable<Reference> getReferences() {
     List<Reference> refs = new AbstractList<Reference>() {
       @Override
       public int size() {
@@ -108,7 +122,7 @@
         return null;
       }
     };
-    return new ReferenceIterator(refs);
+    return new SkipNullsIterator(refs);
   }
 
   @Override public boolean isClassObj() {
diff --git a/tools/ahat/src/heapdump/AhatInstance.java b/tools/ahat/src/heapdump/AhatInstance.java
index e1b83b8..0e78558 100644
--- a/tools/ahat/src/heapdump/AhatInstance.java
+++ b/tools/ahat/src/heapdump/AhatInstance.java
@@ -148,7 +148,7 @@
    * Returns an iterator over the references this AhatInstance has to other
    * AhatInstances.
    */
-  abstract ReferenceIterator getReferences();
+  abstract Iterable<Reference> getReferences();
 
   /**
    * Returns true if this instance is marked as a root instance.
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
index 2b3e056..8b4c679 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderClassObj.java
@@ -29,10 +29,6 @@
     baseline.setBaseline(this);
   }
 
-  @Override public Size getSize() {
-    return Size.ZERO;
-  }
-
   @Override public Size getRetainedSize(AhatHeap heap) {
     return Size.ZERO;
   }
@@ -68,4 +64,8 @@
   @Override public AhatInstance getClassLoader() {
     return getBaseline().asClassObj().getClassLoader().getBaseline();
   }
+
+  @Override public Field[] getInstanceFields() {
+    return getBaseline().asClassObj().getInstanceFields();
+  }
 }
diff --git a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
index d797b11..9abc952 100644
--- a/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
+++ b/tools/ahat/src/heapdump/AhatPlaceHolderInstance.java
@@ -65,8 +65,8 @@
   }
 
   @Override
-  ReferenceIterator getReferences() {
+  Iterable<Reference> getReferences() {
     List<Reference> refs = Collections.emptyList();
-    return new ReferenceIterator(refs);
+    return refs;
   }
 }
diff --git a/tools/ahat/src/heapdump/DiffFields.java b/tools/ahat/src/heapdump/DiffFields.java
index dd73456..e3c671f 100644
--- a/tools/ahat/src/heapdump/DiffFields.java
+++ b/tools/ahat/src/heapdump/DiffFields.java
@@ -17,7 +17,6 @@
 package com.android.ahat.heapdump;
 
 import java.util.ArrayList;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -29,14 +28,19 @@
 public class DiffFields {
   /**
    * Return the result of diffing two collections of field values.
-   * The input collections 'current' and 'baseline' are not modified by this function.
    */
-  public static List<DiffedFieldValue> diff(Collection<FieldValue> current,
-                                            Collection<FieldValue> baseline) {
-    List<FieldValue> currentSorted = new ArrayList<FieldValue>(current);
+  public static List<DiffedFieldValue> diff(Iterable<FieldValue> current,
+                                            Iterable<FieldValue> baseline) {
+    List<FieldValue> currentSorted = new ArrayList<FieldValue>();
+    for (FieldValue field : current) {
+      currentSorted.add(field);
+    }
     Collections.sort(currentSorted, FOR_DIFF);
 
-    List<FieldValue> baselineSorted = new ArrayList<FieldValue>(baseline);
+    List<FieldValue> baselineSorted = new ArrayList<FieldValue>();
+    for (FieldValue field : baseline) {
+      baselineSorted.add(field);
+    }
     Collections.sort(baselineSorted, FOR_DIFF);
 
     // Merge the two lists to form the diffed list of fields.
diff --git a/tools/ahat/src/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/heapdump/DominatorReferenceIterator.java
index ce2e6ef..0b99e49 100644
--- a/tools/ahat/src/heapdump/DominatorReferenceIterator.java
+++ b/tools/ahat/src/heapdump/DominatorReferenceIterator.java
@@ -25,11 +25,11 @@
  */
 class DominatorReferenceIterator implements Iterator<AhatInstance>,
                                             Iterable<AhatInstance> {
-  private ReferenceIterator mIter;
+  private Iterator<Reference> mIter;
   private AhatInstance mNext;
 
-  public DominatorReferenceIterator(ReferenceIterator iter) {
-    mIter = iter;
+  public DominatorReferenceIterator(Iterable<Reference> iter) {
+    mIter = iter.iterator();
     mNext = null;
   }
 
diff --git a/tools/ahat/src/heapdump/Field.java b/tools/ahat/src/heapdump/Field.java
new file mode 100644
index 0000000..01f87c7
--- /dev/null
+++ b/tools/ahat/src/heapdump/Field.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * 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 com.android.ahat.heapdump;
+
+public class Field {
+  public final String name;
+  public final String type;
+
+  public Field(String name, String type) {
+    this.name = name;
+    this.type = type;
+  }
+}
diff --git a/tools/ahat/src/heapdump/ReferenceIterator.java b/tools/ahat/src/heapdump/ReferenceIterator.java
deleted file mode 100644
index a707fb2..0000000
--- a/tools/ahat/src/heapdump/ReferenceIterator.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * 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 com.android.ahat.heapdump;
-
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
-
-class ReferenceIterator implements Iterator<Reference>,
-                                   Iterable<Reference> {
-  private List<Reference> mRefs;
-  private int mLength;
-  private int mNextIndex;
-  private Reference mNext;
-
-  /**
-   * Construct a ReferenceIterator that iterators over the given list of
-   * references. Elements of the given list of references may be null, in
-   * which case the ReferenceIterator will skip over them.
-   */
-  public ReferenceIterator(List<Reference> refs) {
-    mRefs = refs;
-    mLength = refs.size();
-    mNextIndex = 0;
-    mNext = null;
-  }
-
-  @Override
-  public boolean hasNext() {
-    while (mNext == null && mNextIndex < mLength) {
-      mNext = mRefs.get(mNextIndex);
-      mNextIndex++;
-    }
-    return mNext != null;
-  }
-
-  @Override
-  public Reference next() {
-    if (!hasNext()) {
-      throw new NoSuchElementException();
-    }
-    Reference next = mNext;
-    mNext = null;
-    return next;
-  }
-
-  @Override
-  public Iterator<Reference> iterator() {
-    return this;
-  }
-}
diff --git a/tools/ahat/src/heapdump/SkipNullsIterator.java b/tools/ahat/src/heapdump/SkipNullsIterator.java
new file mode 100644
index 0000000..e99fe5e
--- /dev/null
+++ b/tools/ahat/src/heapdump/SkipNullsIterator.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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 com.android.ahat.heapdump;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * An iterator that skips over nulls.
+ */
+class SkipNullsIterator<T> implements Iterator<T>, Iterable<T> {
+  Iterator<T> mIter;
+  private T mNext;
+
+  public SkipNullsIterator(Iterable<T> iterable) {
+    mIter = iterable.iterator();
+    mNext = null;
+  }
+
+  @Override
+  public boolean hasNext() {
+    while (mNext == null && mIter.hasNext()) {
+      mNext = mIter.next();
+    }
+    return mNext != null;
+  }
+
+  @Override
+  public T next() {
+    if (!hasNext()) {
+      throw new NoSuchElementException();
+    }
+    T next = mNext;
+    mNext = null;
+    return next;
+  }
+
+  @Override
+  public Iterator<T> iterator() {
+    return this;
+  }
+}
diff --git a/tools/ahat/src/heapdump/SuperRoot.java b/tools/ahat/src/heapdump/SuperRoot.java
index 54410cf..d377113 100644
--- a/tools/ahat/src/heapdump/SuperRoot.java
+++ b/tools/ahat/src/heapdump/SuperRoot.java
@@ -39,8 +39,8 @@
   }
 
   @Override
-  ReferenceIterator getReferences() {
-    List<Reference> refs = new AbstractList<Reference>() {
+  Iterable<Reference> getReferences() {
+    return new AbstractList<Reference>() {
       @Override
       public int size() {
         return mRoots.size();
@@ -52,6 +52,5 @@
         return new Reference(null, field, mRoots.get(index), true);
       }
     };
-    return new ReferenceIterator(refs);
   }
 }
diff --git a/tools/ahat/src/heapdump/Value.java b/tools/ahat/src/heapdump/Value.java
index 38a6815..7f86c01 100644
--- a/tools/ahat/src/heapdump/Value.java
+++ b/tools/ahat/src/heapdump/Value.java
@@ -52,7 +52,8 @@
     } else if (object instanceof Long) {
       return Value.pack(((Long)object).longValue());
     }
-    throw new IllegalArgumentException("AhatInstance or primitive type required");
+    throw new IllegalArgumentException(
+        "AhatInstance or primitive type required, but got: " + object.toString());
   }
 
   public static Value pack(boolean value) {