[VM/Service] Change how the default Service ID zone is stored in an Isolate

This CL makes it so that the default Service ID zone of an Isolate will
now be stored in a MallocGrowableArray, in preparation for adding the
ability for Isolates to each store multiple Service ID zones.

TEST=CI

Issue: https://github.com/dart-lang/sdk/issues/55869
Change-Id: Ie729c1854faac1f61a466ae893ee8ad0a2128499
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/379543
Reviewed-by: Ben Konyi <bkonyi@google.com>
Commit-Queue: Derek Xu <derekx@google.com>
diff --git a/runtime/lib/developer.cc b/runtime/lib/developer.cc
index f9d649e..4f895b1 100644
--- a/runtime/lib/developer.cc
+++ b/runtime/lib/developer.cc
@@ -173,10 +173,7 @@
   return Object::null();
 #else
   GET_NON_NULL_NATIVE_ARGUMENT(Instance, instance, arguments->NativeArgAt(0));
-  JSONStream js;
-  RingServiceIdZone& ring_service_id_zone =
-      *reinterpret_cast<RingServiceIdZone*>(js.id_zone());
-  return String::New(ring_service_id_zone.GetServiceId(instance));
+  return String::New(isolate->GetDefaultServiceIdZone().GetServiceId(instance));
 #endif
 }
 
diff --git a/runtime/vm/heap/become.cc b/runtime/vm/heap/become.cc
index f93a4f2..5897884 100644
--- a/runtime/vm/heap/become.cc
+++ b/runtime/vm/heap/become.cc
@@ -353,10 +353,7 @@
 #ifndef PRODUCT
   isolate_group->ForEachIsolate(
       [&](Isolate* isolate) {
-        ObjectIdRing* ring = isolate->object_id_ring();
-        if (ring != nullptr) {
-          ring->VisitPointers(&pointer_visitor);
-        }
+        isolate->GetDefaultServiceIdZone().VisitPointers(pointer_visitor);
       },
       /*at_safepoint=*/true);
 #endif  // !PRODUCT
diff --git a/runtime/vm/heap/compactor.cc b/runtime/vm/heap/compactor.cc
index 83e72226..9c9f63f 100644
--- a/runtime/vm/heap/compactor.cc
+++ b/runtime/vm/heap/compactor.cc
@@ -509,10 +509,7 @@
           TIMELINE_FUNCTION_GC_DURATION(thread, "ForwardObjectIdRing");
           isolate_group_->ForEachIsolate(
               [&](Isolate* isolate) {
-                ObjectIdRing* ring = isolate->object_id_ring();
-                if (ring != nullptr) {
-                  ring->VisitPointers(compactor_);
-                }
+                isolate->GetDefaultServiceIdZone().VisitPointers(*compactor_);
               },
               /*at_safepoint=*/true);
           break;
diff --git a/runtime/vm/heap/incremental_compactor.cc b/runtime/vm/heap/incremental_compactor.cc
index e3f6421..481bc3f 100644
--- a/runtime/vm/heap/incremental_compactor.cc
+++ b/runtime/vm/heap/incremental_compactor.cc
@@ -681,10 +681,7 @@
       TIMELINE_FUNCTION_GC_DURATION(thread, "IdRing");
       isolate_group_->ForEachIsolate(
           [&](Isolate* isolate) {
-            ObjectIdRing* ring = isolate->object_id_ring();
-            if (ring != nullptr) {
-              ring->VisitPointers(&visitor);
-            }
+            isolate->GetDefaultServiceIdZone().VisitPointers(visitor);
           },
           /*at_safepoint=*/true);
     }
diff --git a/runtime/vm/heap/marker.cc b/runtime/vm/heap/marker.cc
index c7a7842..df733a2 100644
--- a/runtime/vm/heap/marker.cc
+++ b/runtime/vm/heap/marker.cc
@@ -778,7 +778,7 @@
       case kObjectIdRing: {
         TIMELINE_FUNCTION_GC_DURATION(Thread::Current(),
                                       "ProcessObjectIdTable");
-        isolate_group_->VisitObjectIdRingPointers(visitor);
+        isolate_group_->VisitPointersInDefaultServiceIdZone(*visitor);
         break;
       }
     }
diff --git a/runtime/vm/heap/scavenger.cc b/runtime/vm/heap/scavenger.cc
index 5224072..6f4eb07 100644
--- a/runtime/vm/heap/scavenger.cc
+++ b/runtime/vm/heap/scavenger.cc
@@ -1244,7 +1244,7 @@
 void Scavenger::IterateObjectIdTable(ObjectPointerVisitor* visitor) {
 #ifndef PRODUCT
   TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "IterateObjectIdTable");
-  heap_->isolate_group()->VisitObjectIdRingPointers(visitor);
+  heap_->isolate_group()->VisitPointersInDefaultServiceIdZone(*visitor);
 #endif  // !PRODUCT
 }
 
diff --git a/runtime/vm/isolate.cc b/runtime/vm/isolate.cc
index 05c642a..beb0cc6 100644
--- a/runtime/vm/isolate.cc
+++ b/runtime/vm/isolate.cc
@@ -1776,8 +1776,6 @@
 #if !defined(PRODUCT)
   delete debugger_;
   debugger_ = nullptr;
-  delete object_id_ring_;
-  object_id_ring_ = nullptr;
   delete pause_loop_monitor_;
   pause_loop_monitor_ = nullptr;
 #endif  // !defined(PRODUCT)
@@ -1871,7 +1869,10 @@
 
 #if !defined(PRODUCT)
   result->debugger_ = new Debugger(result);
-#endif
+  // Create the default Service ID zone for this isolate.
+  result->service_id_zones_.Add(
+      new RingServiceIdZone(ObjectIdRing::IdPolicy::kAllocateId));
+#endif  // !defined(PRODUCT)
 
   // Now we register the isolate in the group. From this point on any GC would
   // traverse the isolate roots (before this point, the roots are only pointing
@@ -2974,13 +2975,11 @@
   visitor->clear_gc_root_type();
 }
 
-void IsolateGroup::VisitObjectIdRingPointers(ObjectPointerVisitor* visitor) {
+void IsolateGroup::VisitPointersInDefaultServiceIdZone(
+    ObjectPointerVisitor& visitor) {
 #if !defined(PRODUCT)
   for (Isolate* isolate : isolates_) {
-    ObjectIdRing* ring = isolate->object_id_ring();
-    if (ring != nullptr) {
-      ring->VisitPointers(visitor);
-    }
+    isolate->GetDefaultServiceIdZone().VisitPointers(visitor);
   }
 #endif  // !defined(PRODUCT)
 }
@@ -3001,15 +3000,11 @@
 }
 
 #if !defined(PRODUCT)
-ObjectIdRing* Isolate::EnsureObjectIdRing() {
-  if (object_id_ring_ == nullptr) {
-    object_id_ring_ = new ObjectIdRing();
-  }
-  return object_id_ring_;
+RingServiceIdZone& Isolate::GetDefaultServiceIdZone() const {
+  ASSERT(service_id_zones_.length() == 1);
+  return *service_id_zones_.Last();
 }
-#endif  // !defined(PRODUCT)
 
-#ifndef PRODUCT
 static const char* ExceptionPauseInfoToServiceEnum(Dart_ExceptionPauseInfo pi) {
   switch (pi) {
     case kPauseOnAllExceptions:
@@ -3203,7 +3198,7 @@
   IsolatePauseEvent(this).PrintJSON(stream);
 }
 
-#endif
+#endif  // !defined(PRODUCT)
 
 void Isolate::set_tag_table(const GrowableObjectArray& value) {
   tag_table_ = value.ptr();
@@ -3237,9 +3232,7 @@
     const GrowableObjectArray& value) {
   registered_service_extension_handlers_ = value.ptr();
 }
-#endif  // !defined(PRODUCT)
 
-#ifndef PRODUCT
 ErrorPtr Isolate::InvokePendingServiceExtensionCalls() {
   GrowableObjectArray& calls =
       GrowableObjectArray::Handle(GetAndClearPendingServiceExtensionCalls());
@@ -3516,7 +3509,7 @@
   set_message_notify_callback(saved_notify_callback);
   Dart_ExitScope();
 }
-#endif  // !PRODUCT
+#endif  // !defined(PRODUCT)
 
 void Isolate::VisitIsolates(IsolateVisitor* visitor) {
   if (visitor == nullptr) {
diff --git a/runtime/vm/isolate.h b/runtime/vm/isolate.h
index 7e6328a..08f5974 100644
--- a/runtime/vm/isolate.h
+++ b/runtime/vm/isolate.h
@@ -31,6 +31,7 @@
 #include "vm/metrics.h"
 #include "vm/os_thread.h"
 #include "vm/random.h"
+#include "vm/service.h"
 #include "vm/tags.h"
 #include "vm/thread.h"
 #include "vm/thread_pool.h"
@@ -706,7 +707,7 @@
   void VisitSharedPointers(ObjectPointerVisitor* visitor);
   void VisitStackPointers(ObjectPointerVisitor* visitor,
                           ValidationPolicy validate_frames);
-  void VisitObjectIdRingPointers(ObjectPointerVisitor* visitor);
+  void VisitPointersInDefaultServiceIdZone(ObjectPointerVisitor& visitor);
   void VisitWeakPersistentHandles(HandleVisitor* visitor);
 
   // In precompilation we finalize all regular classes before compiling.
@@ -1247,8 +1248,7 @@
   }
 
 #if !defined(PRODUCT)
-  ObjectIdRing* object_id_ring() const { return object_id_ring_; }
-  ObjectIdRing* EnsureObjectIdRing();
+  RingServiceIdZone& GetDefaultServiceIdZone() const;
 #endif  // !defined(PRODUCT)
 
   bool IsDeoptimizing() const { return deopt_context_ != nullptr; }
@@ -1648,8 +1648,7 @@
   ISOLATE_METRIC_LIST(ISOLATE_METRIC_VARIABLE);
 #undef ISOLATE_METRIC_VARIABLE
 
-  // Ring buffer of objects assigned an id.
-  ObjectIdRing* object_id_ring_ = nullptr;
+  MallocGrowableArray<RingServiceIdZone*> service_id_zones_;
 #endif  // !defined(PRODUCT)
 
   // All other fields go here.
diff --git a/runtime/vm/json_stream.cc b/runtime/vm/json_stream.cc
index 93df64a..459709f 100644
--- a/runtime/vm/json_stream.cc
+++ b/runtime/vm/json_stream.cc
@@ -25,8 +25,7 @@
 
 JSONStream::JSONStream(intptr_t buf_size)
     : writer_(buf_size),
-      default_id_zone_(),
-      id_zone_(&default_id_zone_),
+      default_id_zone_(nullptr),
       reply_port_(ILLEGAL_PORT),
       seq_(nullptr),
       parameter_keys_(nullptr),
@@ -39,12 +38,10 @@
       count_(-1),
       include_private_members_(true),
       ignore_object_depth_(0) {
-  ObjectIdRing* ring = nullptr;
   Isolate* isolate = Isolate::Current();
   if (isolate != nullptr) {
-    ring = isolate->EnsureObjectIdRing();
+    default_id_zone_ = &isolate->GetDefaultServiceIdZone();
   }
-  default_id_zone_.Init(ring, ObjectIdRing::kAllocateId);
 }
 
 void JSONStream::Setup(Zone* zone,
@@ -353,8 +350,8 @@
 }
 
 void JSONStream::PrintServiceId(const Object& o) {
-  ASSERT(id_zone_ != nullptr);
-  PrintProperty("id", id_zone_->GetServiceId(o));
+  ASSERT(default_id_zone_ != nullptr);
+  PrintProperty("id", default_id_zone_->GetServiceId(o));
 }
 
 #define PRIVATE_NAME_CHECK()                                                   \
diff --git a/runtime/vm/json_stream.h b/runtime/vm/json_stream.h
index c020c2c..515bfec 100644
--- a/runtime/vm/json_stream.h
+++ b/runtime/vm/json_stream.h
@@ -99,9 +99,6 @@
 
   void PostReply();
 
-  void set_id_zone(ServiceIdZone* id_zone) { id_zone_ = id_zone; }
-  ServiceIdZone* id_zone() { return id_zone_; }
-
   TextBuffer* buffer() { return writer_.buffer(); }
   const char* ToCString() { return writer_.ToCString(); }
 
@@ -351,9 +348,9 @@
   }
 
   JSONWriter writer_;
-  // Default service id zone.
-  RingServiceIdZone default_id_zone_;
-  ServiceIdZone* id_zone_;
+  // The default Service ID zone of the isolate where the RPC associated with
+  // this |JSONStream| is being handled.
+  RingServiceIdZone* default_id_zone_;
   Dart_Port reply_port_;
   Instance* seq_;
   Array* parameter_keys_;
diff --git a/runtime/vm/object_id_ring.cc b/runtime/vm/object_id_ring.cc
index 8fb39e8..a403488 100644
--- a/runtime/vm/object_id_ring.cc
+++ b/runtime/vm/object_id_ring.cc
@@ -60,7 +60,7 @@
   return table_[index];
 }
 
-void ObjectIdRing::VisitPointers(ObjectPointerVisitor* visitor) {
+void ObjectIdRing::VisitPointers(ObjectPointerVisitor* visitor) const {
   ASSERT(table_ != nullptr);
   visitor->VisitPointers(table_, capacity_);
 }
@@ -179,7 +179,7 @@
   return id;
 }
 
-bool ObjectIdRing::IsValidContiguous(int32_t id) {
+bool ObjectIdRing::IsValidContiguous(int32_t id) const {
   ASSERT(id != kInvalidId);
   ASSERT((id >= 0) && (id < max_serial_));
   if (id >= serial_num_) {
diff --git a/runtime/vm/object_id_ring.h b/runtime/vm/object_id_ring.h
index 07bc854..2deae03 100644
--- a/runtime/vm/object_id_ring.h
+++ b/runtime/vm/object_id_ring.h
@@ -49,7 +49,7 @@
   // Returns Object::null() when the result is not kValid.
   ObjectPtr GetObjectForId(int32_t id, LookupResult* kind);
 
-  void VisitPointers(ObjectPointerVisitor* visitor);
+  void VisitPointers(ObjectPointerVisitor* visitor) const;
 
   void PrintJSON(JSONStream* js);
 
@@ -72,7 +72,7 @@
   int32_t AllocateNewId(ObjectPtr object);
   int32_t IndexOfId(int32_t id);
   int32_t IdOfIndex(int32_t index);
-  bool IsValidContiguous(int32_t id);
+  bool IsValidContiguous(int32_t id) const;
   bool IsValidId(int32_t id);
 
   DISALLOW_COPY_AND_ASSIGN(ObjectIdRing);
diff --git a/runtime/vm/object_id_ring_test.cc b/runtime/vm/object_id_ring_test.cc
index d391e89..2ded147 100644
--- a/runtime/vm/object_id_ring_test.cc
+++ b/runtime/vm/object_id_ring_test.cc
@@ -5,8 +5,7 @@
 #include "vm/object_id_ring.h"
 #include "platform/assert.h"
 #include "vm/dart_api_impl.h"
-#include "vm/dart_api_state.h"
-#include "vm/globals.h"
+#include "vm/service.h"
 #include "vm/symbols.h"
 #include "vm/unit_test.h"
 
@@ -16,26 +15,26 @@
 
 class ObjectIdRingTestHelper {
  public:
-  static void SetCapacityAndMaxSerial(ObjectIdRing* ring,
+  static void SetCapacityAndMaxSerial(ObjectIdRing& ring,
                                       int32_t capacity,
                                       int32_t max_serial) {
-    ring->SetCapacityAndMaxSerial(capacity, max_serial);
+    ring.SetCapacityAndMaxSerial(capacity, max_serial);
   }
 
-  static void ExpectIdIsValid(ObjectIdRing* ring, intptr_t id) {
-    EXPECT(ring->IsValidId(id));
+  static void ExpectIdIsValid(ObjectIdRing& ring, intptr_t id) {
+    EXPECT(ring.IsValidId(id));
   }
 
-  static void ExpectIdIsInvalid(ObjectIdRing* ring, intptr_t id) {
-    EXPECT(!ring->IsValidId(id));
+  static void ExpectIdIsInvalid(ObjectIdRing& ring, intptr_t id) {
+    EXPECT(!ring.IsValidId(id));
   }
 
-  static void ExpectIndexId(ObjectIdRing* ring, intptr_t index, intptr_t id) {
-    EXPECT_EQ(id, ring->IdOfIndex(index));
+  static void ExpectIndexId(ObjectIdRing& ring, intptr_t index, intptr_t id) {
+    EXPECT_EQ(id, ring.IdOfIndex(index));
   }
 
-  static void ExpectInvalidIndex(ObjectIdRing* ring, intptr_t index) {
-    EXPECT_EQ(-1, ring->IdOfIndex(index));
+  static void ExpectInvalidIndex(ObjectIdRing& ring, intptr_t index) {
+    EXPECT_EQ(-1, ring.IdOfIndex(index));
   }
 
   static ObjectPtr MakeString(const char* s) {
@@ -51,21 +50,20 @@
 
 // Test that serial number wrapping works.
 ISOLATE_UNIT_TEST_CASE(ObjectIdRingSerialWrapTest) {
-  Isolate* isolate = Isolate::Current();
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
+  ObjectIdRing ring;
   ObjectIdRingTestHelper::SetCapacityAndMaxSerial(ring, 2, 4);
   intptr_t id;
   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid;
-  id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("0"));
+  id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("0"));
   EXPECT_EQ(0, id);
   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0);
   ObjectIdRingTestHelper::ExpectInvalidIndex(ring, 1);
-  id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("1"));
+  id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("1"));
   EXPECT_EQ(1, id);
   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0);
   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1);
   // Test that id 1 gives us the "1" string.
-  ObjectIdRingTestHelper::ExpectString(ring->GetObjectForId(id, &kind), "1");
+  ObjectIdRingTestHelper::ExpectString(ring.GetObjectForId(id, &kind), "1");
   EXPECT_EQ(ObjectIdRing::kValid, kind);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0);
   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0);
@@ -74,7 +72,7 @@
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2);
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3);
   // We have wrapped, index 0 is being reused.
-  id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("2"));
+  id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("2"));
   EXPECT_EQ(2, id);
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 0);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1);
@@ -84,7 +82,7 @@
   // Index 1 has id 1.
   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1);
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3);
-  id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("3"));
+  id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("3"));
   EXPECT_EQ(3, id);
   // Index 0 has id 2.
   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 2);
@@ -94,25 +92,25 @@
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 1);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 2);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 3);
-  id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("4"));
+  id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("4"));
   EXPECT_EQ(0, id);
   // Index 0 has id 0.
   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0);
   // Index 1 has id 3.
   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 3);
-  ObjectIdRingTestHelper::ExpectString(ring->GetObjectForId(id, &kind), "4");
+  ObjectIdRingTestHelper::ExpectString(ring.GetObjectForId(id, &kind), "4");
   EXPECT_EQ(ObjectIdRing::kValid, kind);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0);
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 1);
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 2);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 3);
-  id = ring->GetIdForObject(ObjectIdRingTestHelper::MakeString("5"));
+  id = ring.GetIdForObject(ObjectIdRingTestHelper::MakeString("5"));
   EXPECT_EQ(1, id);
   // Index 0 has id 0.
   ObjectIdRingTestHelper::ExpectIndexId(ring, 0, 0);
   // Index 1 has id 1.
   ObjectIdRingTestHelper::ExpectIndexId(ring, 1, 1);
-  ObjectIdRingTestHelper::ExpectString(ring->GetObjectForId(id, &kind), "5");
+  ObjectIdRingTestHelper::ExpectString(ring.GetObjectForId(id, &kind), "5");
   EXPECT_EQ(ObjectIdRing::kValid, kind);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 0);
   ObjectIdRingTestHelper::ExpectIdIsValid(ring, 1);
@@ -120,6 +118,21 @@
   ObjectIdRingTestHelper::ExpectIdIsInvalid(ring, 3);
 }
 
+class ServiceIdZonePolicyOverrideScope : public ValueObject {
+ public:
+  explicit ServiceIdZonePolicyOverrideScope(ServiceIdZone& id_zone,
+                                            ObjectIdRing::IdPolicy new_policy)
+      : id_zone_(id_zone), original_policy_(id_zone.policy()) {
+    id_zone_.policy_ = new_policy;
+  }
+
+  ~ServiceIdZonePolicyOverrideScope() { id_zone_.policy_ = original_policy_; }
+
+ private:
+  ServiceIdZone& id_zone_;
+  ObjectIdRing::IdPolicy original_policy_;
+};
+
 // Test that the ring table is updated when the scavenger moves an object.
 TEST_CASE(ObjectIdRingScavengeMoveTest) {
   const char* kScriptChars =
@@ -136,30 +149,38 @@
   EXPECT_VALID(Dart_ListLength(result, &list_length));
   EXPECT_EQ(3, list_length);
 
-  Isolate* isolate = thread->isolate();
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
+  ServiceIdZone& id_zone = thread->isolate()->GetDefaultServiceIdZone();
   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid;
 
   {
     TransitionNativeToVM to_vm(thread);
-    ObjectPtr raw_obj = Api::UnwrapHandle(result);
+    const ObjectPtr raw_obj = Api::UnwrapHandle(result);
     // Located in new heap.
     EXPECT(raw_obj->IsNewObject());
     EXPECT_NE(Object::null(), raw_obj);
-    intptr_t raw_obj_id1 = ring->GetIdForObject(raw_obj);
+
+    intptr_t raw_obj_id1 = id_zone.GetIdForObject(raw_obj);
     EXPECT_EQ(0, raw_obj_id1);
-    // Get id 0 again.
-    EXPECT_EQ(raw_obj_id1,
-              ring->GetIdForObject(raw_obj, ObjectIdRing::kReuseId));
+    {
+      ServiceIdZonePolicyOverrideScope override(
+          id_zone, ObjectIdRing::IdPolicy::kReuseId);
+      // Get id 0 again.
+      EXPECT_EQ(raw_obj_id1, id_zone.GetIdForObject(raw_obj));
+    }
+
     // Add to ring a second time.
-    intptr_t raw_obj_id2 = ring->GetIdForObject(raw_obj);
+    intptr_t raw_obj_id2 = id_zone.GetIdForObject(raw_obj);
     EXPECT_EQ(1, raw_obj_id2);
-    // Get id 0 again.
-    EXPECT_EQ(raw_obj_id1,
-              ring->GetIdForObject(raw_obj, ObjectIdRing::kReuseId));
-    ObjectPtr raw_obj1 = ring->GetObjectForId(raw_obj_id1, &kind);
+    {
+      ServiceIdZonePolicyOverrideScope override(
+          id_zone, ObjectIdRing::IdPolicy::kReuseId);
+      // Get id 0 again.
+      EXPECT_EQ(raw_obj_id1, id_zone.GetIdForObject(raw_obj));
+    }
+
+    const ObjectPtr raw_obj1 = id_zone.GetObjectForId(raw_obj_id1, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
-    ObjectPtr raw_obj2 = ring->GetObjectForId(raw_obj_id2, &kind);
+    const ObjectPtr raw_obj2 = id_zone.GetObjectForId(raw_obj_id2, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
     EXPECT_NE(Object::null(), raw_obj1);
     EXPECT_NE(Object::null(), raw_obj2);
@@ -167,11 +188,14 @@
               UntaggedObject::ToAddr(raw_obj1));
     EXPECT_EQ(UntaggedObject::ToAddr(raw_obj),
               UntaggedObject::ToAddr(raw_obj2));
+
     // Force a scavenge.
     GCTestHelper::CollectNewSpace();
-    ObjectPtr raw_object_moved1 = ring->GetObjectForId(raw_obj_id1, &kind);
+    const ObjectPtr raw_object_moved1 =
+        id_zone.GetObjectForId(raw_obj_id1, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
-    ObjectPtr raw_object_moved2 = ring->GetObjectForId(raw_obj_id2, &kind);
+    const ObjectPtr raw_object_moved2 =
+        id_zone.GetObjectForId(raw_obj_id2, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
     EXPECT_NE(Object::null(), raw_object_moved1);
     EXPECT_NE(Object::null(), raw_object_moved2);
@@ -185,8 +209,12 @@
     // Test that we still point at the same list.
     moved_handle = Api::NewHandle(thread, raw_object_moved1);
     // Test id reuse.
-    EXPECT_EQ(raw_obj_id1,
-              ring->GetIdForObject(raw_object_moved1, ObjectIdRing::kReuseId));
+
+    {
+      ServiceIdZonePolicyOverrideScope override(
+          id_zone, ObjectIdRing::IdPolicy::kReuseId);
+      EXPECT_EQ(raw_obj_id1, id_zone.GetIdForObject(raw_object_moved1));
+    }
   }
   EXPECT_VALID(moved_handle);
   EXPECT(!Dart_IsNull(moved_handle));
@@ -197,8 +225,7 @@
 
 // Test that the ring table is updated when major GC runs.
 ISOLATE_UNIT_TEST_CASE(ObjectIdRingOldGCTest) {
-  Isolate* isolate = thread->isolate();
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
+  ServiceIdZone& id_zone = thread->isolate()->GetDefaultServiceIdZone();
 
   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid;
   intptr_t raw_obj_id1 = -1;
@@ -209,17 +236,19 @@
     EXPECT(!str.IsNull());
     EXPECT_EQ(3, str.Length());
 
-    ObjectPtr raw_obj = Object::RawCast(str.ptr());
+    const ObjectPtr raw_obj = Object::RawCast(str.ptr());
     // Verify that it is located in old heap.
     EXPECT(raw_obj->IsOldObject());
     EXPECT_NE(Object::null(), raw_obj);
-    raw_obj_id1 = ring->GetIdForObject(raw_obj);
+
+    raw_obj_id1 = id_zone.GetIdForObject(raw_obj);
     EXPECT_EQ(0, raw_obj_id1);
-    raw_obj_id2 = ring->GetIdForObject(raw_obj);
+    raw_obj_id2 = id_zone.GetIdForObject(raw_obj);
     EXPECT_EQ(1, raw_obj_id2);
-    ObjectPtr raw_obj1 = ring->GetObjectForId(raw_obj_id1, &kind);
+
+    const ObjectPtr raw_obj1 = id_zone.GetObjectForId(raw_obj_id1, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
-    ObjectPtr raw_obj2 = ring->GetObjectForId(raw_obj_id2, &kind);
+    const ObjectPtr raw_obj2 = id_zone.GetObjectForId(raw_obj_id2, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
     EXPECT_NE(Object::null(), raw_obj1);
     EXPECT_NE(Object::null(), raw_obj2);
@@ -232,10 +261,12 @@
   // Force a GC. No other reference to the old string exists, but the service id
   // should keep it alive.
   GCTestHelper::CollectOldSpace();
-  ObjectPtr raw_object_moved1 = ring->GetObjectForId(raw_obj_id1, &kind);
+  const ObjectPtr raw_object_moved1 =
+      id_zone.GetObjectForId(raw_obj_id1, &kind);
   EXPECT_EQ(ObjectIdRing::kValid, kind);
   EXPECT(raw_object_moved1->IsOneByteString());
-  ObjectPtr raw_object_moved2 = ring->GetObjectForId(raw_obj_id2, &kind);
+  const ObjectPtr raw_object_moved2 =
+      id_zone.GetObjectForId(raw_obj_id2, &kind);
   EXPECT_EQ(ObjectIdRing::kValid, kind);
   EXPECT(raw_object_moved2->IsOneByteString());
   EXPECT_EQ(raw_object_moved1, raw_object_moved2);
@@ -244,14 +275,13 @@
 // Test that the ring table correctly reports an entry as expired when it is
 // overridden by new entries.
 ISOLATE_UNIT_TEST_CASE(ObjectIdRingExpiredEntryTest) {
-  Isolate* isolate = Isolate::Current();
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
+  ObjectIdRing ring;
 
   // Insert an object and check we can look it up.
   String& obj = String::Handle(String::New("I will expire"));
-  intptr_t obj_id = ring->GetIdForObject(obj.ptr());
+  intptr_t obj_id = ring.GetIdForObject(obj.ptr());
   ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid;
-  ObjectPtr obj_lookup = ring->GetObjectForId(obj_id, &kind);
+  ObjectPtr obj_lookup = ring.GetObjectForId(obj_id, &kind);
   EXPECT_EQ(ObjectIdRing::kValid, kind);
   EXPECT_EQ(obj.ptr(), obj_lookup);
 
@@ -259,15 +289,15 @@
   Object& new_obj = Object::Handle();
   for (intptr_t i = 0; i < ObjectIdRing::kDefaultCapacity; i++) {
     new_obj = String::New("Bump");
-    intptr_t new_obj_id = ring->GetIdForObject(new_obj.ptr());
+    intptr_t new_obj_id = ring.GetIdForObject(new_obj.ptr());
     ObjectIdRing::LookupResult kind = ObjectIdRing::kInvalid;
-    ObjectPtr new_obj_lookup = ring->GetObjectForId(new_obj_id, &kind);
+    ObjectPtr new_obj_lookup = ring.GetObjectForId(new_obj_id, &kind);
     EXPECT_EQ(ObjectIdRing::kValid, kind);
     EXPECT_EQ(new_obj.ptr(), new_obj_lookup);
   }
 
   // Check our first entry reports it has expired.
-  obj_lookup = ring->GetObjectForId(obj_id, &kind);
+  obj_lookup = ring.GetObjectForId(obj_id, &kind);
   EXPECT_EQ(ObjectIdRing::kExpired, kind);
   EXPECT_NE(obj.ptr(), obj_lookup);
   EXPECT_EQ(Object::null(), obj_lookup);
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index ec363db..34692c5 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -52,6 +52,7 @@
 #include "vm/symbols.h"
 #include "vm/timeline.h"
 #include "vm/version.h"
+#include "vm/visitor.h"
 
 #if defined(SUPPORT_PERFETTO)
 #include "vm/perfetto_utils.h"
@@ -363,30 +364,36 @@
   return vm_name;
 }
 
-ServiceIdZone::ServiceIdZone() {}
+ServiceIdZone::ServiceIdZone(ObjectIdRing::IdPolicy policy) : policy_(policy) {}
 
 ServiceIdZone::~ServiceIdZone() {}
 
-RingServiceIdZone::RingServiceIdZone()
-    : ring_(nullptr), policy_(ObjectIdRing::kAllocateId) {}
+RingServiceIdZone::RingServiceIdZone(ObjectIdRing::IdPolicy policy)
+    : ServiceIdZone(policy), ring_() {}
 
 RingServiceIdZone::~RingServiceIdZone() {}
 
-void RingServiceIdZone::Init(ObjectIdRing* ring,
-                             ObjectIdRing::IdPolicy policy) {
-  ring_ = ring;
-  policy_ = policy;
+int32_t RingServiceIdZone::GetIdForObject(const ObjectPtr obj) {
+  return ring_.GetIdForObject(obj, policy());
+}
+
+ObjectPtr RingServiceIdZone::GetObjectForId(int32_t id,
+                                            ObjectIdRing::LookupResult* kind) {
+  return ring_.GetObjectForId(id, kind);
 }
 
 char* RingServiceIdZone::GetServiceId(const Object& obj) {
-  ASSERT(ring_ != nullptr);
   Thread* thread = Thread::Current();
   Zone* zone = thread->zone();
   ASSERT(zone != nullptr);
-  const intptr_t id = ring_->GetIdForObject(obj.ptr(), policy_);
+  const intptr_t id = GetIdForObject(obj.ptr());
   return zone->PrintToString("objects/%" Pd "", id);
 }
 
+void RingServiceIdZone::VisitPointers(ObjectPointerVisitor& visitor) const {
+  ring_.VisitPointers(&visitor);
+}
+
 // TODO(johnmccutchan): Unify embedder service handler lists and their APIs.
 EmbedderServiceHandler* Service::isolate_service_handler_head_ = nullptr;
 EmbedderServiceHandler* Service::root_service_handler_head_ = nullptr;
@@ -998,24 +1005,11 @@
     const char* id_zone_param = js.LookupParam("_idZone");
 
     if (id_zone_param != nullptr) {
-      // Override id zone.
-      if (strcmp("default", id_zone_param) == 0) {
-        // Ring with eager id allocation. This is the default ring and default
-        // policy.
-        // Nothing to do.
-      } else if (strcmp("default.reuse", id_zone_param) == 0) {
-        // Change the default ring's policy.
-        RingServiceIdZone* zone =
-            reinterpret_cast<RingServiceIdZone*>(js.id_zone());
-        zone->set_policy(ObjectIdRing::kReuseId);
-      } else {
-        // TODO(johnmccutchan): Support creating, deleting, and selecting
-        // custom service id zones.
-        // For now, always return an error.
-        PrintInvalidParamError(&js, "_idZone");
-        js.PostReply();
-        return T->StealStickyError();
-      }
+      // TODO(derekxu16): Support creating, deleting, and selecting custom
+      // Service ID zones. For now, always return an error.
+      PrintInvalidParamError(&js, "_idZone");
+      js.PostReply();
+      return T->StealStickyError();
     }
     const char* c_method_name = method_name.ToCString();
 
@@ -1836,13 +1830,12 @@
     return Object::null();
   }
 
-  ObjectIdRing* ring = thread->isolate()->EnsureObjectIdRing();
   intptr_t id = -1;
   if (!GetIntegerId(arg, &id)) {
     *kind = ObjectIdRing::kInvalid;
     return Object::null();
   }
-  return ring->GetObjectForId(id, kind);
+  return thread->isolate()->GetDefaultServiceIdZone().GetObjectForId(id, kind);
 }
 
 static ObjectPtr LookupClassMembers(Thread* thread,
diff --git a/runtime/vm/service.h b/runtime/vm/service.h
index ceb3332..2762a7b 100644
--- a/runtime/vm/service.h
+++ b/runtime/vm/service.h
@@ -6,6 +6,7 @@
 #define RUNTIME_VM_SERVICE_H_
 
 #include <atomic>
+#include <memory>
 
 #include "include/dart_tools_api.h"
 
@@ -36,13 +37,22 @@
 
 class ServiceIdZone {
  public:
-  ServiceIdZone();
+  explicit ServiceIdZone(ObjectIdRing::IdPolicy policy);
   virtual ~ServiceIdZone();
 
+  ObjectIdRing::IdPolicy policy() const { return policy_; }
+
+  virtual int32_t GetIdForObject(const ObjectPtr obj) = 0;
+  virtual ObjectPtr GetObjectForId(int32_t id,
+                                   ObjectIdRing::LookupResult* kind) = 0;
   // Returned string will be zone allocated.
   virtual char* GetServiceId(const Object& obj) = 0;
+  virtual void VisitPointers(ObjectPointerVisitor& visitor) const = 0;
 
  private:
+  ObjectIdRing::IdPolicy policy_;
+
+  friend class ServiceIdZonePolicyOverrideScope;
 };
 
 #define ISOLATE_SERVICE_ID_FORMAT_STRING "isolates/%" Pd64 ""
@@ -50,23 +60,19 @@
 #define ISOLATE_GROUP_SERVICE_ID_FORMAT_STRING                                 \
   ISOLATE_GROUP_SERVICE_ID_PREFIX "%" Pu64 ""
 
-class RingServiceIdZone : public ServiceIdZone {
+class RingServiceIdZone final : public ServiceIdZone {
  public:
-  RingServiceIdZone();
-  virtual ~RingServiceIdZone();
+  explicit RingServiceIdZone(ObjectIdRing::IdPolicy policy);
+  ~RingServiceIdZone() final;
 
-  void Init(ObjectIdRing* ring, ObjectIdRing::IdPolicy policy);
-
+  int32_t GetIdForObject(const ObjectPtr obj) final;
+  ObjectPtr GetObjectForId(int32_t id, ObjectIdRing::LookupResult* kind) final;
   // Returned string will be zone allocated.
-  virtual char* GetServiceId(const Object& obj);
-
-  void set_policy(ObjectIdRing::IdPolicy policy) { policy_ = policy; }
-
-  ObjectIdRing::IdPolicy policy() const { return policy_; }
+  char* GetServiceId(const Object& obj) final;
+  void VisitPointers(ObjectPointerVisitor& visitor) const final;
 
  private:
-  ObjectIdRing* ring_;
-  ObjectIdRing::IdPolicy policy_;
+  ObjectIdRing ring_;
 };
 
 class StreamInfo {
diff --git a/runtime/vm/service_test.cc b/runtime/vm/service_test.cc
index b7168cb..9c623d6 100644
--- a/runtime/vm/service_test.cc
+++ b/runtime/vm/service_test.cc
@@ -184,38 +184,32 @@
   }
 }
 
-ISOLATE_UNIT_TEST_CASE(Service_IdZones) {
+ISOLATE_UNIT_TEST_CASE(Service_RingServiceIdZonePolicies) {
   Zone* zone = thread->zone();
-  Isolate* isolate = thread->isolate();
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
 
   const String& test_a = String::Handle(zone, String::New("a"));
   const String& test_b = String::Handle(zone, String::New("b"));
   const String& test_c = String::Handle(zone, String::New("c"));
   const String& test_d = String::Handle(zone, String::New("d"));
 
-  // Both RingServiceIdZones share the same backing store and id space.
-
   // Always allocate a new id.
-  RingServiceIdZone always_new_zone;
-  always_new_zone.Init(ring, ObjectIdRing::kAllocateId);
-  EXPECT_STREQ("objects/0", always_new_zone.GetServiceId(test_a));
-  EXPECT_STREQ("objects/1", always_new_zone.GetServiceId(test_a));
-  EXPECT_STREQ("objects/2", always_new_zone.GetServiceId(test_a));
-  EXPECT_STREQ("objects/3", always_new_zone.GetServiceId(test_b));
-  EXPECT_STREQ("objects/4", always_new_zone.GetServiceId(test_c));
+  RingServiceIdZone always_allocate_zone(ObjectIdRing::kAllocateId);
+  EXPECT_STREQ("objects/0", always_allocate_zone.GetServiceId(test_a));
+  EXPECT_STREQ("objects/1", always_allocate_zone.GetServiceId(test_a));
+  EXPECT_STREQ("objects/2", always_allocate_zone.GetServiceId(test_a));
+  EXPECT_STREQ("objects/3", always_allocate_zone.GetServiceId(test_b));
+  EXPECT_STREQ("objects/4", always_allocate_zone.GetServiceId(test_c));
 
   // Reuse an existing id or allocate a new id.
-  RingServiceIdZone reuse_zone;
-  reuse_zone.Init(ring, ObjectIdRing::kReuseId);
-  EXPECT_STREQ("objects/0", reuse_zone.GetServiceId(test_a));
-  EXPECT_STREQ("objects/0", reuse_zone.GetServiceId(test_a));
-  EXPECT_STREQ("objects/3", reuse_zone.GetServiceId(test_b));
-  EXPECT_STREQ("objects/3", reuse_zone.GetServiceId(test_b));
-  EXPECT_STREQ("objects/4", reuse_zone.GetServiceId(test_c));
-  EXPECT_STREQ("objects/4", reuse_zone.GetServiceId(test_c));
-  EXPECT_STREQ("objects/5", reuse_zone.GetServiceId(test_d));
-  EXPECT_STREQ("objects/5", reuse_zone.GetServiceId(test_d));
+  RingServiceIdZone reuse_existing_zone(ObjectIdRing::kReuseId);
+  EXPECT_STREQ("objects/0", reuse_existing_zone.GetServiceId(test_a));
+  EXPECT_STREQ("objects/0", reuse_existing_zone.GetServiceId(test_a));
+  EXPECT_STREQ("objects/1", reuse_existing_zone.GetServiceId(test_b));
+  EXPECT_STREQ("objects/1", reuse_existing_zone.GetServiceId(test_b));
+  EXPECT_STREQ("objects/2", reuse_existing_zone.GetServiceId(test_c));
+  EXPECT_STREQ("objects/2", reuse_existing_zone.GetServiceId(test_c));
+  EXPECT_STREQ("objects/3", reuse_existing_zone.GetServiceId(test_d));
+  EXPECT_STREQ("objects/3", reuse_existing_zone.GetServiceId(test_d));
 }
 
 ISOLATE_UNIT_TEST_CASE(Service_Code) {
@@ -385,8 +379,8 @@
   const PcDescriptors& descriptors =
       PcDescriptors::Handle(code_c.pc_descriptors());
   EXPECT(!descriptors.IsNull());
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
-  intptr_t id = ring->GetIdForObject(descriptors.ptr());
+  ServiceIdZone& id_zone = isolate->GetDefaultServiceIdZone();
+  const char* id = id_zone.GetServiceId(descriptors);
 
   // Build a mock message handler and wrap it in a dart port.
   ServiceTestMessageHandler handler;
@@ -403,7 +397,7 @@
   // Fetch object.
   service_msg = EvalF(lib,
                       "[0, port, '0', 'getObject', "
-                      "['objectId'], ['objects/%" Pd "']]",
+                      "['objectId'], ['%s']]",
                       id);
   HandleIsolateMessage(isolate, service_msg);
   EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());
@@ -456,8 +450,8 @@
   const LocalVarDescriptors& descriptors =
       LocalVarDescriptors::Handle(code_c.GetLocalVarDescriptors());
   // Generate an ID for this object.
-  ObjectIdRing* ring = isolate->EnsureObjectIdRing();
-  intptr_t id = ring->GetIdForObject(descriptors.ptr());
+  ServiceIdZone& id_zone = isolate->GetDefaultServiceIdZone();
+  const char* id = id_zone.GetServiceId(descriptors);
 
   // Build a mock message handler and wrap it in a dart port.
   ServiceTestMessageHandler handler;
@@ -474,7 +468,7 @@
   // Fetch object.
   service_msg = EvalF(lib,
                       "[0, port, '0', 'getObject', "
-                      "['objectId'], ['objects/%" Pd "']]",
+                      "['objectId'], ['%s']]",
                       id);
   HandleIsolateMessage(isolate, service_msg);
   EXPECT_EQ(MessageHandler::kOK, handler.HandleNextMessage());