Implement as much of VMDebug as we can reasonably do right now.

No hprof and no method tracing, but everything else.

Change-Id: Ifccd1f08e31f34b947c30f1211db788aae674d81
diff --git a/src/class_linker.cc b/src/class_linker.cc
index 6832b84..5e05439 100644
--- a/src/class_linker.cc
+++ b/src/class_linker.cc
@@ -1297,6 +1297,9 @@
     if (self->IsExceptionPending()) {
       klass->SetStatus(Class::kStatusError);
     } else {
+      ++Runtime::Current()->GetStats()->class_init_count;
+      ++self->GetStats()->class_init_count;
+      // TODO: class_init_time_ns
       klass->SetStatus(Class::kStatusInitialized);
     }
     lock.NotifyAll();
@@ -2155,6 +2158,23 @@
   return resolved;
 }
 
+void ClassLinker::DumpAllClasses(int flags) const {
+  // TODO: at the time this was written, it wasn't safe to call PrettyField with the ClassLinker
+  // lock held, because it might need to resolve a field's type, which would try to take the lock.
+  std::vector<Class*> all_classes;
+  {
+    MutexLock mu(lock_);
+    typedef Table::const_iterator It;  // TODO: C++0x auto
+    for (It it = classes_.begin(), end = classes_.end(); it != end; ++it) {
+      all_classes.push_back(it->second);
+    }
+  }
+
+  for (size_t i = 0; i < all_classes.size(); ++i) {
+    all_classes[i]->DumpClass(std::cerr, flags);
+  }
+}
+
 size_t ClassLinker::NumLoadedClasses() const {
   MutexLock mu(lock_);
   return classes_.size();
diff --git a/src/class_linker.h b/src/class_linker.h
index 18bd9d6..ca71b1f 100644
--- a/src/class_linker.h
+++ b/src/class_linker.h
@@ -42,6 +42,8 @@
     return FindClass(descriptor, NULL);
   }
 
+  void DumpAllClasses(int flags) const;
+
   size_t NumLoadedClasses() const;
 
   // Resolve a String with the given index from the DexFile, storing the
diff --git a/src/dalvik_system_VMDebug.cc b/src/dalvik_system_VMDebug.cc
new file mode 100644
index 0000000..1defb18
--- /dev/null
+++ b/src/dalvik_system_VMDebug.cc
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#include "class_linker.h"
+#include "jni_internal.h"
+#include "ScopedUtfChars.h"
+#include "toStringArray.h"
+
+#include "JniConstants.h" // Last to avoid problems with LOG redefinition.
+
+#include <string.h>
+#include <unistd.h>
+
+namespace art {
+
+namespace {
+
+/*
+ * Return a set of strings describing available VM features (this is chiefly
+ * of interest to DDMS).
+ */
+jobjectArray VMDebug_getVmFeatureList(JNIEnv* env, jclass) {
+  std::vector<std::string> features;
+  //features.push_back("method-trace-profiling");
+  //features.push_back("method-trace-profiling-streaming");
+  //features.push_back("hprof-heap-dump");
+  //features.push_back("hprof-heap-dump-streaming");
+  return toStringArray(env, features);
+}
+
+void VMDebug_startAllocCounting(JNIEnv*, jclass) {
+  Runtime::Current()->SetStatsEnabled(true);
+}
+
+void VMDebug_stopAllocCounting(JNIEnv*, jclass) {
+  Runtime::Current()->SetStatsEnabled(false);
+}
+
+jint VMDebug_getAllocCount(JNIEnv* env, jclass, jint kind) {
+  return Runtime::Current()->GetStat(kind);
+}
+
+void VMDebug_resetAllocCount(JNIEnv*, jclass, jint kinds) {
+  Runtime::Current()->ResetStats(kinds);
+}
+
+void VMDebug_startMethodTracingDdmsImpl(JNIEnv* env, jclass, jint bufferSize, jint flags) {
+  UNIMPLEMENTED(WARNING);
+  //dvmMethodTraceStart("[DDMS]", -1, bufferSize, flags, true);
+}
+
+void VMDebug_startMethodTracingFd(JNIEnv* env, jclass, jstring javaTraceFilename, jobject javaFd, jint bufferSize, jint flags) {
+  int originalFd = jniGetFDFromFileDescriptor(env, javaFd);
+  if (originalFd < 0) {
+    return;
+  }
+
+  int fd = dup(originalFd);
+  if (fd < 0) {
+    jniThrowExceptionFmt(env, "java/lang/RuntimeException", "dup(%d) failed: %s", originalFd, strerror(errno));
+    return;
+  }
+
+  ScopedUtfChars traceFilename(env, javaTraceFilename);
+  if (traceFilename.c_str() == NULL) {
+    return;
+  }
+  UNIMPLEMENTED(WARNING);
+  //dvmMethodTraceStart(traceFilename.c_str(), fd, bufferSize, flags, false);
+}
+
+void VMDebug_startMethodTracingFilename(JNIEnv* env, jclass, jstring javaTraceFilename, jint bufferSize, jint flags) {
+  ScopedUtfChars traceFilename(env, javaTraceFilename);
+  if (traceFilename.c_str() == NULL) {
+    return;
+  }
+  UNIMPLEMENTED(WARNING);
+  //dvmMethodTraceStart(traceFilename.c_str(), -1, bufferSize, flags, false);
+}
+
+jboolean VMDebug_isMethodTracingActive(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return JNI_FALSE; //dvmIsMethodTraceActive();
+}
+
+void VMDebug_stopMethodTracing(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  //dvmMethodTraceStop();
+}
+
+void VMDebug_startEmulatorTracing(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  //dvmEmulatorTraceStart();
+}
+
+void VMDebug_stopEmulatorTracing(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  //dvmEmulatorTraceStop();
+}
+
+jboolean VMDebug_isDebuggerConnected(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return JNI_FALSE; //dvmDbgIsDebuggerConnected();
+}
+
+jboolean VMDebug_isDebuggingEnabled(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return JNI_FALSE; //return gDvm.jdwpConfigured;
+}
+
+jlong VMDebug_lastDebuggerActivity(JNIEnv*, jclass) {
+  UNIMPLEMENTED(WARNING);
+  return 0; //dvmDbgLastDebuggerActivity();
+}
+
+void VMDebug_startInstructionCounting(JNIEnv* env, jclass) {
+  jniThrowException(env, "java/lang/UnsupportedOperationException", NULL);
+}
+
+void VMDebug_stopInstructionCounting(JNIEnv* env, jclass) {
+  jniThrowException(env, "java/lang/UnsupportedOperationException", NULL);
+}
+
+void VMDebug_getInstructionCount(JNIEnv* env, jclass, jintArray javaCounts) {
+  jniThrowException(env, "java/lang/UnsupportedOperationException", NULL);
+}
+
+void VMDebug_resetInstructionCount(JNIEnv* env, jclass) {
+  jniThrowException(env, "java/lang/UnsupportedOperationException", NULL);
+}
+
+void VMDebug_printLoadedClasses(JNIEnv*, jclass, jint flags) {
+  return Runtime::Current()->GetClassLinker()->DumpAllClasses(flags);
+}
+
+jint VMDebug_getLoadedClassCount(JNIEnv*, jclass) {
+  return Runtime::Current()->GetClassLinker()->NumLoadedClasses();
+}
+
+/*
+ * Returns the thread-specific CPU-time clock value for the current thread,
+ * or -1 if the feature isn't supported.
+ */
+jlong VMDebug_threadCpuTimeNanos(JNIEnv*, jclass) {
+#ifdef HAVE_POSIX_CLOCKS
+  struct timespec now;
+  clock_gettime(CLOCK_THREAD_CPUTIME_ID, &now);
+  return static_cast<jlong>(now.tv_sec*1000000000LL + now.tv_nsec);
+#else
+  return -1LL;
+#endif
+}
+
+/*
+ * static void dumpHprofData(String fileName, FileDescriptor fd)
+ *
+ * Cause "hprof" data to be dumped.  We can throw an IOException if an
+ * error occurs during file handling.
+ */
+void VMDebug_dumpHprofData(JNIEnv* env, jclass, jstring javaFilename, jobject javaFd) {
+  // Only one of these may be NULL.
+  if (javaFilename == NULL && javaFd == NULL) {
+    jniThrowNullPointerException(env, "fileName == null && fd == null");
+    return;
+  }
+
+  std::string filename;
+  if (javaFilename != NULL) {
+    ScopedUtfChars chars(env, javaFilename);
+    if (env->ExceptionCheck()) {
+      return;
+    }
+    filename = chars.c_str();
+  } else {
+    filename = "[fd]";
+  }
+
+  int fd = -1;
+  if (javaFd != NULL) {
+    fd = jniGetFDFromFileDescriptor(env, javaFd);
+    if (fd < 0) {
+      jniThrowException(env, "Ljava/lang/RuntimeException;", "Invalid file descriptor");
+      return;
+    }
+  }
+
+  UNIMPLEMENTED(WARNING);
+  int result = 0; //hprofDumpHeap(filename.c_str(), fd, false);
+  if (result != 0) {
+    // TODO: ideally we'd throw something more specific based on actual failure
+    jniThrowException(env, "Ljava/lang/RuntimeException;", "Failure during heap dump; check log output for details");
+    return;
+  }
+}
+
+void VMDebug_dumpHprofDataDdms(JNIEnv* env, jclass) {
+  UNIMPLEMENTED(WARNING);
+  int result = 0; //hprofDumpHeap("[DDMS]", -1, true);
+  if (result != 0) {
+    // TODO: ideally we'd throw something more specific based on actual failure
+    jniThrowException(env, "Ljava/lang/RuntimeException;", "Failure during heap dump; check log output for details");
+    return;
+  }
+}
+
+void VMDebug_dumpReferenceTables(JNIEnv* env, jclass) {
+  LOG(INFO) << "--- reference table dump ---";
+
+  JNIEnvExt* e = reinterpret_cast<JNIEnvExt*>(env);
+  e->DumpReferenceTables();
+  e->vm->DumpReferenceTables();
+
+  LOG(INFO) << "---";
+}
+
+/*
+ * Dump the current thread's interpreted stack and abort the VM.  Useful
+ * for seeing both interpreted and native stack traces.
+ */
+void VMDebug_crash(JNIEnv*, jclass) {
+  std::stringstream os;
+  os << "Crashing VM on request:\n";
+  Thread::Current()->Dump(os);
+  LOG(FATAL) << os.str();
+}
+
+/*
+ * Provide a hook for gdb to hang to so that the VM can be stopped when
+ * user-tagged source locations are being executed.
+ */
+void VMDebug_infopoint(JNIEnv*, jclass, jint id) {
+  LOG(INFO) << "VMDebug infopoint " << id << " hit";
+}
+
+jlong VMDebug_countInstancesOfClass(JNIEnv* env, jclass, jclass javaClass, jboolean countAssignable) {
+  Class* c = Decode<Class*>(env, javaClass);
+  if (c == NULL) {
+    return 0;
+  }
+  return Heap::CountInstances(c, countAssignable);
+}
+
+JNINativeMethod gMethods[] = {
+  NATIVE_METHOD(VMDebug, countInstancesOfClass, "(Ljava/lang/Class;Z)J"),
+  NATIVE_METHOD(VMDebug, crash, "()V"),
+  NATIVE_METHOD(VMDebug, dumpHprofData, "(Ljava/lang/String;Ljava/io/FileDescriptor;)V"),
+  NATIVE_METHOD(VMDebug, dumpHprofDataDdms, "()V"),
+  NATIVE_METHOD(VMDebug, dumpReferenceTables, "()V"),
+  NATIVE_METHOD(VMDebug, getAllocCount, "(I)I"),
+  NATIVE_METHOD(VMDebug, getInstructionCount, "([I)V"),
+  NATIVE_METHOD(VMDebug, getLoadedClassCount, "()I"),
+  NATIVE_METHOD(VMDebug, getVmFeatureList, "()[Ljava/lang/String;"),
+  NATIVE_METHOD(VMDebug, infopoint, "(I)V"),
+  NATIVE_METHOD(VMDebug, isDebuggerConnected, "()Z"),
+  NATIVE_METHOD(VMDebug, isDebuggingEnabled, "()Z"),
+  NATIVE_METHOD(VMDebug, isMethodTracingActive, "()Z"),
+  NATIVE_METHOD(VMDebug, lastDebuggerActivity, "()J"),
+  NATIVE_METHOD(VMDebug, printLoadedClasses, "(I)V"),
+  NATIVE_METHOD(VMDebug, resetAllocCount, "(I)V"),
+  NATIVE_METHOD(VMDebug, resetInstructionCount, "()V"),
+  NATIVE_METHOD(VMDebug, startAllocCounting, "()V"),
+  NATIVE_METHOD(VMDebug, startEmulatorTracing, "()V"),
+  NATIVE_METHOD(VMDebug, startInstructionCounting, "()V"),
+  NATIVE_METHOD(VMDebug, startMethodTracingDdmsImpl, "(II)V"),
+  NATIVE_METHOD(VMDebug, startMethodTracingFd, "(Ljava/lang/String;Ljava/io/FileDescriptor;II)V"),
+  NATIVE_METHOD(VMDebug, startMethodTracingFilename, "(Ljava/lang/String;II)V"),
+  NATIVE_METHOD(VMDebug, stopAllocCounting, "()V"),
+  NATIVE_METHOD(VMDebug, stopEmulatorTracing, "()V"),
+  NATIVE_METHOD(VMDebug, stopInstructionCounting, "()V"),
+  NATIVE_METHOD(VMDebug, stopMethodTracing, "()V"),
+  NATIVE_METHOD(VMDebug, threadCpuTimeNanos, "()J"),
+};
+
+}  // namespace
+
+void register_dalvik_system_VMDebug(JNIEnv* env) {
+  jniRegisterNativeMethods(env, "dalvik/system/VMDebug", gMethods, NELEM(gMethods));
+}
+
+}  // namespace art
diff --git a/src/heap.cc b/src/heap.cc
index 094790a..4d8d176 100644
--- a/src/heap.cc
+++ b/src/heap.cc
@@ -40,6 +40,8 @@
 
 Mutex* Heap::lock_ = NULL;
 
+bool Heap::verify_objects_ = false;
+
 class ScopedHeapLock {
  public:
   ScopedHeapLock() {
@@ -169,8 +171,6 @@
   return true;
 }
 
-bool Heap::verify_objects_ = false;
-
 #if VERIFY_OBJECT_ENABLED
 void Heap::VerifyObject(const Object* obj) {
   if (!verify_objects_) {
@@ -237,6 +237,16 @@
   DCHECK_NE(size, 0u);
   num_bytes_allocated_ += size;
   num_objects_allocated_ += 1;
+
+  if (Runtime::Current()->HasStatsEnabled()) {
+    RuntimeStats* global_stats = Runtime::Current()->GetStats();
+    RuntimeStats* thread_stats = Thread::Current()->GetStats();
+    ++global_stats->allocated_objects;
+    ++thread_stats->allocated_objects;
+    global_stats->allocated_bytes += size;
+    thread_stats->allocated_bytes += size;
+  }
+
   live_bitmap_->Set(obj);
 }
 
@@ -253,6 +263,15 @@
   if (num_objects_allocated_ > 0) {
     num_objects_allocated_ -= 1;
   }
+
+  if (Runtime::Current()->HasStatsEnabled()) {
+    RuntimeStats* global_stats = Runtime::Current()->GetStats();
+    RuntimeStats* thread_stats = Thread::Current()->GetStats();
+    ++global_stats->freed_objects;
+    ++thread_stats->freed_objects;
+    global_stats->freed_bytes += size;
+    thread_stats->freed_bytes += size;
+  }
 }
 
 void Heap::RecordImageAllocations(Space* space) {
@@ -307,6 +326,10 @@
   // Another failure.  Our thread was starved or there may be too many
   // live objects.  Try a foreground GC.  This will have no effect if
   // the concurrent GC is already running.
+  if (Runtime::Current()->HasStatsEnabled()) {
+    ++Runtime::Current()->GetStats()->gc_for_alloc_count;
+    ++Thread::Current()->GetStats()->gc_for_alloc_count;
+  }
   CollectGarbageInternal();
   ptr = space->AllocWithoutGrowth(size);
   if (ptr != NULL) {
@@ -365,6 +388,46 @@
   return 0;
 }
 
+class InstanceCounter {
+ public:
+  InstanceCounter(Class* c, bool count_assignable)
+      : class_(c), count_assignable_(count_assignable), count_(0) {
+  }
+
+  size_t GetCount() {
+    return count_;
+  }
+
+  static void Callback(Object* o, void* arg) {
+    reinterpret_cast<InstanceCounter*>(arg)->VisitInstance(o);
+  }
+
+ private:
+  void VisitInstance(Object* o) {
+    Class* instance_class = o->GetClass();
+    if (count_assignable_) {
+      if (instance_class == class_) {
+        ++count_;
+      }
+    } else {
+      if (instance_class != NULL && class_->IsAssignableFrom(instance_class)) {
+        ++count_;
+      }
+    }
+  }
+
+  Class* class_;
+  bool count_assignable_;
+  size_t count_;
+};
+
+int64_t Heap::CountInstances(Class* c, bool count_assignable) {
+  ScopedHeapLock lock;
+  InstanceCounter counter(c, count_assignable);
+  live_bitmap_->Walk(InstanceCounter::Callback, &counter);
+  return counter.GetCount();
+}
+
 void Heap::CollectGarbage() {
   ScopedHeapLock lock;
   CollectGarbageInternal();
diff --git a/src/heap.h b/src/heap.h
index e12ef4a..f39454a 100644
--- a/src/heap.h
+++ b/src/heap.h
@@ -1,4 +1,18 @@
-// Copyright 2011 Google Inc. All Rights Reserved.
+/*
+ * Copyright (C) 2008 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.
+ */
 
 #ifndef ART_SRC_HEAP_H_
 #define ART_SRC_HEAP_H_
@@ -64,6 +78,9 @@
   // Implements java.lang.Runtime.freeMemory.
   static int64_t GetFreeMemory();
 
+  // Implements VMDebug.countInstancesOfClass.
+  static int64_t CountInstances(Class* c, bool count_assignable);
+
   // Implements dalvik.system.VMRuntime.clearGrowthLimit.
   static void ClearGrowthLimit() {
     UNIMPLEMENTED(WARNING);
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 6c25f6e..f9fcb78 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -2537,6 +2537,11 @@
 JNIEnvExt::~JNIEnvExt() {
 }
 
+void JNIEnvExt::DumpReferenceTables() {
+  locals.Dump();
+  monitors.Dump();
+}
+
 // JNI Invocation interface.
 
 extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, void** p_env, void* vm_args) {
@@ -2672,6 +2677,21 @@
   delete libraries;
 }
 
+void JavaVMExt::DumpReferenceTables() {
+  {
+    MutexLock mu(globals_lock);
+    globals.Dump();
+  }
+  {
+    MutexLock mu(weak_globals_lock);
+    weak_globals.Dump();
+  }
+  {
+    MutexLock mu(pins_lock);
+    pin_table.Dump();
+  }
+}
+
 bool JavaVMExt::LoadNativeLibrary(const std::string& path, ClassLoader* class_loader, std::string& detail) {
   detail.clear();
 
diff --git a/src/jni_internal.h b/src/jni_internal.h
index 38e38fc..736e5db 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -62,6 +62,8 @@
    */
   void* FindCodeForNativeMethod(Method* m);
 
+  void DumpReferenceTables();
+
   void VisitRoots(Heap::RootVisitor*, void*);
 
   Runtime* runtime;
@@ -104,6 +106,8 @@
   JNIEnvExt(Thread* self, JavaVMExt* vm);
   ~JNIEnvExt();
 
+  void DumpReferenceTables();
+
   Thread* const self;
   JavaVMExt* vm;
 
diff --git a/src/object.cc b/src/object.cc
index b0a6745..b94d392 100644
--- a/src/object.cc
+++ b/src/object.cc
@@ -5,6 +5,7 @@
 #include <string.h>
 
 #include <algorithm>
+#include <iostream>
 #include <string>
 #include <utility>
 
@@ -710,6 +711,63 @@
   return Heap::AllocObject(this, this->object_size_);
 }
 
+void Class::DumpClass(std::ostream& os, int flags) {
+  if ((flags & kDumpClassFullDetail) == 0) {
+    os << PrettyClass(this);
+    if ((flags & kDumpClassClassLoader) != 0) {
+      os << ' ' << GetClassLoader();
+    }
+    if ((flags & kDumpClassInitialized) != 0) {
+      os << ' ' << GetStatus();
+    }
+    os << std::endl;
+    return;
+  }
+
+  Class* super = GetSuperClass();
+  os << "----- " << (IsInterface() ? "interface" : "class") << " "
+     << "'" << GetDescriptor()->ToModifiedUtf8() << "' cl=" << GetClassLoader() << " -----\n",
+  os << "  objectSize=" << SizeOf() << " "
+     << "(" << (super != NULL ? super->SizeOf() : -1) << " from super)\n",
+  os << StringPrintf("  access=0x%04x.%04x\n",
+      GetAccessFlags() >> 16, GetAccessFlags() & kAccJavaFlagsMask);
+  if (super != NULL) {
+    os << "  super='" << PrettyClass(super) << "' (cl=" << super->GetClassLoader() << ")\n";
+  }
+  if (IsArrayClass()) {
+    os << "  componentType=" << PrettyClass(GetComponentType()) << "\n";
+  }
+  if (NumInterfaces() > 0) {
+    os << "  interfaces (" << NumInterfaces() << "):\n";
+    for (size_t i = 0; i < NumInterfaces(); ++i) {
+      Class* interface = GetInterface(i);
+      const ClassLoader* cl = interface->GetClassLoader();
+      os << StringPrintf("    %2d: %s (cl=%p)\n", i, PrettyClass(interface).c_str(), cl);
+    }
+  }
+  os << "  vtable (" << NumVirtualMethods() << " entries, "
+     << (super != NULL ? super->NumVirtualMethods() : 0) << " in super):\n";
+  for (size_t i = 0; i < NumVirtualMethods(); ++i) {
+    os << StringPrintf("    %2d: %s\n", i, PrettyMethod(GetVirtualMethod(i)).c_str());
+  }
+  os << "  direct methods (" << NumDirectMethods() << " entries):\n";
+  for (size_t i = 0; i < NumDirectMethods(); ++i) {
+    os << StringPrintf("    %2d: %s\n", i, PrettyMethod(GetDirectMethod(i)).c_str());
+  }
+  if (NumStaticFields() > 0) {
+    os << "  static fields (" << NumStaticFields() << " entries):\n";
+    for (size_t i = 0; i < NumStaticFields(); ++i) {
+      os << StringPrintf("    %2d: %s\n", i, PrettyField(GetStaticField(i)).c_str());
+    }
+  }
+  if (NumInstanceFields() > 0) {
+    os << "  instance fields (" << NumInstanceFields() << " entries):\n";
+    for (size_t i = 0; i < NumInstanceFields(); ++i) {
+      os << StringPrintf("    %2d: %s\n", i, PrettyField(GetInstanceField(i)).c_str());
+    }
+  }
+}
+
 void Class::SetReferenceInstanceOffsets(uint32_t new_reference_offsets) {
   if (new_reference_offsets != CLASS_WALK_SUPER) {
     // Sanity check that the number of bits set in the reference offset bitmap
diff --git a/src/object.h b/src/object.h
index 2a4078d..6f37d5a 100644
--- a/src/object.h
+++ b/src/object.h
@@ -17,6 +17,7 @@
 #ifndef ART_SRC_OBJECT_H_
 #define ART_SRC_OBJECT_H_
 
+#include <iosfwd>
 #include <vector>
 
 #include "UniquePtr.h"
@@ -1551,6 +1552,14 @@
     return MemberOffset(OFFSETOF_MEMBER(Class, dex_cache_));
   }
 
+  enum {
+    kDumpClassFullDetail = 1,
+    kDumpClassClassLoader = (1 << 1),
+    kDumpClassInitialized = (1 << 2),
+  };
+
+  void DumpClass(std::ostream& os, int flags);
+
   DexCache* GetDexCache() const;
 
   void SetDexCache(DexCache* new_dex_cache);
diff --git a/src/runtime.cc b/src/runtime.cc
index 1f6a06a..68f42af 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -34,7 +34,8 @@
       started_(false),
       vfprintf_(NULL),
       exit_(NULL),
-      abort_(NULL) {
+      abort_(NULL),
+      stats_enabled_(false) {
 }
 
 Runtime::~Runtime() {
@@ -451,7 +452,7 @@
 void Runtime::RegisterRuntimeNativeMethods(JNIEnv* env) {
 #define REGISTER(FN) extern void FN(JNIEnv*); FN(env)
   //REGISTER(register_dalvik_system_DexFile);
-  //REGISTER(register_dalvik_system_VMDebug);
+  REGISTER(register_dalvik_system_VMDebug);
   REGISTER(register_dalvik_system_VMRuntime);
   REGISTER(register_dalvik_system_VMStack);
   //REGISTER(register_dalvik_system_Zygote);
@@ -491,6 +492,60 @@
   thread_list_->Dump(os);
 }
 
+void Runtime::SetStatsEnabled(bool new_state) {
+  if (new_state == true) {
+    GetStats()->Clear(~0);
+    // TODO: wouldn't it make more sense to clear _all_ threads' stats?
+    Thread::Current()->GetStats()->Clear(~0);
+  }
+  stats_enabled_ = new_state;
+}
+
+void Runtime::ResetStats(int kinds) {
+  GetStats()->Clear(kinds & 0xffff);
+  // TODO: wouldn't it make more sense to clear _all_ threads' stats?
+  Thread::Current()->GetStats()->Clear(kinds >> 16);
+}
+
+RuntimeStats* Runtime::GetStats() {
+  return &stats_;
+}
+
+int32_t Runtime::GetStat(int kind) {
+  RuntimeStats* stats;
+  if (kind < (1<<16)) {
+    stats = GetStats();
+  } else {
+    stats = Thread::Current()->GetStats();
+    kind >>= 16;
+  }
+  switch (kind) {
+  case KIND_ALLOCATED_OBJECTS:
+    return stats->allocated_objects;
+  case KIND_ALLOCATED_BYTES:
+    return stats->allocated_bytes;
+  case KIND_FREED_OBJECTS:
+    return stats->freed_objects;
+  case KIND_FREED_BYTES:
+    return stats->freed_bytes;
+  case KIND_GC_INVOCATIONS:
+    return stats->gc_for_alloc_count;
+  case KIND_CLASS_INIT_COUNT:
+    return stats->class_init_count;
+  case KIND_CLASS_INIT_TIME:
+    // Convert ns to us, reduce to 32 bits.
+    return (int) (stats->class_init_time_ns / 1000);
+  case KIND_EXT_ALLOCATED_OBJECTS:
+  case KIND_EXT_ALLOCATED_BYTES:
+  case KIND_EXT_FREED_OBJECTS:
+  case KIND_EXT_FREED_BYTES:
+    return 0;  // backward compatibility
+  default:
+    CHECK(false);
+    return -1; // unreachable
+  }
+}
+
 void Runtime::BlockSignals() {
   sigset_t sigset;
   if (sigemptyset(&sigset) == -1) {
diff --git a/src/runtime.h b/src/runtime.h
index 70bdc99..b3d3ebf 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -15,6 +15,7 @@
 #include "heap.h"
 #include "globals.h"
 #include "macros.h"
+#include "runtime_stats.h"
 #include "stringpiece.h"
 #include "unordered_set.h"
 
@@ -153,6 +154,18 @@
     jni_stub_array_ = jni_stub_array;
   }
 
+  int32_t GetStat(int kind);
+
+  RuntimeStats* GetStats();
+
+  bool HasStatsEnabled() const {
+    return stats_enabled_;
+  }
+
+  void ResetStats(int kinds);
+
+  void SetStatsEnabled(bool new_state);
+
  private:
   static void PlatformAbort(const char*, int);
 
@@ -192,6 +205,9 @@
   void (*exit_)(jint status);
   void (*abort_)();
 
+  bool stats_enabled_;
+  RuntimeStats stats_;
+
   // A pointer to the active runtime or NULL.
   static Runtime* instance_;
 
diff --git a/src/runtime_stats.h b/src/runtime_stats.h
new file mode 100644
index 0000000..2d2bf0c
--- /dev/null
+++ b/src/runtime_stats.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2008 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.
+ */
+
+#ifndef ART_SRC_RUNTIME_STATS_H_
+#define ART_SRC_RUNTIME_STATS_H_
+
+#include <stdint.h>
+
+namespace art {
+
+// These must match the values in dalvik.system.VMDebug.
+enum StatKinds {
+  KIND_ALLOCATED_OBJECTS      = 1<<0,
+  KIND_ALLOCATED_BYTES        = 1<<1,
+  KIND_FREED_OBJECTS          = 1<<2,
+  KIND_FREED_BYTES            = 1<<3,
+  KIND_GC_INVOCATIONS         = 1<<4,
+  KIND_CLASS_INIT_COUNT       = 1<<5,
+  KIND_CLASS_INIT_TIME        = 1<<6,
+
+  // These values exist for backward compatibility.
+  KIND_EXT_ALLOCATED_OBJECTS = 1<<12,
+  KIND_EXT_ALLOCATED_BYTES   = 1<<13,
+  KIND_EXT_FREED_OBJECTS     = 1<<14,
+  KIND_EXT_FREED_BYTES       = 1<<15,
+
+  KIND_GLOBAL_ALLOCATED_OBJECTS   = KIND_ALLOCATED_OBJECTS,
+  KIND_GLOBAL_ALLOCATED_BYTES     = KIND_ALLOCATED_BYTES,
+  KIND_GLOBAL_FREED_OBJECTS       = KIND_FREED_OBJECTS,
+  KIND_GLOBAL_FREED_BYTES         = KIND_FREED_BYTES,
+  KIND_GLOBAL_GC_INVOCATIONS      = KIND_GC_INVOCATIONS,
+  KIND_GLOBAL_CLASS_INIT_COUNT    = KIND_CLASS_INIT_COUNT,
+  KIND_GLOBAL_CLASS_INIT_TIME     = KIND_CLASS_INIT_TIME,
+
+  KIND_THREAD_ALLOCATED_OBJECTS   = KIND_ALLOCATED_OBJECTS << 16,
+  KIND_THREAD_ALLOCATED_BYTES     = KIND_ALLOCATED_BYTES << 16,
+  KIND_THREAD_FREED_OBJECTS       = KIND_FREED_OBJECTS << 16,
+  KIND_THREAD_FREED_BYTES         = KIND_FREED_BYTES << 16,
+
+  KIND_THREAD_GC_INVOCATIONS      = KIND_GC_INVOCATIONS << 16,
+
+  // TODO: failedAllocCount, failedAllocSize
+};
+
+/*
+ * Memory allocation profiler state.  This is used both globally and
+ * per-thread.
+ */
+struct PACKED RuntimeStats {
+  RuntimeStats() {
+    Clear(~0);
+  }
+
+  void Clear(int flags) {
+    if ((flags & KIND_ALLOCATED_OBJECTS) != 0) {
+      allocated_objects = 0;
+    }
+    if ((flags & KIND_ALLOCATED_BYTES) != 0) {
+      allocated_bytes = 0;
+    }
+    if ((flags & KIND_FREED_OBJECTS) != 0) {
+      freed_objects = 0;
+    }
+    if ((flags & KIND_FREED_BYTES) != 0) {
+      freed_bytes = 0;
+    }
+    if ((flags & KIND_GC_INVOCATIONS) != 0) {
+      gc_for_alloc_count = 0;
+    }
+    if ((flags & KIND_CLASS_INIT_COUNT) != 0) {
+      class_init_count = 0;
+    }
+    if ((flags & KIND_CLASS_INIT_TIME) != 0) {
+      class_init_time_ns = 0;
+    }
+  }
+
+  // Number of objects allocated.
+  int allocated_objects;
+  // Cumulative size of all objects allocated.
+  int allocated_bytes;
+
+  // Number of objects freed.
+  int freed_objects;
+  // Cumulative size of all freed objects.
+  int freed_bytes;
+
+  // Number of times an allocation triggered a GC.
+  int gc_for_alloc_count;
+
+  // Number of initialized classes.
+  int class_init_count;
+  // Cumulative time spent in class initialization.
+  uint64_t class_init_time_ns;
+
+  DISALLOW_COPY_AND_ASSIGN(RuntimeStats);
+};
+
+}  // namespace art
+
+#endif  // ART_SRC_HEAP_H_
diff --git a/src/thread.h b/src/thread.h
index 0be4aa6..5a0c4f0 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -32,6 +32,7 @@
 #include "mutex.h"
 #include "mem_map.h"
 #include "offsets.h"
+#include "runtime_stats.h"
 #include "UniquePtr.h"
 
 namespace art {
@@ -311,6 +312,10 @@
     return peer_;
   }
 
+  RuntimeStats* GetStats() {
+    return &stats_;
+  }
+
   // Returns the Method* for the current method.
   // This is used by the JNI implementation for logging and diagnostic purposes.
   const Method* GetCurrentMethod() const {
@@ -566,6 +571,8 @@
 
   friend class Monitor;
 
+  RuntimeStats stats_;
+
   // FIXME: placeholder for the gc cardTable
   uint32_t card_table_;