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) {