Annotate $classOverhead arrays.

ART adds a fake byte[] $classOverhead static field to classes to show
the overheads associated with the class. This change causes ahat to
annotate those $classOverhead arrays with the class that they are
overheads for.

Test: m ahat-test, with new test added.
Test: Open an Android heap dump and visually inspect:
      http://localhost:7100/objects?id=0&heap=app&class=byte[]
Bug: 64832332

Change-Id: Ief6ed7ce6c8c1196bc644df36f03c8e5158bf658
diff --git a/tools/ahat/etc/ahat_api.txt b/tools/ahat/etc/ahat_api.txt
index 6fc62e7..84266d9 100644
--- a/tools/ahat/etc/ahat_api.txt
+++ b/tools/ahat/etc/ahat_api.txt
@@ -58,6 +58,7 @@
     method public java.lang.String asString(int);
     method public java.lang.String asString();
     method public com.android.ahat.heapdump.AhatInstance getAssociatedBitmapInstance();
+    method public com.android.ahat.heapdump.AhatClassObj getAssociatedClassForOverhead();
     method public com.android.ahat.heapdump.AhatInstance getBaseline();
     method public java.lang.String getClassName();
     method public com.android.ahat.heapdump.AhatClassObj getClassObj();
diff --git a/tools/ahat/src/main/com/android/ahat/Summarizer.java b/tools/ahat/src/main/com/android/ahat/Summarizer.java
index ae0776a..127ff37 100644
--- a/tools/ahat/src/main/com/android/ahat/Summarizer.java
+++ b/tools/ahat/src/main/com/android/ahat/Summarizer.java
@@ -16,6 +16,7 @@
 
 package com.android.ahat;
 
+import com.android.ahat.heapdump.AhatClassObj;
 import com.android.ahat.heapdump.AhatInstance;
 import com.android.ahat.heapdump.Site;
 import com.android.ahat.heapdump.Value;
@@ -100,11 +101,17 @@
 
     // Annotate bitmaps with a thumbnail.
     AhatInstance bitmap = inst.getAssociatedBitmapInstance();
-    String thumbnail = "";
     if (bitmap != null) {
       URI uri = DocString.formattedUri("bitmap?id=0x%x", bitmap.getId());
       formatted.appendThumbnail(uri, "bitmap image");
     }
+
+    // Annotate $classOverhead arrays
+    AhatClassObj cls = inst.getAssociatedClassForOverhead();
+    if (cls != null) {
+      formatted.append(" overhead for ");
+      formatted.append(summarize(cls));
+    }
     return formatted;
   }
 
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..c574e98 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatArrayInstance.java
@@ -333,6 +333,26 @@
     return null;
   }
 
+  @Override public AhatClassObj getAssociatedClassForOverhead() {
+    if (mByteArray != null) {
+      List<AhatInstance> refs = getHardReverseReferences();
+      if (refs.size() == 1) {
+        AhatClassObj ref = refs.get(0).asClassObj();
+        if (ref != null) {
+          for (FieldValue field : ref.getStaticFieldValues()) {
+            if (field.name.equals("$classOverhead")) {
+              if (field.value.asAhatInstance() == this) {
+                return ref;
+              }
+              return null;
+            }
+          }
+        }
+      }
+    }
+    return null;
+  }
+
   @Override public String toString() {
     String className = getClassName();
     if (className.endsWith("[]")) {
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..a91da82 100644
--- a/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
+++ b/tools/ahat/src/main/com/android/ahat/heapdump/AhatInstance.java
@@ -453,6 +453,18 @@
   }
 
   /**
+   * Returns the class object that this object represents the overhead for.
+   * ART adds a fake byte[] $classOverhead static field to classes to show the
+   * overheads associated with the class. If this is one such byte[] instance,
+   * returns the class it is associated with. Otherwise null is returned.
+   *
+   * @return the class instance that this is the overhead for
+   */
+  public AhatClassObj getAssociatedClassForOverhead() {
+    return null;
+  }
+
+  /**
    * Returns the (bounded-length) string associated with this instance.
    * Applies to instances of java.lang.String, char[], and in some cases
    * byte[]. Returns null if this object cannot be interpreted as a string.
diff --git a/tools/ahat/src/test/com/android/ahat/InstanceTest.java b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
index 8fbb884..65a3fb8 100644
--- a/tools/ahat/src/test/com/android/ahat/InstanceTest.java
+++ b/tools/ahat/src/test/com/android/ahat/InstanceTest.java
@@ -481,4 +481,19 @@
     assertEquals("java.lang.String", str.getClassName());
     assertNull(str.asString());
   }
+
+  @Test
+  public void classOverhead() throws IOException {
+    TestDump dump = TestDump.getTestDump("O.hprof", null, null);
+    AhatSnapshot snapshot = dump.getAhatSnapshot();
+
+    // class libore.io.IoTracker has byte[124]@12c028d1 as its class overhead.
+    AhatInstance overhead = snapshot.findInstance(0x12c028d1);
+    AhatClassObj cls = overhead.getAssociatedClassForOverhead();
+    assertEquals(0x12c028d0, cls.getId());
+    assertEquals("libcore.io.IoTracker", cls.getName());
+
+    // Other kinds of objects should not have associated classes for overhead.
+    assertNull(cls.getAssociatedClassForOverhead());
+  }
 }