Distinguish between soft/weak/phantom/etc references in ahat.

Instead of grouping all the different kinds of references into a single
category of "weak".

Bug: 79131879
Test: m ahat-test
Change-Id: I800f70b7f10386510e20d9d7b8a76069ddd38982
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index 6fc62e7..95da10f 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -65,19 +65,21 @@
     method public java.util.List<com.android.ahat.heapdump.AhatInstance> getDominated();
     method public java.lang.Object getDominatorsComputationState();
     method public com.android.ahat.heapdump.Value getField(java.lang.String);
-    method public java.util.List<com.android.ahat.heapdump.AhatInstance> getHardReverseReferences();
+    method public deprecated java.util.List<com.android.ahat.heapdump.AhatInstance> getHardReverseReferences();
     method public com.android.ahat.heapdump.AhatHeap getHeap();
     method public long getId();
     method public com.android.ahat.heapdump.AhatInstance getImmediateDominator();
     method public java.util.List<com.android.ahat.heapdump.PathElement> getPathFromGcRoot();
+    method public com.android.ahat.heapdump.Reachability getReachability();
     method public com.android.ahat.heapdump.AhatInstance getRefField(java.lang.String);
     method public java.lang.Iterable<? extends com.android.ahat.dominators.DominatorsComputation.Node> getReferencesForDominators();
     method public com.android.ahat.heapdump.AhatInstance getReferent();
     method public com.android.ahat.heapdump.Size getRetainedSize(com.android.ahat.heapdump.AhatHeap);
+    method public java.util.List<com.android.ahat.heapdump.AhatInstance> getReverseReferences();
     method public java.util.Collection<com.android.ahat.heapdump.RootType> getRootTypes();
     method public com.android.ahat.heapdump.Site getSite();
     method public com.android.ahat.heapdump.Size getSize();
-    method public java.util.List<com.android.ahat.heapdump.AhatInstance> getSoftReverseReferences();
+    method public deprecated java.util.List<com.android.ahat.heapdump.AhatInstance> getSoftReverseReferences();
     method public com.android.ahat.heapdump.Size getTotalRetainedSize();
     method public boolean isArrayInstance();
     method public boolean isClassInstance();
@@ -86,7 +88,7 @@
     method public boolean isRoot();
     method public boolean isStronglyReachable();
     method public boolean isUnreachable();
-    method public boolean isWeaklyReachable();
+    method public deprecated boolean isWeaklyReachable();
     method public void setDominator(com.android.ahat.dominators.DominatorsComputation.Node);
     method public void setDominatorsComputationState(java.lang.Object);
     method public abstract java.lang.String toString();
@@ -173,6 +175,17 @@
     field public boolean isDominator;
   }
 
+  public final class Reachability extends java.lang.Enum {
+    method public static com.android.ahat.heapdump.Reachability valueOf(java.lang.String);
+    method public static final com.android.ahat.heapdump.Reachability[] values();
+    enum_constant public static final com.android.ahat.heapdump.Reachability FINALIZER;
+    enum_constant public static final com.android.ahat.heapdump.Reachability PHANTOM;
+    enum_constant public static final com.android.ahat.heapdump.Reachability SOFT;
+    enum_constant public static final com.android.ahat.heapdump.Reachability STRONG;
+    enum_constant public static final com.android.ahat.heapdump.Reachability UNREACHABLE;
+    enum_constant public static final com.android.ahat.heapdump.Reachability WEAK;
+  }
+
   public final class RootType extends java.lang.Enum {
     method public static com.android.ahat.heapdump.RootType valueOf(java.lang.String);
     method public static final com.android.ahat.heapdump.RootType[] values();
diff --git a/tools/ahat/src/main/com/android/ahat/ObjectHandler.java b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
index bfd5d5c..c099da8 100644
--- a/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
+++ b/tools/ahat/src/main/com/android/ahat/ObjectHandler.java
@@ -44,8 +44,7 @@
   private static final String DOMINATED_OBJECTS_ID = "dominated";
   private static final String INSTANCE_FIELDS_ID = "ifields";
   private static final String STATIC_FIELDS_ID = "sfields";
-  private static final String HARD_REFS_ID = "refs";
-  private static final String SOFT_REFS_ID = "srefs";
+  private static final String REFS_ID = "refs";
 
   private AhatSnapshot mSnapshot;
 
@@ -223,24 +222,12 @@
 
   private static void printReferences(Doc doc, Query query, AhatInstance inst) {
     doc.section("Objects with References to this Object");
-    if (inst.getHardReverseReferences().isEmpty()) {
+    if (inst.getReverseReferences().isEmpty()) {
       doc.println(DocString.text("(none)"));
     } else {
       doc.table(new Column("Object"));
-      List<AhatInstance> references = inst.getHardReverseReferences();
-      SubsetSelector<AhatInstance> selector = new SubsetSelector(query, HARD_REFS_ID, references);
-      for (AhatInstance ref : selector.selected()) {
-        doc.row(Summarizer.summarize(ref));
-      }
-      doc.end();
-      selector.render(doc);
-    }
-
-    if (!inst.getSoftReverseReferences().isEmpty()) {
-      doc.section("Objects with Soft References to this Object");
-      doc.table(new Column("Object"));
-      List<AhatInstance> references = inst.getSoftReverseReferences();
-      SubsetSelector<AhatInstance> selector = new SubsetSelector(query, SOFT_REFS_ID, references);
+      List<AhatInstance> references = inst.getReverseReferences();
+      SubsetSelector<AhatInstance> selector = new SubsetSelector(query, REFS_ID, references);
       for (AhatInstance ref : selector.selected()) {
         doc.row(Summarizer.summarize(ref));
       }
diff --git a/tools/ahat/src/main/com/android/ahat/Summarizer.java b/tools/ahat/src/main/com/android/ahat/Summarizer.java
index ae0776a..6fc399d 100644
--- a/tools/ahat/src/main/com/android/ahat/Summarizer.java
+++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java
@@ -17,6 +17,7 @@
 package com.android.ahat;
 
 import com.android.ahat.heapdump.AhatInstance;
+import com.android.ahat.heapdump.Reachability;
 import com.android.ahat.heapdump.Site;
 import com.android.ahat.heapdump.Value;
 import java.net.URI;
@@ -50,11 +51,10 @@
       formatted.append(DocString.removed("del "));
     }
 
-    // Annotate unreachable objects as such.
-    if (inst.isWeaklyReachable()) {
-      formatted.append("weak ");
-    } else if (inst.isUnreachable()) {
-      formatted.append("unreachable ");
+    // Annotate non-strongly reachable objects as such.
+    Reachability reachability = inst.getReachability();
+    if (reachability != Reachability.STRONG) {
+      formatted.append(reachability.toString() + " ");
     }
 
     // Annotate roots as roots.
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
index 9c80802..cf6cf93 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
@@ -240,7 +240,10 @@
             if (value != null) {
               assert value.isAhatInstance();
               String field = "[" + Integer.toString(index) + "]";
-              return new Reference(AhatArrayInstance.this, field, value.asAhatInstance(), true);
+              return new Reference(AhatArrayInstance.this,
+                                   field,
+                                   value.asAhatInstance(),
+                                   Reachability.STRONG);
             }
             return null;
           }
@@ -324,7 +327,7 @@
 
   @Override public AhatInstance getAssociatedBitmapInstance() {
     if (mByteArray != null) {
-      List<AhatInstance> refs = getHardReverseReferences();
+      List<AhatInstance> refs = getReverseReferences();
       if (refs.size() == 1) {
         AhatInstance ref = refs.get(0);
         return ref.getAssociatedBitmapInstance();
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
index c82ef20..f377ae3 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassInstance.java
@@ -104,10 +104,7 @@
 
   @Override
   Iterable<Reference> getReferences() {
-    if (isInstanceOfClass("java.lang.ref.Reference")) {
-      return new WeakReferentReferenceIterator();
-    }
-    return new StrongReferenceIterator();
+    return new ReferenceIterator();
   }
 
   /**
@@ -352,59 +349,48 @@
   }
 
   /**
-   * A Reference iterator that iterates over the fields of this instance
-   * assuming all field references are strong references.
+   * Returns the reachability type associated with this instance.
+   * For example, returns Reachability.WEAK for an instance of
+   * java.lang.ref.WeakReference.
    */
-  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);
-        }
+  private Reachability getJavaLangRefType() {
+    AhatClassObj cls = getClassObj();
+    while (cls != null) {
+      switch (cls.getName()) {
+        case "java.lang.ref.PhantomReference": return Reachability.PHANTOM;
+        case "java.lang.ref.WeakReference": return Reachability.WEAK;
+        case "java.lang.ref.FinalizerReference": return Reachability.FINALIZER;
+        case "java.lang.ref.SoftReference": return Reachability.SOFT;
       }
-      return mNext != null;
+      cls = cls.getSuperClassObj();
     }
-
-    @Override
-    public Reference next() {
-      if (!hasNext()) {
-        throw new NoSuchElementException();
-      }
-      Reference next = mNext;
-      mNext = null;
-      return next;
-    }
-
-    @Override
-    public Iterator<Reference> iterator() {
-      return this;
-    }
+    return Reachability.STRONG;
   }
 
   /**
-   * A Reference iterator that iterates over the fields of a subclass of
-   * java.lang.ref.Reference, where the 'referent' field is considered weak.
+   * A Reference iterator that iterates over the fields of this instance.
    */
-  private class WeakReferentReferenceIterator implements Iterable<Reference>,
-                                                         Iterator<Reference> {
-    private Iterator<FieldValue> mIter = getInstanceFields().iterator();
+  private class ReferenceIterator implements Iterable<Reference>,
+                                             Iterator<Reference> {
+    private final Iterator<FieldValue> mIter = getInstanceFields().iterator();
     private Reference mNext = null;
 
+    // If we are iterating over a subclass of java.lang.ref.Reference, the
+    // 'referent' field doesn't have strong reachability. mJavaLangRefType
+    // describes what type of java.lang.ref.Reference subinstance this is.
+    private final Reachability mJavaLangRefType = getJavaLangRefType();
+
     @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");
+          Reachability reachability = Reachability.STRONG;
+          if (mJavaLangRefType != Reachability.STRONG && "referent".equals(field.name)) {
+            reachability = mJavaLangRefType;
+          }
           AhatInstance ref = field.value.asAhatInstance();
-          mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, strong);
+          mNext = new Reference(AhatClassInstance.this, "." + field.name, ref, reachability);
         }
       }
       return mNext != null;
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
index 36ada28..765a411 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatClassObj.java
@@ -131,7 +131,10 @@
         FieldValue field = mStaticFieldValues[index];
         Value value = field.value;
         if (value != null && value.isAhatInstance()) {
-          return new Reference(AhatClassObj.this, "." + field.name, value.asAhatInstance(), true);
+          return new Reference(AhatClassObj.this,
+                               "." + field.name,
+                               value.asAhatInstance(),
+                               Reachability.STRONG);
         }
         return null;
       }
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
index 95553a2..37ef6de 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -24,6 +24,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Deque;
+import java.util.EnumMap;
 import java.util.List;
 import java.util.Queue;
 
@@ -48,11 +49,11 @@
   // Field initialized via addRegisterednativeSize.
   private long mRegisteredNativeSize = 0;
 
-  // Fields initialized in computeReverseReferences().
+  // Fields initialized in computeReachability().
+  private Reachability mReachability = Reachability.UNREACHABLE;
   private AhatInstance mNextInstanceToGcRoot;
   private String mNextInstanceToGcRootField;
-  private ArrayList<AhatInstance> mHardReverseReferences;
-  private ArrayList<AhatInstance> mSoftReverseReferences;
+  private ArrayList<AhatInstance> mReverseReferences;
 
   // Fields initialized in DominatorsComputation.computeDominators().
   // mDominated - the list of instances immediately dominated by this instance.
@@ -157,6 +158,15 @@
   }
 
   /**
+   * Returns the reachability of the instance.
+   *
+   * @return the reachability of the instance.
+   */
+  public Reachability getReachability() {
+    return mReachability;
+  }
+
+  /**
    * Returns true if this object is strongly reachable. An object is strongly
    * reachable if there exists a path of (strong) references from some root
    * object to this object.
@@ -164,7 +174,7 @@
    * @return true if the object is strongly reachable
    */
   public boolean isStronglyReachable() {
-    return mImmediateDominator != null;
+    return mReachability == Reachability.STRONG;
   }
 
   /**
@@ -178,10 +188,13 @@
    * Unlike a strongly reachable object, a weakly reachable object is allowed
    * to be garbage collected.
    *
+   * @deprecated Use {@link #getReachability()} instead, which can distinguish
+   *             among soft, weak, phantom, and other kinds of references.
+   *
    * @return true if the object is weakly reachable
    */
-  public boolean isWeaklyReachable() {
-    return !isStronglyReachable() && mNextInstanceToGcRoot != null;
+  @Deprecated public boolean isWeaklyReachable() {
+    return !isStronglyReachable() && !isUnreachable();
   }
 
   /**
@@ -193,7 +206,7 @@
    * @return true if the object is completely unreachable
    */
   public boolean isUnreachable() {
-    return !isStronglyReachable() && !isWeaklyReachable();
+    return mReachability == Reachability.UNREACHABLE;
   }
 
   /**
@@ -215,7 +228,6 @@
    * Returns true if this instance is a GC root.
    *
    * @return true if this instance is a GC root.
-   * @see getRootTypes
    */
   public boolean isRoot() {
     return mRootTypes != 0;
@@ -374,28 +386,50 @@
   }
 
   /**
-   * Returns a list of objects with (strong) references to this object.
+   * Returns a list of objects with any kind of reference to this object.
    *
    * @return the objects referencing this object
    */
-  public List<AhatInstance> getHardReverseReferences() {
-    if (mHardReverseReferences != null) {
-      return mHardReverseReferences;
+  public List<AhatInstance> getReverseReferences() {
+    if (mReverseReferences != null) {
+      return mReverseReferences;
     }
     return Collections.emptyList();
   }
 
   /**
+   * Returns a list of objects with (strong) references to this object.
+   *
+   * @deprecated Use {@link #getReverseReferences()} instead.
+   *
+   * @return the objects referencing this object
+   */
+  @Deprecated public List<AhatInstance> getHardReverseReferences() {
+    List<AhatInstance> refs = new ArrayList<AhatInstance>();
+    for (AhatInstance ref : getReverseReferences()) {
+      if (ref.getReachability() == Reachability.STRONG && ref.getReferent() != this) {
+        refs.add(ref);
+      }
+    }
+    return refs;
+  }
+
+  /**
    * Returns a list of objects with soft/weak/phantom/finalizer references to
    * this object.
    *
+   * @deprecated Use {@link #getReverseReferences()} instead.
+   *
    * @return the objects weakly referencing this object
    */
-  public List<AhatInstance> getSoftReverseReferences() {
-    if (mSoftReverseReferences != null) {
-      return mSoftReverseReferences;
+  @Deprecated public List<AhatInstance> getSoftReverseReferences() {
+    List<AhatInstance> refs = new ArrayList<AhatInstance>();
+    for (AhatInstance ref : getReverseReferences()) {
+      if (ref.getReachability() != Reachability.STRONG || ref.getReferent() == this) {
+        refs.add(ref);
+      }
     }
-    return Collections.emptyList();
+    return refs;
   }
 
   /**
@@ -598,82 +632,60 @@
   }
 
   /**
-   * Initialize the reverse reference fields of this instance and all other
-   * instances reachable from it. Initializes the following fields:
+   * Determine the reachability of the all instances reachable from the given
+   * root instance. Initializes the following fields:
+   *   mReachability
    *   mNextInstanceToGcRoot
    *   mNextInstanceToGcRootField
-   *   mHardReverseReferences
-   *   mSoftReverseReferences
+   *   mReverseReferences
    *
    * @param progress used to track progress of the traversal.
    * @param numInsts upper bound on the total number of instances reachable
    *                 from the root, solely used for the purposes of tracking
    *                 progress.
    */
-  static void computeReverseReferences(SuperRoot root, Progress progress, long numInsts) {
+  static void computeReachability(SuperRoot root, Progress progress, long numInsts) {
     // Start by doing a breadth first search through strong references.
-    // Then continue the breadth first search through weak references.
-    progress.start("Reversing references", numInsts);
-    Queue<Reference> strong = new ArrayDeque<Reference>();
-    Queue<Reference> weak = new ArrayDeque<Reference>();
+    // Then continue the breadth first through each weaker kind of reference.
+    progress.start("Computing reachability", numInsts);
+    EnumMap<Reachability, Queue<Reference>> queues = new EnumMap<>(Reachability.class);
+    for (Reachability reachability : Reachability.values()) {
+      queues.put(reachability, new ArrayDeque<Reference>());
+    }
 
     for (Reference ref : root.getReferences()) {
-      strong.add(ref);
+      queues.get(Reachability.STRONG).add(ref);
     }
 
-    while (!strong.isEmpty()) {
-      Reference ref = strong.poll();
-      assert ref.strong;
+    for (Reachability reachability : Reachability.values()) {
+      Queue<Reference> queue = queues.get(reachability);
+      while (!queue.isEmpty()) {
+        Reference ref = queue.poll();
+        if (ref.ref.mReachability == Reachability.UNREACHABLE) {
+          // This is the first time we have seen ref.ref.
+          progress.advance();
+          ref.ref.mReachability = reachability;
+          ref.ref.mNextInstanceToGcRoot = ref.src;
+          ref.ref.mNextInstanceToGcRootField = ref.field;
+          ref.ref.mReverseReferences = new ArrayList<AhatInstance>();
 
-      if (ref.ref.mNextInstanceToGcRoot == null) {
-        // This is the first time we have seen ref.ref.
-        progress.advance();
-        ref.ref.mNextInstanceToGcRoot = ref.src;
-        ref.ref.mNextInstanceToGcRootField = ref.field;
-        ref.ref.mHardReverseReferences = new ArrayList<AhatInstance>();
-
-        for (Reference childRef : ref.ref.getReferences()) {
-          if (childRef.strong) {
-            strong.add(childRef);
-          } else {
-            weak.add(childRef);
+          for (Reference childRef : ref.ref.getReferences()) {
+            if (childRef.reachability.ordinal() <= reachability.ordinal()) {
+              queue.add(childRef);
+            } else {
+              queues.get(childRef.reachability).add(childRef);
+            }
           }
         }
-      }
 
-      // Note: We specifically exclude 'root' from the reverse references
-      // because it is a fake SuperRoot instance not present in the original
-      // heap dump.
-      if (ref.src != root) {
-        ref.ref.mHardReverseReferences.add(ref.src);
-      }
-    }
-
-    while (!weak.isEmpty()) {
-      Reference ref = weak.poll();
-
-      if (ref.ref.mNextInstanceToGcRoot == null) {
-        // This is the first time we have seen ref.ref.
-        progress.advance();
-        ref.ref.mNextInstanceToGcRoot = ref.src;
-        ref.ref.mNextInstanceToGcRootField = ref.field;
-        ref.ref.mHardReverseReferences = new ArrayList<AhatInstance>();
-
-        for (Reference childRef : ref.ref.getReferences()) {
-          weak.add(childRef);
+        // Note: We specifically exclude 'root' from the reverse references
+        // because it is a fake SuperRoot instance not present in the original
+        // heap dump.
+        if (ref.src != root) {
+          ref.ref.mReverseReferences.add(ref.src);
         }
       }
-
-      if (ref.strong) {
-        ref.ref.mHardReverseReferences.add(ref.src);
-      } else {
-        if (ref.ref.mSoftReverseReferences == null) {
-          ref.ref.mSoftReverseReferences = new ArrayList<AhatInstance>();
-        }
-        ref.ref.mSoftReverseReferences.add(ref.src);
-      }
     }
-
     progress.done();
   }
 
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
index bc94047..d9c7a19 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatSnapshot.java
@@ -55,7 +55,7 @@
       }
     }
 
-    AhatInstance.computeReverseReferences(mSuperRoot, progress, mInstances.size());
+    AhatInstance.computeReachability(mSuperRoot, progress, mInstances.size());
     DominatorsComputation.computeDominators(mSuperRoot, progress, mInstances.size());
     AhatInstance.computeRetainedSize(mSuperRoot, mHeaps.size());
 
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java b/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java
index 0b99e49..8c8de23 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/DominatorReferenceIterator.java
@@ -37,7 +37,7 @@
   public boolean hasNext() {
     while (mNext == null && mIter.hasNext()) {
       Reference ref = mIter.next();
-      if (ref.strong) {
+      if (ref.reachability == Reachability.STRONG) {
         mNext = ref.ref;
       }
     }
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Reachability.java b/tools/ahat/src/main/com/android/ahat/heapdump/Reachability.java
new file mode 100644
index 0000000..8df6c8c
--- /dev/null
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Reachability.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2018 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;
+
+/**
+ * Enum corresponding to the reachability of an instance.
+ * See {@link java.lang.ref} for a specification of the various kinds of
+ * reachibility. The enum constants are specified in decreasing order of
+ * strength.
+ */
+public enum Reachability {
+  /**
+   * The instance is strongly reachable.
+   */
+  STRONG("strong"),
+
+  /**
+   * The instance is softly reachable.
+   */
+  SOFT("soft"),
+
+  /**
+   * The instance is finalizer reachable, but is neither strongly nor softly
+   * reachable.
+   */
+  FINALIZER("finalizer"),
+
+  /**
+   * The instance is weakly reachable.
+   */
+  WEAK("weak"),
+
+  /**
+   * The instance is phantom reachable.
+   */
+  PHANTOM("phantom"),
+
+  /**
+   * The instance is unreachable.
+   */
+  UNREACHABLE("unreachable");
+
+  /**
+   * The name of the reachibility.
+   */
+  private final String name;
+
+  Reachability(String name) {
+    this.name = name;
+  }
+
+  @Override
+  public String toString() {
+    return name;
+  }
+}
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java b/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java
index f1340bd..2de76fd 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/Reference.java
@@ -20,19 +20,18 @@
  * Reference represents a reference from 'src' to 'ref' through 'field'.
  * Field is a string description for human consumption. This is typically
  * either "." followed by the field name or an array subscript such as "[4]".
- * 'strong' is true if this is a strong reference, false if it is a
- * weak/soft/other reference.
+ * reachability describes whether the reference is strong/soft/weak/etc.
  */
 class Reference {
   public final AhatInstance src;
   public final String field;
   public final AhatInstance ref;
-  public final boolean strong;
+  public final Reachability reachability;
 
-  public Reference(AhatInstance src, String field, AhatInstance ref, boolean strong) {
+  public Reference(AhatInstance src, String field, AhatInstance ref, Reachability reachability) {
     this.src = src;
     this.field = field;
     this.ref = ref;
-    this.strong = strong;
+    this.reachability = reachability;
   }
 }
diff --git a/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java b/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java
index b01cfff..d06df90 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/SuperRoot.java
@@ -54,7 +54,7 @@
       @Override
       public Reference get(int index) {
         String field = ".roots[" + Integer.toString(index) + "]";
-        return new Reference(SuperRoot.this, field, mRoots.get(index), true);
+        return new Reference(SuperRoot.this, field, mRoots.get(index), Reachability.STRONG);
       }
     };
   }
diff --git a/tools/ahat/src/test-dump/DumpedStuff.java b/tools/ahat/src/test-dump/DumpedStuff.java
index 98ead07..804a3a3 100644
--- a/tools/ahat/src/test-dump/DumpedStuff.java
+++ b/tools/ahat/src/test-dump/DumpedStuff.java
@@ -136,6 +136,7 @@
   public WeakReference aWeakReference = new WeakReference(anObject, referenceQueue);
   public WeakReference aNullReferentReference = new WeakReference(null, referenceQueue);
   public SoftReference aSoftReference = new SoftReference(new Object());
+  public Reference reachabilityReferenceChain;
   public byte[] bigArray;
   public ObjectTree[] gcPathArray = new ObjectTree[]{null, null,
     new ObjectTree(
@@ -145,7 +146,7 @@
   public Reference aLongStrongPathToSamplePathObject;
   public WeakReference aShortWeakPathToSamplePathObject;
   public WeakReference aWeakRefToGcRoot = new WeakReference(Main.class);
-  public SoftReference aWeakChain = new SoftReference(new Reference(new Reference(new Object())));
+  public SoftReference aSoftChain = new SoftReference(new Reference(new Reference(new Object())));
   public Object[] basicStringRef;
   public AddedObject addedObject;
   public UnchangedObject unchangedObject = new UnchangedObject();
@@ -157,4 +158,15 @@
   public int[] modifiedArray;
   public Object objectAllocatedAtKnownSite;
   public Object objectAllocatedAtKnownSubSite;
+
+  // Allocate those objects that we need to not be GC'd before taking the heap
+  // dump.
+  public void shouldNotGc() {
+    reachabilityReferenceChain = new Reference(
+        new SoftReference(
+        new Reference(
+        new WeakReference(
+        new SoftReference(
+        new PhantomReference(new Object(), referenceQueue))))));
+  }
 }
diff --git a/tools/ahat/src/test-dump/Main.java b/tools/ahat/src/test-dump/Main.java
index de36748..ca18fd8 100644
--- a/tools/ahat/src/test-dump/Main.java
+++ b/tools/ahat/src/test-dump/Main.java
@@ -49,6 +49,8 @@
       stuff.basicStringRef = new Object[]{stuff.basicString};
     }
 
+    stuff.shouldNotGc();
+
     // Take a heap dump that will include that instance of DumpedStuff.
     System.err.println("Dumping hprof data to " + file);
     VMDebug.dumpHprofData(file);
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 8fbb884..9d33211 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -21,6 +21,7 @@
 import com.android.ahat.heapdump.AhatInstance;
 import com.android.ahat.heapdump.AhatSnapshot;
 import com.android.ahat.heapdump.PathElement;
+import com.android.ahat.heapdump.Reachability;
 import com.android.ahat.heapdump.Size;
 import com.android.ahat.heapdump.Value;
 import java.io.IOException;
@@ -216,10 +217,31 @@
     AhatInstance ref = dump.getDumpedAhatInstance("aSoftReference");
     AhatInstance referent = ref.getReferent();
     assertNotNull(referent);
+    assertEquals(Reachability.SOFT, referent.getReachability());
     assertTrue(referent.isWeaklyReachable());
   }
 
   @Test
+  public void reachability() throws IOException {
+    TestDump dump = TestDump.getTestDump();
+    AhatInstance strong1 = dump.getDumpedAhatInstance("reachabilityReferenceChain");
+    AhatInstance soft1 = strong1.getField("referent").asAhatInstance();
+    AhatInstance strong2 = soft1.getField("referent").asAhatInstance();
+    AhatInstance weak1 = strong2.getField("referent").asAhatInstance();
+    AhatInstance soft2 = weak1.getField("referent").asAhatInstance();
+    AhatInstance phantom1 = soft2.getField("referent").asAhatInstance();
+    AhatInstance obj = phantom1.getField("referent").asAhatInstance();
+
+    assertEquals(Reachability.STRONG, strong1.getReachability());
+    assertEquals(Reachability.STRONG, soft1.getReachability());
+    assertEquals(Reachability.SOFT, strong2.getReachability());
+    assertEquals(Reachability.SOFT, weak1.getReachability());
+    assertEquals(Reachability.WEAK, soft2.getReachability());
+    assertEquals(Reachability.WEAK, phantom1.getReachability());
+    assertEquals(Reachability.PHANTOM, obj.getReachability());
+  }
+
+  @Test
   public void gcRootPath() throws IOException {
     TestDump dump = TestDump.getTestDump();
 
@@ -388,24 +410,31 @@
 
     // We had a bug in the past where weak references to GC roots caused the
     // roots to be incorrectly be considered weakly reachable.
+    assertEquals(Reachability.STRONG, root.getReachability());
     assertTrue(root.isStronglyReachable());
     assertFalse(root.isWeaklyReachable());
   }
 
   @Test
-  public void weakReferenceChain() throws IOException {
+  public void softReferenceChain() throws IOException {
     // If the only reference to a chain of strongly referenced objects is a
-    // weak reference, then all of the objects should be considered weakly
+    // soft reference, then all of the objects should be considered softly
     // reachable.
     TestDump dump = TestDump.getTestDump();
-    AhatInstance ref = dump.getDumpedAhatInstance("aWeakChain");
-    AhatInstance weak1 = ref.getField("referent").asAhatInstance();
-    AhatInstance weak2 = weak1.getField("referent").asAhatInstance();
-    AhatInstance weak3 = weak2.getField("referent").asAhatInstance();
+    AhatInstance ref = dump.getDumpedAhatInstance("aSoftChain");
+    AhatInstance soft1 = ref.getField("referent").asAhatInstance();
+    AhatInstance soft2 = soft1.getField("referent").asAhatInstance();
+    AhatInstance soft3 = soft2.getField("referent").asAhatInstance();
     assertTrue(ref.isStronglyReachable());
-    assertTrue(weak1.isWeaklyReachable());
-    assertTrue(weak2.isWeaklyReachable());
-    assertTrue(weak3.isWeaklyReachable());
+    assertEquals(Reachability.SOFT, soft1.getReachability());
+    assertEquals(Reachability.SOFT, soft2.getReachability());
+    assertEquals(Reachability.SOFT, soft3.getReachability());
+
+    // Test the deprecated isWeaklyReachable API, which interprets weak as any
+    // kind of phantom/finalizer/weak/soft reference.
+    assertTrue(soft1.isWeaklyReachable());
+    assertTrue(soft2.isWeaklyReachable());
+    assertTrue(soft3.isWeaklyReachable());
   }
 
   @Test
@@ -414,6 +443,8 @@
     AhatInstance obj = dump.getDumpedAhatInstance("anObject");
     AhatInstance ref = dump.getDumpedAhatInstance("aReference");
     AhatInstance weak = dump.getDumpedAhatInstance("aWeakReference");
+    assertTrue(obj.getReverseReferences().contains(ref));
+    assertTrue(obj.getReverseReferences().contains(weak));
     assertTrue(obj.getHardReverseReferences().contains(ref));
     assertFalse(obj.getHardReverseReferences().contains(weak));
     assertFalse(obj.getSoftReverseReferences().contains(ref));