ART: Improve Dbg::GetRecentAllocations

Move ClassTable to const char* instead of std::string, and let it
know when it needs to keep a copy of the string itself. This avoids
lots of temporary allocations.

Increase the stack depth in test 098 to better cover this behavior.
Add allocations of proxies and arrays to cover the backing storage.

Improvements on test 098:

Before:

heaptrack stats:
        allocations:            598174
        leaked allocations:     186
        temporary allocations:  132302

65797 calls to allocation functions with 3.34KB peak consumption from
art::StringTable::Add(char const*)
65666 calls to allocation functions with 144B peak consumption from
art::StringTable::IndexOf(char const*) const

After:

heaptrack stats:
        allocations:            466828
        leaked allocations:     186
        temporary allocations:  1002

(cherry picked from commit f774a4e5243b9b06f3c71ea9b67fc40b96a78342)

Bug: 37620770
Test: m test-art-host
Test: art/test/testrunner/testrunner.py -b --host -t 098
Test: SANITIZE_HOST=address art/test/testrunner/testrunner.py -b --host -t 098
Merged-In: Iee496c96471dbb825c22b2656598c95f7c029a2f
Change-Id: Iee496c96471dbb825c22b2656598c95f7c029a2f
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index cfa56a5..1a910c0 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -18,7 +18,9 @@
 
 #include <sys/uio.h>
 
+#include <memory>
 #include <set>
+#include <vector>
 
 #include "android-base/stringprintf.h"
 
@@ -4922,11 +4924,20 @@
   StringTable() {
   }
 
-  void Add(const std::string& str) {
-    table_.insert(str);
-  }
+  void Add(const char* str, bool copy_string) {
+    if (UNLIKELY(copy_string)) {
+      // Check whether it's already there.
+      if (table_.find(str) != table_.end()) {
+        return;
+      }
 
-  void Add(const char* str) {
+      // Make a copy.
+      size_t str_len = strlen(str);
+      char* copy = new char[str_len + 1];
+      strncpy(copy, str, str_len + 1);
+      string_backup_.emplace_back(copy);
+      str = copy;
+    }
     table_.insert(str);
   }
 
@@ -4943,17 +4954,23 @@
   }
 
   void WriteTo(std::vector<uint8_t>& bytes) const {
-    for (const std::string& str : table_) {
-      const char* s = str.c_str();
-      size_t s_len = CountModifiedUtf8Chars(s);
+    for (const char* str : table_) {
+      size_t s_len = CountModifiedUtf8Chars(str);
       std::unique_ptr<uint16_t[]> s_utf16(new uint16_t[s_len]);
-      ConvertModifiedUtf8ToUtf16(s_utf16.get(), s);
+      ConvertModifiedUtf8ToUtf16(s_utf16.get(), str);
       JDWP::AppendUtf16BE(bytes, s_utf16.get(), s_len);
     }
   }
 
  private:
-  std::set<std::string> table_;
+  struct ConstCharStarComparator {
+    bool operator()(const char *s1, const char *s2) const {
+      return strcmp(s1, s2) < 0;
+    }
+  };
+
+  std::set<const char*, ConstCharStarComparator> table_;
+  std::vector<std::unique_ptr<char[]>> string_backup_;
   DISALLOW_COPY_AND_ASSIGN(StringTable);
 };
 
@@ -5040,12 +5057,13 @@
          count > 0 && it != end; count--, it++) {
       const gc::AllocRecord* record = &it->second;
       std::string temp;
-      class_names.Add(record->GetClassDescriptor(&temp));
+      const char* class_descr = record->GetClassDescriptor(&temp);
+      class_names.Add(class_descr, !temp.empty());
       for (size_t i = 0, depth = record->GetDepth(); i < depth; i++) {
         ArtMethod* m = record->StackElement(i).GetMethod();
-        class_names.Add(m->GetDeclaringClassDescriptor());
-        method_names.Add(m->GetName());
-        filenames.Add(GetMethodSourceFile(m));
+        class_names.Add(m->GetDeclaringClassDescriptor(), false);
+        method_names.Add(m->GetName(), false);
+        filenames.Add(GetMethodSourceFile(m), false);
       }
     }
 
diff --git a/test/098-ddmc/src/Main.java b/test/098-ddmc/src/Main.java
index 72c5a28..e9a11d7 100644
--- a/test/098-ddmc/src/Main.java
+++ b/test/098-ddmc/src/Main.java
@@ -14,8 +14,11 @@
  * limitations under the License.
  */
 
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
 
 public class Main {
     public static void main(String[] args) throws Exception {
@@ -27,6 +30,8 @@
         testRecentAllocationTracking();
     }
 
+    private static ArrayList<Object> staticHolder = new ArrayList<>(100000);
+
     private static void testRecentAllocationTracking() throws Exception {
         System.out.println("Confirm empty");
         Allocations empty = new Allocations(DdmVmInternal.getRecentAllocations());
@@ -44,18 +49,15 @@
         System.out.println("Confirm when we overflow, we don't roll over to zero. b/17392248");
         final int overflowAllocations = 64 * 1024;  // Won't fit in unsigned 16-bit value.
         for (int i = 0; i < overflowAllocations; i++) {
-            new Object() {
-                // Add a finalizer so that the allocation won't be eliminated.
-                public void finalize() {
-                    System.out.print("");
-                }
-            };
+            allocate(i, 0);
         }
         Allocations after = new Allocations(DdmVmInternal.getRecentAllocations());
         System.out.println("before < overflowAllocations=" + (before.numberOfEntries < overflowAllocations));
         System.out.println("after > before=" + (after.numberOfEntries > before.numberOfEntries));
         System.out.println("after.numberOfEntries=" + after.numberOfEntries);
 
+        staticHolder.clear();  // Free the allocated objects.
+
         System.out.println("Disable and confirm back to empty");
         DdmVmInternal.enableRecentAllocations(false);
         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
@@ -72,7 +74,7 @@
         DdmVmInternal.enableRecentAllocations(true);
         System.out.println("status=" + DdmVmInternal.getRecentAllocationStatus());
         for (int i = 0; i < 16 * 1024; i++) {
-            new String("fnord");
+            staticHolder.add(new String("fnord"));
         }
         Allocations first = new Allocations(DdmVmInternal.getRecentAllocations());
         DdmVmInternal.enableRecentAllocations(true);
@@ -86,6 +88,50 @@
         System.out.println("goodbye=" + goodbye);
     }
 
+    // Allocate a simple object. Use depth for a reasonably deep stack.
+    private static final int ALLOCATE1_DEPTH = 50;
+
+    private static Object createProxy() {
+        try {
+            InvocationHandler handler = new InvocationHandler() {
+                public Object invoke(Object proxy, Method method, Object[] args) {
+                    // Don't expect to be invoked.
+                    return null;
+                }
+            };
+            return Proxy.newProxyInstance(Main.class.getClassLoader(),
+                    new Class[] { Runnable.class }, handler);
+        } catch (Exception e) {
+            // We don't really expect exceptions here.
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void allocate(int i, int depth) {
+        if (depth >= ALLOCATE1_DEPTH) {
+            // Mix proxies, int arrays and Objects to test the different descriptor paths.
+            switch (i) {
+                case 0:
+                    staticHolder.add(createProxy());
+                    break;
+
+                case 1:
+                    staticHolder.add(new int[0]);
+                    break;
+
+                case 2:
+                    staticHolder.add(new Object[0]);
+                    break;
+
+                default:
+                    staticHolder.add(new Object());
+                    break;
+            }
+        } else {
+            allocate(i, depth + 1);
+        }
+    }
+
     private static class Allocations {
         final int messageHeaderLen;
         final int entryHeaderLen;