Implement EnableCollection, DisableCollection, IsCollected, and fix Exit.

Rewrites the object registry to not just be one big leak. We use jweaks
by default, which means (a) the GC doesn't need to know anything about us
and (b) we don't cause unnecessary heap retention. We promote to regular
JNI global references if the debugger asks us to.

The problem with VirtualMachine.Exit turned out to be that you're supposed
to send a success reply to the command before exiting. This is a bit awkward
with our current division of responsibilities, but it lets us pass another
test.

Also log a summary of our replies when -verbose:jdwp is in effect, not
just the requests.

Change-Id: Idb33e99fe7d8bee7a79152d81fee42e2af00852b
diff --git a/src/debugger.cc b/src/debugger.cc
index 3121725..697c7cd 100644
--- a/src/debugger.cc
+++ b/src/debugger.cc
@@ -26,6 +26,7 @@
 #include "gc/card_table-inl.h"
 #include "gc/large_object_space.h"
 #include "gc/space.h"
+#include "jdwp/object_registry.h"
 #include "mirror/abstract_method-inl.h"
 #include "mirror/class.h"
 #include "mirror/class-inl.h"
@@ -37,9 +38,9 @@
 #include "oat/runtime/context.h"
 #include "object_utils.h"
 #include "safe_map.h"
+#include "scoped_thread_state_change.h"
 #include "ScopedLocalRef.h"
 #include "ScopedPrimitiveArray.h"
-#include "scoped_thread_state_change.h"
 #include "sirt_ref.h"
 #include "stack_indirect_reference_table.h"
 #include "thread_list.h"
@@ -49,60 +50,7 @@
 namespace art {
 
 static const size_t kMaxAllocRecordStackDepth = 16; // Max 255.
-static const size_t kNumAllocRecords = 512; // Must be power of 2.
-
-static const uintptr_t kInvalidId = 1;
-static const mirror::Object* kInvalidObject = reinterpret_cast<mirror::Object*>(kInvalidId);
-
-class ObjectRegistry {
- public:
-  ObjectRegistry() : lock_("ObjectRegistry lock") {
-  }
-
-  JDWP::ObjectId Add(mirror::Object* o) {
-    if (o == NULL) {
-      return 0;
-    }
-    JDWP::ObjectId id = static_cast<JDWP::ObjectId>(reinterpret_cast<uintptr_t>(o));
-    MutexLock mu(Thread::Current(), lock_);
-    map_.Overwrite(id, o);
-    return id;
-  }
-
-  void Clear() {
-    MutexLock mu(Thread::Current(), lock_);
-    LOG(DEBUG) << "Debugger has detached; object registry had " << map_.size() << " entries";
-    map_.clear();
-  }
-
-  bool Contains(JDWP::ObjectId id) {
-    MutexLock mu(Thread::Current(), lock_);
-    return map_.find(id) != map_.end();
-  }
-
-  template<typename T> T Get(JDWP::ObjectId id) {
-    if (id == 0) {
-      return NULL;
-    }
-
-    MutexLock mu(Thread::Current(), lock_);
-    typedef SafeMap<JDWP::ObjectId, mirror::Object*>::iterator It; // C++0x auto
-    It it = map_.find(id);
-    return (it != map_.end()) ? reinterpret_cast<T>(it->second) : reinterpret_cast<T>(kInvalidId);
-  }
-
-  void VisitRoots(RootVisitor* visitor, void* arg) {
-    MutexLock mu(Thread::Current(), lock_);
-    typedef SafeMap<JDWP::ObjectId, mirror::Object*>::iterator It; // C++0x auto
-    for (It it = map_.begin(); it != map_.end(); ++it) {
-      visitor(it->second, arg);
-    }
-  }
-
- private:
-  Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  SafeMap<JDWP::ObjectId, mirror::Object*> map_;
-};
+static const size_t kNumAllocRecords = 512; // Must be a power of 2.
 
 struct AllocRecordStackTraceElement {
   mirror::AbstractMethod* method;
@@ -213,7 +161,7 @@
 static mirror::Array* DecodeArray(JDWP::RefTypeId id, JDWP::JdwpError& status)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(id);
-  if (o == NULL || o == kInvalidObject) {
+  if (o == NULL || o == ObjectRegistry::kInvalidObject) {
     status = JDWP::ERR_INVALID_OBJECT;
     return NULL;
   }
@@ -228,7 +176,7 @@
 static mirror::Class* DecodeClass(JDWP::RefTypeId id, JDWP::JdwpError& status)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(id);
-  if (o == NULL || o == kInvalidObject) {
+  if (o == NULL || o == ObjectRegistry::kInvalidObject) {
     status = JDWP::ERR_INVALID_OBJECT;
     return NULL;
   }
@@ -245,7 +193,7 @@
     LOCKS_EXCLUDED(Locks::thread_suspend_count_lock_)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* thread_peer = gRegistry->Get<mirror::Object*>(thread_id);
-  if (thread_peer == NULL || thread_peer == kInvalidObject) {
+  if (thread_peer == NULL || thread_peer == ObjectRegistry::kInvalidObject) {
     // This isn't even an object.
     return JDWP::ERR_INVALID_OBJECT;
   }
@@ -573,22 +521,12 @@
   Runtime::Current()->GetThreadList()->UndoDebuggerSuspensions();
 }
 
-void Dbg::Exit(int status) {
-  exit(status); // This is all dalvik did.
-}
-
-void Dbg::VisitRoots(RootVisitor* visitor, void* arg) {
-  if (gRegistry != NULL) {
-    gRegistry->VisitRoots(visitor, arg);
-  }
-}
-
 std::string Dbg::GetClassName(JDWP::RefTypeId class_id) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(class_id);
   if (o == NULL) {
     return "NULL";
   }
-  if (o == kInvalidObject) {
+  if (o == ObjectRegistry::kInvalidObject) {
     return StringPrintf("invalid object %p", reinterpret_cast<void*>(class_id));
   }
   if (!o->IsClass()) {
@@ -624,7 +562,7 @@
 
 JDWP::JdwpError Dbg::GetClassLoader(JDWP::RefTypeId id, JDWP::ExpandBuf* pReply) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(id);
-  if (o == NULL || o == kInvalidObject) {
+  if (o == NULL || o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
   expandBufAddObjectId(pReply, gRegistry->Add(o->GetClass()->GetClassLoader()));
@@ -652,7 +590,7 @@
 JDWP::JdwpError Dbg::GetMonitorInfo(JDWP::ObjectId object_id, JDWP::ExpandBuf* reply)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id);
-  if (o == NULL || o == kInvalidObject) {
+  if (o == NULL || o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
 
@@ -789,7 +727,7 @@
                                          std::vector<JDWP::ObjectId>& referring_objects)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id);
-  if (o == NULL || o == kInvalidObject) {
+  if (o == NULL || o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
 
@@ -801,6 +739,29 @@
   return JDWP::ERR_NONE;
 }
 
+JDWP::JdwpError Dbg::DisableCollection(JDWP::ObjectId object_id)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  gRegistry->DisableCollection(object_id);
+  return JDWP::ERR_NONE;
+}
+
+JDWP::JdwpError Dbg::EnableCollection(JDWP::ObjectId object_id)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  gRegistry->EnableCollection(object_id);
+  return JDWP::ERR_NONE;
+}
+
+JDWP::JdwpError Dbg::IsCollected(JDWP::ObjectId object_id, bool& is_collected)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  is_collected = gRegistry->IsCollected(object_id);
+  return JDWP::ERR_NONE;
+}
+
+void Dbg::DisposeObject(JDWP::ObjectId object_id, uint32_t reference_count)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  gRegistry->DisposeObject(object_id, reference_count);
+}
+
 JDWP::JdwpError Dbg::GetReflectedType(JDWP::RefTypeId class_id, JDWP::ExpandBuf* pReply) {
   JDWP::JdwpError status;
   mirror::Class* c = DecodeClass(class_id, status);
@@ -825,9 +786,11 @@
       return reinterpret_cast<ClassListCreator*>(arg)->Visit(c);
     }
 
-    bool Visit(mirror::Class* c) {
+    // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses
+    // annotalysis.
+    bool Visit(mirror::Class* c) NO_THREAD_SAFETY_ANALYSIS {
       if (!c->IsPrimitive()) {
-        classes.push_back(static_cast<JDWP::RefTypeId>(gRegistry->Add(c)));
+        classes.push_back(gRegistry->AddRefType(c));
       }
       return true;
     }
@@ -873,9 +836,10 @@
   }
 }
 
-JDWP::JdwpError Dbg::GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply) {
+JDWP::JdwpError Dbg::GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id);
-  if (o == NULL || o == kInvalidObject) {
+  if (o == NULL || o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
 
@@ -887,7 +851,7 @@
   } else {
     type_tag = JDWP::TT_CLASS;
   }
-  JDWP::RefTypeId type_id = gRegistry->Add(o->GetClass());
+  JDWP::RefTypeId type_id = gRegistry->AddRefType(o->GetClass());
 
   expandBufAdd1(pReply, type_tag);
   expandBufAddRefTypeId(pReply, type_id);
@@ -917,7 +881,7 @@
 
 JDWP::JdwpError Dbg::GetObjectTag(JDWP::ObjectId object_id, uint8_t& tag) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id);
-  if (o == kInvalidObject) {
+  if (o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
   tag = TagFromObject(o);
@@ -1054,7 +1018,7 @@
     for (int i = 0; i < count; ++i) {
       JDWP::ObjectId id = JDWP::ReadObjectId(&src);
       mirror::Object* o = gRegistry->Get<mirror::Object*>(id);
-      if (o == kInvalidObject) {
+      if (o == ObjectRegistry::kInvalidObject) {
         return JDWP::ERR_INVALID_OBJECT;
       }
       oa->Set(offset + i, o);
@@ -1278,7 +1242,7 @@
   size_t interface_count = kh.NumDirectInterfaces();
   expandBufAdd4BE(pReply, interface_count);
   for (size_t i = 0; i < interface_count; ++i) {
-    expandBufAddRefTypeId(pReply, gRegistry->Add(kh.GetDirectInterface(i)));
+    expandBufAddRefTypeId(pReply, gRegistry->AddRefType(kh.GetDirectInterface(i)));
   }
   return JDWP::ERR_NONE;
 }
@@ -1412,7 +1376,7 @@
   }
 
   mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id);
-  if ((!is_static && o == NULL) || o == kInvalidObject) {
+  if ((!is_static && o == NULL) || o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
   mirror::Field* f = FromFieldId(field_id);
@@ -1478,7 +1442,7 @@
                                          uint64_t value, int width, bool is_static)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   mirror::Object* o = gRegistry->Get<mirror::Object*>(object_id);
-  if ((!is_static && o == NULL) || o == kInvalidObject) {
+  if ((!is_static && o == NULL) || o == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
   mirror::Field* f = FromFieldId(field_id);
@@ -1510,7 +1474,7 @@
     }
   } else {
     mirror::Object* v = gRegistry->Get<mirror::Object*>(value);
-    if (v == kInvalidObject) {
+    if (v == ObjectRegistry::kInvalidObject) {
       return JDWP::ERR_INVALID_OBJECT;
     }
     if (v != NULL) {
@@ -1563,7 +1527,7 @@
 JDWP::JdwpError Dbg::GetThreadGroup(JDWP::ObjectId thread_id, JDWP::ExpandBuf* pReply) {
   ScopedObjectAccess soa(Thread::Current());
   mirror::Object* thread_object = gRegistry->Get<mirror::Object*>(thread_id);
-  if (thread_object == kInvalidObject) {
+  if (thread_object == ObjectRegistry::kInvalidObject) {
     return JDWP::ERR_INVALID_OBJECT;
   }
 
@@ -1784,7 +1748,9 @@
     CountStackDepthVisitor(Thread* thread)
         : StackVisitor(thread, NULL), depth(0) {}
 
-    bool VisitFrame() {
+    // TODO: Enable annotalysis. We know lock is held in constructor, but abstraction confuses
+    // annotalysis.
+    bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
       if (!GetMethod()->IsRuntimeMethod()) {
         ++depth;
       }
@@ -2169,7 +2135,7 @@
         {
           CHECK_EQ(width_, sizeof(JDWP::ObjectId));
           mirror::Object* o = gRegistry->Get<mirror::Object*>(static_cast<JDWP::ObjectId>(value_));
-          if (o == kInvalidObject) {
+          if (o == ObjectRegistry::kInvalidObject) {
             UNIMPLEMENTED(FATAL) << "return an error code when given an invalid object to store";
           }
           SetVReg(m, reg, static_cast<uint32_t>(reinterpret_cast<uintptr_t>(o)), kReferenceVReg);
@@ -2216,26 +2182,23 @@
 
   JDWP::JdwpLocation location;
   location.type_tag = c->IsInterface() ? JDWP::TT_INTERFACE : JDWP::TT_CLASS;
-  location.class_id = gRegistry->Add(c);
+  location.class_id = gRegistry->AddRefType(c);
   location.method_id = ToMethodId(m);
   location.dex_pc = m->IsNative() ? -1 : dex_pc;
 
-  // Note we use "NoReg" so we don't keep track of references that are
-  // never actually sent to the debugger. 'this_id' is only used to
-  // compare against registered events...
-  JDWP::ObjectId this_id = static_cast<JDWP::ObjectId>(reinterpret_cast<uintptr_t>(this_object));
-  if (gJdwpState->PostLocationEvent(&location, this_id, event_flags)) {
-    // ...unless there's a registered event, in which case we
-    // need to really track the class and 'this'.
-    gRegistry->Add(c);
-    gRegistry->Add(this_object);
+  // If 'this_object' isn't already in the registry, we know that we're not looking for it,
+  // so there's no point adding it to the registry and burning through ids.
+  JDWP::ObjectId this_id = 0;
+  if (gRegistry->Contains(this_object)) {
+    this_id = gRegistry->Add(this_object);
   }
+  gJdwpState->PostLocationEvent(&location, this_id, event_flags);
 }
 
 void Dbg::PostException(Thread* thread,
                         JDWP::FrameId throw_frame_id, mirror::AbstractMethod* throw_method,
                         uint32_t throw_dex_pc, mirror::AbstractMethod* catch_method,
-                        uint32_t catch_dex_pc, mirror::Throwable* exception) {
+                        uint32_t catch_dex_pc, mirror::Throwable* exception_object) {
   if (!IsDebuggerActive()) {
     return;
   }
@@ -2251,17 +2214,8 @@
   visitor.WalkStack();
   JDWP::ObjectId this_id = gRegistry->Add(visitor.this_object);
 
-  /*
-   * Hand the event to the JDWP exception handler.  Note we're using the
-   * "NoReg" objectID on the exception, which is not strictly correct --
-   * the exception object WILL be passed up to the debugger if the
-   * debugger is interested in the event.  We do this because the current
-   * implementation of the debugger object registry never throws anything
-   * away, and some people were experiencing a fatal build up of exception
-   * objects when dealing with certain libraries.
-   */
-  JDWP::ObjectId exception_id = static_cast<JDWP::ObjectId>(reinterpret_cast<uintptr_t>(exception));
-  JDWP::RefTypeId exception_class_id = gRegistry->Add(exception->GetClass());
+  JDWP::ObjectId exception_id = gRegistry->Add(exception_object);
+  JDWP::RefTypeId exception_class_id = gRegistry->AddRefType(exception_object->GetClass());
 
   gJdwpState->PostException(&throw_location, exception_id, exception_class_id, &catch_location, this_id);
 }
@@ -2633,12 +2587,12 @@
 
     JDWP::JdwpError status;
     mirror::Object* receiver = gRegistry->Get<mirror::Object*>(object_id);
-    if (receiver == kInvalidObject) {
+    if (receiver == ObjectRegistry::kInvalidObject) {
       return JDWP::ERR_INVALID_OBJECT;
     }
 
     mirror::Object* thread = gRegistry->Get<mirror::Object*>(thread_id);
-    if (thread == kInvalidObject) {
+    if (thread == ObjectRegistry::kInvalidObject) {
       return JDWP::ERR_INVALID_OBJECT;
     }
     // TODO: check that 'thread' is actually a java.lang.Thread!
@@ -2808,17 +2762,6 @@
 }
 
 /*
- * Register an object ID that might not have been registered previously.
- *
- * Normally this wouldn't happen -- the conversion to an ObjectId would
- * have added the object to the registry -- but in some cases (e.g.
- * throwing exceptions) we really want to do the registration late.
- */
-void Dbg::RegisterObjectId(JDWP::ObjectId id) {
-  gRegistry->Add(reinterpret_cast<mirror::Object*>(id));
-}
-
-/*
  * "buf" contains a full JDWP packet, possibly with multiple chunks.  We
  * need to process each, accumulate the replies, and ship the whole thing
  * back.
diff --git a/src/debugger.h b/src/debugger.h
index a796349..d63e44b 100644
--- a/src/debugger.h
+++ b/src/debugger.h
@@ -123,10 +123,6 @@
 
   static void UndoDebuggerSuspensions();
 
-  static void Exit(int status);
-
-  static void VisitRoots(RootVisitor* visitor, void* arg);
-
   /*
    * Class, Object, Array
    */
@@ -149,7 +145,8 @@
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   static void FindLoadedClassBySignature(const char* descriptor, std::vector<JDWP::RefTypeId>& ids)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  static JDWP::JdwpError GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply);
+  static JDWP::JdwpError GetReferenceType(JDWP::ObjectId object_id, JDWP::ExpandBuf* pReply)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   static JDWP::JdwpError GetSignature(JDWP::RefTypeId ref_type_id, std::string& signature)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   static JDWP::JdwpError GetSourceFile(JDWP::RefTypeId ref_type_id, std::string& source_file)
@@ -202,6 +199,14 @@
   static JDWP::JdwpError GetReferringObjects(JDWP::ObjectId object_id, int32_t max_count,
                                              std::vector<JDWP::ObjectId>& referring_objects)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  static JDWP::JdwpError DisableCollection(JDWP::ObjectId object_id)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  static JDWP::JdwpError EnableCollection(JDWP::ObjectId object_id)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  static JDWP::JdwpError IsCollected(JDWP::ObjectId object_id, bool& is_collected)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  static void DisposeObject(JDWP::ObjectId object_id, uint32_t reference_count)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   //
   // Methods and fields.
@@ -360,9 +365,6 @@
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
   static void ExecuteMethod(DebugInvokeReq* pReq);
 
-  /* perform "late registration" of an object ID */
-  static void RegisterObjectId(JDWP::ObjectId id);
-
   /*
    * DDM support.
    */
diff --git a/src/jdwp/jdwp.h b/src/jdwp/jdwp.h
index 71bae08..981a732 100644
--- a/src/jdwp/jdwp.h
+++ b/src/jdwp/jdwp.h
@@ -131,6 +131,8 @@
    */
   int64_t LastDebuggerActivity();
 
+  void ExitAfterReplying(int exit_status);
+
   /*
    * When we hit a debugger event that requires suspension, it's important
    * that we wait for the thread to suspend itself before processing any
@@ -344,6 +346,9 @@
   ObjectId event_thread_id_;
 
   bool ddm_is_active_;
+
+  bool should_exit_;
+  int exit_status_;
 };
 
 }  // namespace JDWP
diff --git a/src/jdwp/jdwp_event.cc b/src/jdwp/jdwp_event.cc
index a2c10b5..4423058 100644
--- a/src/jdwp/jdwp_event.cc
+++ b/src/jdwp/jdwp_event.cc
@@ -723,7 +723,6 @@
  */
 bool JdwpState::PostLocationEvent(const JdwpLocation* pLoc, ObjectId thisPtr, int eventFlags) {
   ModBasket basket;
-
   basket.pLoc = pLoc;
   basket.classId = pLoc->class_id;
   basket.thisPtr = thisPtr;
@@ -802,7 +801,6 @@
   }
 
   SendRequestAndPossiblySuspend(pReq, suspend_policy, basket.threadId);
-
   return match_count != 0;
 }
 
@@ -951,9 +949,6 @@
         expandBufAdd8BE(pReq, exceptionId);
         expandBufAddLocation(pReq, *pCatchLoc);
       }
-
-      /* don't let the GC discard it */
-      Dbg::RegisterObjectId(exceptionId);
     }
 
     CleanupMatchList(match_list, match_count);
diff --git a/src/jdwp/jdwp_handler.cc b/src/jdwp/jdwp_handler.cc
index dd80089..bc7c4d9 100644
--- a/src/jdwp/jdwp_handler.cc
+++ b/src/jdwp/jdwp_handler.cc
@@ -421,17 +421,11 @@
   return ERR_NONE;
 }
 
-/*
- * The debugger wants the entire VM to exit.
- */
-static JdwpError VM_Exit(JdwpState*, const uint8_t* buf, int, ExpandBuf*)
+static JdwpError VM_Exit(JdwpState* state, const uint8_t* buf, int, ExpandBuf*)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  uint32_t exitCode = Get4BE(buf);
-
-  LOG(WARNING) << "Debugger is telling the VM to exit with code=" << exitCode;
-
-  Dbg::Exit(exitCode);
-  return ERR_NOT_IMPLEMENTED;     // shouldn't get here
+  uint32_t exit_status = ReadUnsigned32("exit_status", &buf);
+  state->ExitAfterReplying(exit_status);
+  return ERR_NONE;
 }
 
 /*
@@ -472,13 +466,14 @@
   return ERR_NONE;
 }
 
-/*
- * Release a list of object IDs.  (Seen in jdb.)
- *
- * Currently does nothing.
- */
-static JdwpError VM_DisposeObjects(JdwpState*, const uint8_t*, int, ExpandBuf*)
+static JdwpError VM_DisposeObjects(JdwpState*, const uint8_t* buf, int, ExpandBuf*)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  size_t object_count = ReadUnsigned32("object_count", &buf);
+  for (size_t i = 0; i < object_count; ++i) {
+    ObjectId object_id = ReadObjectId(&buf);
+    uint32_t reference_count = ReadUnsigned32("reference_count", &buf);
+    Dbg::DisposeObject(object_id, reference_count);
+  }
   return ERR_NONE;
 }
 
@@ -1014,35 +1009,25 @@
   return FinishInvoke(state, buf, dataLen, pReply, thread_id, object_id, class_id, method_id, false);
 }
 
-/*
- * Disable garbage collection of the specified object.
- */
-static JdwpError OR_DisableCollection(JdwpState*, const uint8_t*, int, ExpandBuf*)
+static JdwpError OR_DisableCollection(JdwpState*, const uint8_t* buf, int, ExpandBuf*)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  // TODO: this is currently a no-op.
-  return ERR_NONE;
+  ObjectId object_id = ReadObjectId(&buf);
+  return Dbg::DisableCollection(object_id);
 }
 
-/*
- * Enable garbage collection of the specified object.
- */
-static JdwpError OR_EnableCollection(JdwpState*, const uint8_t*, int, ExpandBuf*)
+static JdwpError OR_EnableCollection(JdwpState*, const uint8_t* buf, int, ExpandBuf*)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  // TODO: this is currently a no-op.
-  return ERR_NONE;
+  ObjectId object_id = ReadObjectId(&buf);
+  return Dbg::EnableCollection(object_id);
 }
 
-/*
- * Determine whether an object has been garbage collected.
- */
 static JdwpError OR_IsCollected(JdwpState*, const uint8_t* buf, int, ExpandBuf* pReply)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  //ObjectId object_id = ReadObjectId(&buf);
-
-  // TODO: currently returning false; must integrate with GC
-  expandBufAdd1(pReply, 0);
-
-  return ERR_NONE;
+  ObjectId object_id = ReadObjectId(&buf);
+  bool is_collected;
+  JdwpError rc = Dbg::IsCollected(object_id, is_collected);
+  expandBufAdd1(pReply, is_collected ? 1 : 0);
+  return rc;
 }
 
 static JdwpError OR_ReferringObjects(JdwpState*, const uint8_t* buf, int, ExpandBuf* reply)
@@ -1824,9 +1809,9 @@
 
 static std::string DescribeCommand(const JdwpReqHeader* pHeader, int dataLen) {
   std::string result;
-  result += "REQ: ";
+  result += "REQUEST: ";
   result += GetCommandName(pHeader->cmdSet, pHeader->cmd);
-  result += StringPrintf(" (dataLen=%d id=0x%06x)", dataLen, pHeader->id);
+  result += StringPrintf(" (length=%d id=0x%06x)", dataLen, pHeader->id);
   return result;
 }
 
@@ -1905,9 +1890,9 @@
   }
 
   size_t respLen = expandBufGetLength(pReply) - kJDWPHeaderLen;
+  VLOG(jdwp) << "REPLY: " << GetCommandName(pHeader->cmdSet, pHeader->cmd) << " " << result << " (length=" << respLen << ")";
   if (false) {
-    LOG(INFO) << "reply: dataLen=" << respLen << " err=" << result << (result != ERR_NONE ? " **FAILED**" : "");
-    LOG(INFO) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen);
+    VLOG(jdwp) << HexDump(expandBufGetBuffer(pReply) + kJDWPHeaderLen, respLen);
   }
 
   /*
@@ -1920,7 +1905,6 @@
 
   /* tell the VM that GC is okay again */
   self->TransitionFromRunnableToSuspended(old_state);
-
 }
 
 }  // namespace JDWP
diff --git a/src/jdwp/jdwp_main.cc b/src/jdwp/jdwp_main.cc
index 9e6825c..cbac920 100644
--- a/src/jdwp/jdwp_main.cc
+++ b/src/jdwp/jdwp_main.cc
@@ -104,7 +104,9 @@
       event_thread_lock_("JDWP event thread lock"),
       event_thread_cond_("JDWP event thread condition variable", event_thread_lock_),
       event_thread_id_(0),
-      ddm_is_active_(false) {
+      ddm_is_active_(false),
+      should_exit_(false),
+      exit_status_(0) {
 }
 
 /*
@@ -351,6 +353,10 @@
         break;
       }
 
+      if (should_exit_) {
+        exit(exit_status_);
+      }
+
       if (first && !(*transport_->awaitingHandshake)(this)) {
         /* handshake worked, tell the interpreter that we're active */
         first = false;
@@ -462,6 +468,12 @@
   return now - last;
 }
 
+void JdwpState::ExitAfterReplying(int exit_status) {
+  LOG(WARNING) << "Debugger told VM to exit with status " << exit_status;
+  should_exit_ = true;
+  exit_status_ = exit_status;
+}
+
 std::ostream& operator<<(std::ostream& os, const JdwpLocation& rhs) {
   os << "JdwpLocation["
      << Dbg::GetClassName(rhs.class_id) << "." << Dbg::GetMethodName(rhs.method_id)
diff --git a/src/jdwp/object_registry.cc b/src/jdwp/object_registry.cc
new file mode 100644
index 0000000..82898c1
--- /dev/null
+++ b/src/jdwp/object_registry.cc
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2013 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 "object_registry.h"
+
+#include "scoped_thread_state_change.h"
+
+namespace art {
+
+mirror::Object* const ObjectRegistry::kInvalidObject = reinterpret_cast<mirror::Object*>(1);
+
+std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs) {
+  os << "ObjectRegistryEntry[" << rhs.jni_reference_type
+     << ",reference=" << rhs.jni_reference
+     << ",count=" << rhs.reference_count
+     << ",id=" << rhs.id << "]";
+  return os;
+}
+
+ObjectRegistry::ObjectRegistry() : lock_("ObjectRegistry lock"), next_id_(1) {
+}
+
+JDWP::RefTypeId ObjectRegistry::AddRefType(mirror::Class* c) {
+  return InternalAdd(c);
+}
+
+JDWP::ObjectId ObjectRegistry::Add(mirror::Object* o) {
+  return InternalAdd(o);
+}
+
+JDWP::ObjectId ObjectRegistry::InternalAdd(mirror::Object* o) {
+  if (o == NULL) {
+    return 0;
+  }
+
+  ScopedObjectAccessUnchecked soa(Thread::Current());
+  MutexLock mu(soa.Self(), lock_);
+  ObjectRegistryEntry dummy;
+  std::pair<object_iterator, bool> result = object_to_entry_.insert(std::make_pair(o, dummy));
+  ObjectRegistryEntry& entry = result.first->second;
+  if (!result.second) {
+    // This object was already in our map.
+    entry.reference_count += 1;
+    return entry.id;
+  }
+
+  // This object isn't in the registry yet, so add it.
+  JNIEnv* env = soa.Env();
+
+  jobject local_reference = soa.AddLocalReference<jobject>(o);
+
+  entry.jni_reference_type = JNIWeakGlobalRefType;
+  entry.jni_reference = env->NewWeakGlobalRef(local_reference);
+  entry.reference_count = 1;
+  entry.id = next_id_++;
+
+  id_to_entry_.Put(entry.id, &entry);
+
+  env->DeleteLocalRef(local_reference);
+
+  return entry.id;
+}
+
+bool ObjectRegistry::Contains(mirror::Object* o) {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  return (object_to_entry_.find(o) != object_to_entry_.end());
+}
+
+void ObjectRegistry::Clear() {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  VLOG(jdwp) << "Object registry contained " << object_to_entry_.size() << " entries";
+
+  // Delete all the JNI references.
+  JNIEnv* env = self->GetJniEnv();
+  for (object_iterator it = object_to_entry_.begin(); it != object_to_entry_.end(); ++it) {
+    ObjectRegistryEntry& entry = (it->second);
+    if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+      env->DeleteWeakGlobalRef(entry.jni_reference);
+    } else {
+      env->DeleteGlobalRef(entry.jni_reference);
+    }
+  }
+
+  // Clear the maps.
+  object_to_entry_.clear();
+  id_to_entry_.clear();
+}
+
+mirror::Object* ObjectRegistry::InternalGet(JDWP::ObjectId id) {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  id_iterator it = id_to_entry_.find(id);
+  if (it == id_to_entry_.end()) {
+    return kInvalidObject;
+  }
+  ObjectRegistryEntry& entry = *(it->second);
+  return self->DecodeJObject(entry.jni_reference);
+}
+
+void ObjectRegistry::DisableCollection(JDWP::ObjectId id) {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  id_iterator it = id_to_entry_.find(id);
+  if (it == id_to_entry_.end()) {
+    return;
+  }
+  Promote(*(it->second));
+}
+
+void ObjectRegistry::EnableCollection(JDWP::ObjectId id) {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  id_iterator it = id_to_entry_.find(id);
+  if (it == id_to_entry_.end()) {
+    return;
+  }
+  Demote(*(it->second));
+}
+
+void ObjectRegistry::Demote(ObjectRegistryEntry& entry) {
+  if (entry.jni_reference_type == JNIGlobalRefType) {
+    Thread* self = Thread::Current();
+    JNIEnv* env = self->GetJniEnv();
+    jobject global = entry.jni_reference;
+    entry.jni_reference = env->NewWeakGlobalRef(entry.jni_reference);
+    entry.jni_reference_type = JNIWeakGlobalRefType;
+    env->DeleteGlobalRef(global);
+  }
+}
+
+void ObjectRegistry::Promote(ObjectRegistryEntry& entry) {
+  if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+    Thread* self = Thread::Current();
+    JNIEnv* env = self->GetJniEnv();
+    jobject weak = entry.jni_reference;
+    entry.jni_reference = env->NewGlobalRef(entry.jni_reference);
+    entry.jni_reference_type = JNIGlobalRefType;
+    env->DeleteWeakGlobalRef(weak);
+  }
+}
+
+bool ObjectRegistry::IsCollected(JDWP::ObjectId id) {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  id_iterator it = id_to_entry_.find(id);
+  if (it == id_to_entry_.end()) {
+    return true; // TODO: can we report that this was an invalid id?
+  }
+
+  ObjectRegistryEntry& entry = *(it->second);
+  if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+    JNIEnv* env = self->GetJniEnv();
+    return env->IsSameObject(entry.jni_reference, NULL); // Has the jweak been collected?
+  } else {
+    return false; // We hold a strong reference, so we know this is live.
+  }
+}
+
+void ObjectRegistry::DisposeObject(JDWP::ObjectId id, uint32_t reference_count) {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, lock_);
+  id_iterator it = id_to_entry_.find(id);
+  if (it == id_to_entry_.end()) {
+    return;
+  }
+
+  ObjectRegistryEntry& entry = *(it->second);
+  entry.reference_count -= reference_count;
+  if (entry.reference_count <= 0) {
+    JNIEnv* env = self->GetJniEnv();
+    mirror::Object* object = self->DecodeJObject(entry.jni_reference);
+    if (entry.jni_reference_type == JNIWeakGlobalRefType) {
+      env->DeleteWeakGlobalRef(entry.jni_reference);
+    } else {
+      env->DeleteGlobalRef(entry.jni_reference);
+    }
+    object_to_entry_.erase(object);
+    id_to_entry_.erase(id);
+  }
+}
+
+}  // namespace art
diff --git a/src/jdwp/object_registry.h b/src/jdwp/object_registry.h
new file mode 100644
index 0000000..0d39806
--- /dev/null
+++ b/src/jdwp/object_registry.h
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2013 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 <stdint.h>
+
+#include <map>
+
+#include "jdwp/jdwp.h"
+#include "mirror/class.h"
+#include "mirror/class-inl.h"
+#include "mirror/field-inl.h"
+#include "mirror/object-inl.h"
+#include "safe_map.h"
+
+namespace art {
+
+struct ObjectRegistryEntry {
+  // Is jni_reference a weak global or a regular global reference?
+  jobjectRefType jni_reference_type;
+
+  // The reference itself.
+  jobject jni_reference;
+
+  // A reference count, so we can implement DisposeObject.
+  int32_t reference_count;
+
+  // The corresponding id, so we only need one map lookup in Add.
+  JDWP::ObjectId id;
+};
+std::ostream& operator<<(std::ostream& os, const ObjectRegistryEntry& rhs);
+
+// Tracks those objects currently known to the debugger, so we can use consistent ids when
+// referring to them. Normally we keep JNI weak global references to objects, so they can
+// still be garbage collected. The debugger can ask us to retain objects, though, so we can
+// also promote references to regular JNI global references (and demote them back again if
+// the debugger tells us that's okay).
+class ObjectRegistry {
+ public:
+  ObjectRegistry();
+
+  JDWP::ObjectId Add(mirror::Object* o) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  JDWP::RefTypeId AddRefType(mirror::Class* c) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  template<typename T> T Get(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (id == 0) {
+      return NULL;
+    }
+    return reinterpret_cast<T>(InternalGet(id));
+  }
+
+  bool Contains(mirror::Object* o) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void Clear();
+
+  void DisableCollection(JDWP::ObjectId id);
+  void EnableCollection(JDWP::ObjectId id);
+
+  bool IsCollected(JDWP::ObjectId id);
+
+  void DisposeObject(JDWP::ObjectId id, uint32_t reference_count)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  // Returned by Get when passed an invalid object id.
+  static mirror::Object* const kInvalidObject;
+
+ private:
+  JDWP::ObjectId InternalAdd(mirror::Object* o) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  mirror::Object* InternalGet(JDWP::ObjectId id) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void Demote(ObjectRegistryEntry& entry);
+  void Promote(ObjectRegistryEntry& entry);
+
+  Mutex lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+
+  typedef std::map<mirror::Object*, ObjectRegistryEntry>::iterator object_iterator;
+  std::map<mirror::Object*, ObjectRegistryEntry> object_to_entry_ GUARDED_BY(lock_);
+
+  typedef SafeMap<JDWP::ObjectId, ObjectRegistryEntry*>::iterator id_iterator;
+  SafeMap<JDWP::ObjectId, ObjectRegistryEntry*> id_to_entry_ GUARDED_BY(lock_);
+
+  size_t next_id_ GUARDED_BY(lock_);
+};
+
+}  // namespace art
diff --git a/src/runtime.cc b/src/runtime.cc
index ae0f49b..07cd4c8 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -1034,7 +1034,6 @@
 }
 
 void Runtime::VisitNonThreadRoots(RootVisitor* visitor, void* arg) {
-  Dbg::VisitRoots(visitor, arg);
   java_vm_->VisitRoots(visitor, arg);
   if (pre_allocated_OutOfMemoryError_ != NULL) {
     visitor(pre_allocated_OutOfMemoryError_, arg);