Implement RedefineClasses, also redefine multiple classes atomically.

We need to be able to redefine multiple classes atomically for JVMTI.
This implements that behavior. It also implements RedefineClasses
since until we have class transformation it is trivial.

Test: mma -j40 test-art-host

Change-Id: I80784f919a4366c465b93fede94f4bf763c0ee70
diff --git a/runtime/openjdkjvmti/OpenjdkJvmTi.cc b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
index 4334ca3..7bf2491 100644
--- a/runtime/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/runtime/openjdkjvmti/OpenjdkJvmTi.cc
@@ -632,7 +632,17 @@
   static jvmtiError RedefineClasses(jvmtiEnv* env,
                                     jint class_count,
                                     const jvmtiClassDefinition* class_definitions) {
-    return ERR(NOT_IMPLEMENTED);
+    std::string error_msg;
+    jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                                                art::Runtime::Current(),
+                                                art::Thread::Current(),
+                                                class_count,
+                                                class_definitions,
+                                                &error_msg);
+    if (res != OK) {
+      LOG(WARNING) << "FAILURE TO REDEFINE " << error_msg;
+    }
+    return res;
   }
 
   static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr) {
@@ -1180,34 +1190,6 @@
     return RetransformClassesWithHook(reinterpret_cast<ArtJvmTiEnv*>(env), classes, hook);
   }
 
-  static jvmtiError RedefineClassDirect(ArtJvmTiEnv* env,
-                                        jclass klass,
-                                        jint dex_size,
-                                        unsigned char* dex_file) {
-    if (!IsValidEnv(env)) {
-      return ERR(INVALID_ENVIRONMENT);
-    }
-    jvmtiError ret = OK;
-    std::string location;
-    if ((ret = GetClassLocation(env, klass, &location)) != OK) {
-      // TODO Do something more here? Maybe give log statements?
-      return ret;
-    }
-    std::string error;
-    ret = Redefiner::RedefineClass(env,
-                                    art::Runtime::Current(),
-                                    art::Thread::Current(),
-                                    klass,
-                                    location,
-                                    dex_size,
-                                    reinterpret_cast<uint8_t*>(dex_file),
-                                    &error);
-    if (ret != OK) {
-      LOG(WARNING) << "FAILURE TO REDEFINE " << error;
-    }
-    return ret;
-  }
-
   // TODO This will be called by the event handler for the art::ti Event Load Event
   static jvmtiError RetransformClassesWithHook(ArtJvmTiEnv* env,
                                                const std::vector<jclass>& classes,
@@ -1252,14 +1234,13 @@
            /*out*/&new_dex_data);
       // Check if anything actually changed.
       if ((new_data_len != 0 || new_dex_data != nullptr) && new_dex_data != dex_data) {
-        res = Redefiner::RedefineClass(env,
-                                       art::Runtime::Current(),
-                                       art::Thread::Current(),
-                                       klass,
-                                       location,
-                                       new_data_len,
-                                       new_dex_data,
-                                       &error);
+        jvmtiClassDefinition def = { klass, new_data_len, new_dex_data };
+        res = Redefiner::RedefineClasses(env,
+                                         art::Runtime::Current(),
+                                         art::Thread::Current(),
+                                         1,
+                                         &def,
+                                         &error);
         env->Deallocate(new_dex_data);
       }
       // Deallocate the old dex data.
@@ -1318,10 +1299,7 @@
   reinterpret_cast<void*>(JvmtiFunctions::RetransformClassWithHook),
   // nullptr,  // reserved1
   JvmtiFunctions::SetEventNotificationMode,
-  // SPECIAL FUNCTION: RedefineClassDirect Is normally reserved3
-  // TODO Remove once we have events working.
-  reinterpret_cast<void*>(JvmtiFunctions::RedefineClassDirect),
-  // nullptr,  // reserved3
+  nullptr,  // reserved3
   JvmtiFunctions::GetAllThreads,
   JvmtiFunctions::SuspendThread,
   JvmtiFunctions::ResumeThread,
diff --git a/runtime/openjdkjvmti/ti_redefine.cc b/runtime/openjdkjvmti/ti_redefine.cc
index 5bf8445..14477a1 100644
--- a/runtime/openjdkjvmti/ti_redefine.cc
+++ b/runtime/openjdkjvmti/ti_redefine.cc
@@ -53,6 +53,7 @@
 #include "object_lock.h"
 #include "runtime.h"
 #include "ScopedLocalRef.h"
+#include "transform.h"
 
 namespace openjdkjvmti {
 
@@ -207,7 +208,7 @@
 // Moves dex data to an anonymous, read-only mmap'd region.
 std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location,
                                                          jint data_len,
-                                                         unsigned char* dex_data,
+                                                         const unsigned char* dex_data,
                                                          std::string* error_msg) {
   std::unique_ptr<art::MemMap> map(art::MemMap::MapAnonymous(
       StringPrintf("%s-transformed", original_location.c_str()).c_str(),
@@ -227,34 +228,87 @@
   return map;
 }
 
+Redefiner::ClassRedefinition::ClassRedefinition(Redefiner* driver,
+                                                jclass klass,
+                                                const art::DexFile* redefined_dex_file,
+                                                const char* class_sig) :
+      driver_(driver), klass_(klass), dex_file_(redefined_dex_file), class_sig_(class_sig) {
+  GetMirrorClass()->MonitorEnter(driver_->self_);
+}
+
+Redefiner::ClassRedefinition::~ClassRedefinition() {
+  if (driver_ != nullptr) {
+    GetMirrorClass()->MonitorExit(driver_->self_);
+  }
+}
+
 // TODO This should handle doing multiple classes at once so we need to do less cleanup when things
 // go wrong.
-jvmtiError Redefiner::RedefineClass(ArtJvmTiEnv* env,
-                                    art::Runtime* runtime,
-                                    art::Thread* self,
-                                    jclass klass,
-                                    const std::string& original_dex_location,
-                                    jint data_len,
-                                    unsigned char* dex_data,
-                                    std::string* error_msg) {
+jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env,
+                                      art::Runtime* runtime,
+                                      art::Thread* self,
+                                      jint class_count,
+                                      const jvmtiClassDefinition* definitions,
+                                      std::string* error_msg) {
+  if (env == nullptr) {
+    *error_msg = "env was null!";
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (class_count < 0) {
+    *error_msg = "class_count was less then 0";
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (class_count == 0) {
+    // We don't actually need to do anything. Just return OK.
+    return OK;
+  } else if (definitions == nullptr) {
+    *error_msg = "null definitions!";
+    return ERR(NULL_POINTER);
+  }
+  // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
+  // are going to redefine.
+  art::jit::ScopedJitSuspend suspend_jit;
+  // Get shared mutator lock so we can lock all the classes.
+  art::ScopedObjectAccess soa(self);
+  std::vector<Redefiner::ClassRedefinition> redefinitions;
+  redefinitions.reserve(class_count);
+  Redefiner r(runtime, self, error_msg);
+  for (jint i = 0; i < class_count; i++) {
+    jvmtiError res = r.AddRedefinition(env, definitions[i]);
+    if (res != OK) {
+      return res;
+    }
+  }
+  return r.Run();
+}
+
+jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) {
+  std::string original_dex_location;
+  jvmtiError ret = OK;
+  if ((ret = GetClassLocation(env, def.klass, &original_dex_location))) {
+    *error_msg_ = "Unable to get original dex file location!";
+    return ret;
+  }
   std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
-                                                    data_len,
-                                                    dex_data,
-                                                    error_msg));
+                                                    def.class_byte_count,
+                                                    def.class_bytes,
+                                                    error_msg_));
   std::ostringstream os;
   char* generic_ptr_unused = nullptr;
   char* signature_ptr = nullptr;
-  if (env->GetClassSignature(klass, &signature_ptr, &generic_ptr_unused) != OK) {
-    signature_ptr = const_cast<char*>("<UNKNOWN CLASS>");
+  if (env->GetClassSignature(def.klass, &signature_ptr, &generic_ptr_unused) != OK) {
+    *error_msg_ = "A jclass passed in does not seem to be valid";
+    return ERR(INVALID_CLASS);
   }
+  // These will make sure we deallocate the signature.
+  JvmtiUniquePtr sig_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
+  JvmtiUniquePtr generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
   if (map.get() == nullptr) {
     os << "Failed to create anonymous mmap for modified dex file of class " << signature_ptr
-       << "in dex file " << original_dex_location << " because: " << *error_msg;
-    *error_msg = os.str();
+       << "in dex file " << original_dex_location << " because: " << *error_msg_;
+    *error_msg_ = os.str();
     return ERR(OUT_OF_MEMORY);
   }
   if (map->Size() < sizeof(art::DexFile::Header)) {
-    *error_msg = "Could not read dex file header because dex_data was too short";
+    *error_msg_ = "Could not read dex file header because dex_data was too short";
     return ERR(INVALID_CLASS_FORMAT);
   }
   uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
@@ -263,28 +317,21 @@
                                                                   std::move(map),
                                                                   /*verify*/true,
                                                                   /*verify_checksum*/true,
-                                                                  error_msg));
+                                                                  error_msg_));
   if (dex_file.get() == nullptr) {
-    os << "Unable to load modified dex file for " << signature_ptr << ": " << *error_msg;
-    *error_msg = os.str();
+    os << "Unable to load modified dex file for " << signature_ptr << ": " << *error_msg_;
+    *error_msg_ = os.str();
     return ERR(INVALID_CLASS_FORMAT);
   }
-  // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
-  // are going to redefine.
-  art::jit::ScopedJitSuspend suspend_jit;
-  // Get shared mutator lock.
-  art::ScopedObjectAccess soa(self);
-  art::StackHandleScope<1> hs(self);
-  Redefiner r(runtime, self, klass, signature_ptr, dex_file, error_msg);
-  // Lock around this class to avoid races.
-  art::ObjectLock<art::mirror::Class> lock(self, hs.NewHandle(r.GetMirrorClass()));
-  return r.Run();
+  redefinitions_.push_back(
+      Redefiner::ClassRedefinition(this, def.klass, dex_file.release(), signature_ptr));
+  return OK;
 }
 
 // TODO *MAJOR* This should return the actual source java.lang.DexFile object for the klass.
 // TODO Make mirror of DexFile and associated types to make this less hellish.
 // TODO Make mirror of BaseDexClassLoader and associated types to make this less hellish.
-art::mirror::Object* Redefiner::FindSourceDexFileObject(
+art::mirror::Object* Redefiner::ClassRedefinition::FindSourceDexFileObject(
     art::Handle<art::mirror::ClassLoader> loader) {
   const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;";
   const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;";
@@ -292,14 +339,14 @@
   const char* dex_path_list_name = "Ldalvik/system/DexPathList;";
   const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;";
 
-  CHECK(!self_->IsExceptionPending());
-  art::StackHandleScope<11> hs(self_);
-  art::ClassLinker* class_linker = runtime_->GetClassLinker();
+  CHECK(!driver_->self_->IsExceptionPending());
+  art::StackHandleScope<11> hs(driver_->self_);
+  art::ClassLinker* class_linker = driver_->runtime_->GetClassLinker();
 
   art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>(
       nullptr));
   art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass(
-      self_, dex_class_loader_name, null_loader)));
+      driver_->self_, dex_class_loader_name, null_loader)));
 
   // Get all the ArtFields so we can look in the BaseDexClassLoader
   art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField(
@@ -307,12 +354,12 @@
   CHECK(path_list_field != nullptr);
 
   art::ArtField* dex_path_list_element_field =
-      class_linker->FindClass(self_, dex_path_list_name, null_loader)
+      class_linker->FindClass(driver_->self_, dex_path_list_name, null_loader)
         ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name);
   CHECK(dex_path_list_element_field != nullptr);
 
   art::ArtField* element_dex_file_field =
-      class_linker->FindClass(self_, dex_path_list_element_name, null_loader)
+      class_linker->FindClass(driver_->self_, dex_path_list_element_name, null_loader)
         ->FindDeclaredInstanceField("dexFile", dex_file_name);
   CHECK(element_dex_file_field != nullptr);
 
@@ -327,11 +374,11 @@
   art::Handle<art::mirror::Object> path_list(
       hs.NewHandle(path_list_field->GetObject(loader.Get())));
   CHECK(path_list.Get() != nullptr);
-  CHECK(!self_->IsExceptionPending());
+  CHECK(!driver_->self_->IsExceptionPending());
   art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle(
       dex_path_list_element_field->GetObject(path_list.Get())->
       AsObjectArray<art::mirror::Object>()));
-  CHECK(!self_->IsExceptionPending());
+  CHECK(!driver_->self_->IsExceptionPending());
   CHECK(dex_elements_list.Get() != nullptr);
   size_t num_elements = dex_elements_list->GetLength();
   art::MutableHandle<art::mirror::Object> current_element(
@@ -343,9 +390,9 @@
   for (size_t i = 0; i < num_elements; i++) {
     current_element.Assign(dex_elements_list->Get(i));
     CHECK(current_element.Get() != nullptr);
-    CHECK(!self_->IsExceptionPending());
+    CHECK(!driver_->self_->IsExceptionPending());
     CHECK(dex_elements_list.Get() != nullptr);
-    CHECK_EQ(current_element->GetClass(), class_linker->FindClass(self_,
+    CHECK_EQ(current_element->GetClass(), class_linker->FindClass(driver_->self_,
                                                                   dex_path_list_element_name,
                                                                   null_loader));
     // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class
@@ -360,22 +407,23 @@
   return nullptr;
 }
 
-art::mirror::Class* Redefiner::GetMirrorClass() {
-  return self_->DecodeJObject(klass_)->AsClass();
+art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() {
+  return driver_->self_->DecodeJObject(klass_)->AsClass();
 }
 
-art::mirror::ClassLoader* Redefiner::GetClassLoader() {
+art::mirror::ClassLoader* Redefiner::ClassRedefinition::GetClassLoader() {
   return GetMirrorClass()->GetClassLoader();
 }
 
-art::mirror::DexCache* Redefiner::CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader) {
-  return runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get());
+art::mirror::DexCache* Redefiner::ClassRedefinition::CreateNewDexCache(
+    art::Handle<art::mirror::ClassLoader> loader) {
+  return driver_->runtime_->GetClassLinker()->RegisterDexFile(*dex_file_, loader.Get());
 }
 
 // TODO Really wishing I had that mirror of java.lang.DexFile now.
-art::mirror::LongArray* Redefiner::AllocateDexFileCookie(
+art::mirror::LongArray* Redefiner::ClassRedefinition::AllocateDexFileCookie(
     art::Handle<art::mirror::Object> java_dex_file_obj) {
-  art::StackHandleScope<2> hs(self_);
+  art::StackHandleScope<2> hs(driver_->self_);
   // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
   // the object is finalized. Since they always point to the same array if mCookie is not null we
   // just use the mInternalCookie field. We will update one or both of these fields later.
@@ -390,9 +438,9 @@
   CHECK(cookie.Get() != nullptr);
   CHECK_GE(cookie->GetLength(), 1);
   art::Handle<art::mirror::LongArray> new_cookie(
-      hs.NewHandle(art::mirror::LongArray::Alloc(self_, cookie->GetLength() + 1)));
+      hs.NewHandle(art::mirror::LongArray::Alloc(driver_->self_, cookie->GetLength() + 1)));
   if (new_cookie.Get() == nullptr) {
-    self_->AssertPendingOOMException();
+    driver_->self_->AssertPendingOOMException();
     return nullptr;
   }
   // Copy the oat-dex field at the start.
@@ -405,19 +453,21 @@
   return new_cookie.Get();
 }
 
-void Redefiner::RecordFailure(jvmtiError result, const std::string& error_msg) {
+void Redefiner::RecordFailure(jvmtiError result,
+                              const std::string& class_sig,
+                              const std::string& error_msg) {
   *error_msg_ = StringPrintf("Unable to perform redefinition of '%s': %s",
-                             class_sig_,
+                             class_sig.c_str(),
                              error_msg.c_str());
   result_ = result;
 }
 
-bool Redefiner::FinishRemainingAllocations(
+bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
     /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader,
     /*out*/art::MutableHandle<art::mirror::Object>* java_dex_file_obj,
     /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie,
     /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache) {
-  art::StackHandleScope<4> hs(self_);
+  art::StackHandleScope<4> hs(driver_->self_);
   // This shouldn't allocate
   art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
   if (loader.Get() == nullptr) {
@@ -433,15 +483,15 @@
   }
   art::Handle<art::mirror::LongArray> new_cookie(hs.NewHandle(AllocateDexFileCookie(dex_file_obj)));
   if (new_cookie.Get() == nullptr) {
-    self_->AssertPendingOOMException();
-    self_->ClearException();
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
     RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
     return false;
   }
   art::Handle<art::mirror::DexCache> dex_cache(hs.NewHandle(CreateNewDexCache(loader)));
   if (dex_cache.Get() == nullptr) {
-    self_->AssertPendingOOMException();
-    self_->ClearException();
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
     RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache");
     return false;
   }
@@ -453,13 +503,11 @@
 }
 
 struct CallbackCtx {
-  Redefiner* const r;
   art::LinearAlloc* allocator;
   std::unordered_map<art::ArtMethod*, art::ArtMethod*> obsolete_map;
   std::unordered_set<art::ArtMethod*> obsolete_methods;
 
-  CallbackCtx(Redefiner* self, art::LinearAlloc* alloc)
-      : r(self), allocator(alloc) {}
+  explicit CallbackCtx(art::LinearAlloc* alloc) : allocator(alloc) {}
 };
 
 void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
@@ -472,11 +520,12 @@
 
 // This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is
 // updated so they will be run.
-void Redefiner::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) {
+// TODO Rewrite so we can do this only once regardless of how many redefinitions there are.
+void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) {
   art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking");
   art::mirror::ClassExt* ext = art_klass->GetExtData();
   CHECK(ext->GetObsoleteMethods() != nullptr);
-  CallbackCtx ctx(this, art_klass->GetClassLoader()->GetAllocator());
+  CallbackCtx ctx(art_klass->GetClassLoader()->GetAllocator());
   // Add all the declared methods to the map
   for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
     ctx.obsolete_methods.insert(&m);
@@ -484,7 +533,7 @@
     DCHECK(!m.IsIntrinsic());
   }
   {
-    art::MutexLock mu(self_, *art::Locks::thread_list_lock_);
+    art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_);
     art::ThreadList* list = art::Runtime::Current()->GetThreadList();
     list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx));
   }
@@ -493,7 +542,7 @@
 
 // Fills the obsolete method map in the art_klass's extData. This is so obsolete methods are able to
 // figure out their DexCaches.
-void Redefiner::FillObsoleteMethodMap(
+void Redefiner::ClassRedefinition::FillObsoleteMethodMap(
     art::mirror::Class* art_klass,
     const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes) {
   int32_t index = 0;
@@ -532,9 +581,9 @@
   i->ReJitEverything("libOpenJkdJvmti - Class Redefinition");
 }
 
-bool Redefiner::CheckClass() {
+bool Redefiner::ClassRedefinition::CheckClass() {
   // TODO Might just want to put it in a ObjPtr and NoSuspend assert.
-  art::StackHandleScope<1> hs(self_);
+  art::StackHandleScope<1> hs(driver_->self_);
   // Easy check that only 1 class def is present.
   if (dex_file_->NumClassDefs() != 1) {
     RecordFailure(ERR(ILLEGAL_ARGUMENT),
@@ -607,14 +656,15 @@
     }
   }
   LOG(WARNING) << "No verification is done on annotations of redefined classes.";
+  LOG(WARNING) << "Bytecodes of redefinitions are not verified.";
 
   return true;
 }
 
 // TODO Move this to use IsRedefinable when that function is made.
-bool Redefiner::CheckRedefinable() {
+bool Redefiner::ClassRedefinition::CheckRedefinable() {
   std::string err;
-  art::StackHandleScope<1> hs(self_);
+  art::StackHandleScope<1> hs(driver_->self_);
 
   art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
   jvmtiError res = Redefiner::GetClassRedefinitionError(h_klass, &err);
@@ -626,46 +676,200 @@
   }
 }
 
-bool Redefiner::CheckRedefinitionIsValid() {
+bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
   return CheckRedefinable() &&
       CheckClass() &&
       CheckSameFields() &&
       CheckSameMethods();
 }
 
+// A wrapper that lets us hold onto the arbitrary sized data needed for redefinitions in a
+// reasonably sane way. This adds no fields to the normal ObjectArray. By doing this we can avoid
+// having to deal with the fact that we need to hold an arbitrary number of references live.
+class RedefinitionDataHolder {
+ public:
+  enum DataSlot : int32_t {
+    kSlotSourceClassLoader = 0,
+    kSlotJavaDexFile = 1,
+    kSlotNewDexFileCookie = 2,
+    kSlotNewDexCache = 3,
+    kSlotMirrorClass = 4,
+
+    // Must be last one.
+    kNumSlots = 5,
+  };
+
+  // This needs to have a HandleScope passed in that is capable of creating a new Handle without
+  // overflowing. Only one handle will be created. This object has a lifetime identical to that of
+  // the passed in handle-scope.
+  RedefinitionDataHolder(art::StackHandleScope<1>* hs,
+                         art::Runtime* runtime,
+                         art::Thread* self,
+                         int32_t num_redefinitions) REQUIRES_SHARED(art::Locks::mutator_lock_) :
+    arr_(
+      hs->NewHandle(
+        art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+            self,
+            runtime->GetClassLinker()->GetClassRoot(art::ClassLinker::kObjectArrayClass),
+            num_redefinitions * kNumSlots))) {}
+
+  bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return arr_.IsNull();
+  }
+
+  // TODO Maybe make an iterable view type to simplify using this.
+  art::mirror::ClassLoader* GetSourceClassLoader(jint klass_index)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::ClassLoader*>(GetSlot(klass_index, kSlotSourceClassLoader));
+  }
+  art::mirror::Object* GetJavaDexFile(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return GetSlot(klass_index, kSlotJavaDexFile);
+  }
+  art::mirror::LongArray* GetNewDexFileCookie(jint klass_index)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::LongArray*>(GetSlot(klass_index, kSlotNewDexFileCookie));
+  }
+  art::mirror::DexCache* GetNewDexCache(jint klass_index)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::DexCache*>(GetSlot(klass_index, kSlotNewDexCache));
+  }
+  art::mirror::Class* GetMirrorClass(jint klass_index) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass));
+  }
+
+  void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotSourceClassLoader, loader);
+  }
+  void SetJavaDexFile(jint klass_index, art::mirror::Object* dexfile)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotJavaDexFile, dexfile);
+  }
+  void SetNewDexFileCookie(jint klass_index, art::mirror::LongArray* cookie)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewDexFileCookie, cookie);
+  }
+  void SetNewDexCache(jint klass_index, art::mirror::DexCache* cache)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewDexCache, cache);
+  }
+  void SetMirrorClass(jint klass_index, art::mirror::Class* klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotMirrorClass, klass);
+  }
+
+  int32_t Length() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return arr_->GetLength() / kNumSlots;
+  }
+
+ private:
+  art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_;
+
+  art::mirror::Object* GetSlot(jint klass_index,
+                               DataSlot slot) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK_LT(klass_index, Length());
+    return arr_->Get((kNumSlots * klass_index) + slot);
+  }
+
+  void SetSlot(jint klass_index,
+               DataSlot slot,
+               art::ObjPtr<art::mirror::Object> obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK(!art::Runtime::Current()->IsActiveTransaction());
+    DCHECK_LT(klass_index, Length());
+    arr_->Set<false>((kNumSlots * klass_index) + slot, obj);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
+};
+
+bool Redefiner::CheckAllRedefinitionAreValid() {
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    if (!redef.CheckRedefinitionIsValid()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Redefiner::EnsureAllClassAllocationsFinished() {
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    if (!redef.EnsureClassAllocationsFinished()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
+  int32_t cnt = 0;
+  art::StackHandleScope<4> hs(self_);
+  art::MutableHandle<art::mirror::Object> java_dex_file(hs.NewHandle<art::mirror::Object>(nullptr));
+  art::MutableHandle<art::mirror::ClassLoader> source_class_loader(
+      hs.NewHandle<art::mirror::ClassLoader>(nullptr));
+  art::MutableHandle<art::mirror::LongArray> new_dex_file_cookie(
+      hs.NewHandle<art::mirror::LongArray>(nullptr));
+  art::MutableHandle<art::mirror::DexCache> new_dex_cache(
+      hs.NewHandle<art::mirror::DexCache>(nullptr));
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    // Reset the out pointers to null
+    source_class_loader.Assign(nullptr);
+    java_dex_file.Assign(nullptr);
+    new_dex_file_cookie.Assign(nullptr);
+    new_dex_cache.Assign(nullptr);
+    // Allocate the data this redefinition requires.
+    if (!redef.FinishRemainingAllocations(&source_class_loader,
+                                          &java_dex_file,
+                                          &new_dex_file_cookie,
+                                          &new_dex_cache)) {
+      return false;
+    }
+    // Save the allocated data into the holder.
+    holder.SetSourceClassLoader(cnt, source_class_loader.Get());
+    holder.SetJavaDexFile(cnt, java_dex_file.Get());
+    holder.SetNewDexFileCookie(cnt, new_dex_file_cookie.Get());
+    holder.SetNewDexCache(cnt, new_dex_cache.Get());
+    holder.SetMirrorClass(cnt, redef.GetMirrorClass());
+    cnt++;
+  }
+  return true;
+}
+
+void Redefiner::ClassRedefinition::ReleaseDexFile() {
+  dex_file_.release();
+}
+
+void Redefiner::ReleaseAllDexFiles() {
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    redef.ReleaseDexFile();
+  }
+}
+
 jvmtiError Redefiner::Run() {
-  art::StackHandleScope<5> hs(self_);
-  // TODO We might want to have a global lock (or one based on the class being redefined at least)
-  // in order to make cleanup easier. Not a huge deal though.
-  //
+  art::StackHandleScope<1> hs(self_);
+  // Allocate an array to hold onto all java temporary objects associated with this redefinition.
+  // We will let this be collected after the end of this function.
+  RedefinitionDataHolder holder(&hs, runtime_, self_, redefinitions_.size());
+  if (holder.IsNull()) {
+    self_->AssertPendingOOMException();
+    self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate storage for temporaries");
+    return result_;
+  }
+
   // First we just allocate the ClassExt and its fields that we need. These can be updated
   // atomically without any issues (since we allocate the map arrays as empty) so we don't bother
   // doing a try loop. The other allocations we need to ensure that nothing has changed in the time
   // between allocating them and pausing all threads before we can update them so we need to do a
   // try loop.
-  if (!CheckRedefinitionIsValid() || !EnsureClassAllocationsFinished()) {
-    return result_;
-  }
-  art::MutableHandle<art::mirror::ClassLoader> source_class_loader(
-      hs.NewHandle<art::mirror::ClassLoader>(nullptr));
-  art::MutableHandle<art::mirror::Object> java_dex_file(
-      hs.NewHandle<art::mirror::Object>(nullptr));
-  art::MutableHandle<art::mirror::LongArray> new_dex_file_cookie(
-      hs.NewHandle<art::mirror::LongArray>(nullptr));
-  art::MutableHandle<art::mirror::DexCache> new_dex_cache(
-      hs.NewHandle<art::mirror::DexCache>(nullptr));
-  if (!FinishRemainingAllocations(&source_class_loader,
-                                  &java_dex_file,
-                                  &new_dex_file_cookie,
-                                  &new_dex_cache)) {
+  if (!CheckAllRedefinitionAreValid() ||
+      !EnsureAllClassAllocationsFinished() ||
+      !FinishAllRemainingAllocations(holder)) {
     // TODO Null out the ClassExt fields we allocated (if possible, might be racing with another
     // redefineclass call which made it even bigger. Leak shouldn't be huge (2x array of size
-    // declared_methods_.length) but would be good to get rid of.
-    // new_dex_file_cookie & new_dex_cache should be cleaned up by the GC.
+    // declared_methods_.length) but would be good to get rid of. All other allocations should be
+    // cleaned up by the GC eventually.
     return result_;
   }
-  // Get the mirror class now that we aren't allocating anymore.
-  art::Handle<art::mirror::Class> art_class(hs.NewHandle(GetMirrorClass()));
   // Disable GC and wait for it to be done if we are a moving GC.  This is fine since we are done
   // allocating so no deadlocks.
   art::gc::Heap* heap = runtime_->GetHeap();
@@ -673,31 +877,29 @@
     // GC moving objects can cause deadlocks as we are deoptimizing the stack.
     heap->IncrementDisableMovingGC(self_);
   }
-  // Enable assertion that this thread isn't interrupted during this installation.
-  // After this we will need to do real cleanup in case of failure. Prior to this we could simply
-  // return and would let everything get cleaned up or harmlessly leaked.
   // Do transition to final suspension
   // TODO We might want to give this its own suspended state!
   // TODO This isn't right. We need to change state without any chance of suspend ideally!
   self_->TransitionFromRunnableToSuspended(art::ThreadState::kNative);
   runtime_->GetThreadList()->SuspendAll(
-      "Final installation of redefined Class!", /*long_suspend*/true);
+      "Final installation of redefined Classes!", /*long_suspend*/true);
   // TODO We need to invalidate all breakpoints in the redefined class with the debugger.
   // TODO We need to deal with any instrumentation/debugger deoptimized_methods_.
   // TODO We need to update all debugger MethodIDs so they note the method they point to is
   // obsolete or implement some other well defined semantics.
   // TODO We need to decide on & implement semantics for JNI jmethodids when we redefine methods.
-  // TODO Might want to move this into a different type.
-  // Now we reach the part where we must do active cleanup if something fails.
-  // TODO We should really Retry if this fails instead of simply aborting.
-  // Set the new DexFileCookie returns the original so we can fix it back up if redefinition fails
-  art::ObjPtr<art::mirror::LongArray> original_dex_file_cookie(nullptr);
-  UpdateJavaDexFile(java_dex_file.Get(), new_dex_file_cookie.Get(), &original_dex_file_cookie);
-  FindAndAllocateObsoleteMethods(art_class.Get());
-  UpdateClass(art_class.Get(), new_dex_cache.Get());
+  int32_t cnt = 0;
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    art::mirror::Class* klass = holder.GetMirrorClass(cnt);
+    redef.UpdateJavaDexFile(holder.GetJavaDexFile(cnt), holder.GetNewDexFileCookie(cnt));
+    // TODO Rewrite so we don't do a stack walk for each and every class.
+    redef.FindAndAllocateObsoleteMethods(klass);
+    redef.UpdateClass(klass, holder.GetNewDexCache(cnt));
+    cnt++;
+  }
   // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
   // pointers to their ArtMethod's stashed in registers that they then use to attempt to hit the
-  // DexCache.
+  // DexCache. (b/33630159)
   // TODO This can fail (leave some methods optimized) near runtime methods (including
   // quick-to-interpreter transition function).
   // TODO We probably don't need this at all once we have a way to ensure that the
@@ -705,26 +907,25 @@
   // stack-walker.
   EnsureObsoleteMethodsAreDeoptimized();
   // TODO Verify the new Class.
-  // TODO   Failure then undo updates to class
   // TODO Shrink the obsolete method maps if possible?
   // TODO find appropriate class loader.
   // TODO Put this into a scoped thing.
   runtime_->GetThreadList()->ResumeAll();
   // Get back shared mutator lock as expected for return.
   self_->TransitionFromSuspendedToRunnable();
-  // TODO Do the dex_file_ release at a more reasonable place. This works but it muddles who really
-  // owns the DexFile.
-  dex_file_.release();
+  // TODO Do the dex_file release at a more reasonable place. This works but it muddles who really
+  // owns the DexFile and when ownership is transferred.
+  ReleaseAllDexFiles();
   if (heap->IsGcConcurrentAndMoving()) {
     heap->DecrementDisableMovingGC(self_);
   }
   return OK;
 }
 
-void Redefiner::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
-                              art::ObjPtr<art::mirror::DexCache> new_dex_cache,
-                              const art::DexFile::ClassDef& class_def) {
-  art::ClassLinker* linker = runtime_->GetClassLinker();
+void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
+                                                 art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+                                                 const art::DexFile::ClassDef& class_def) {
+  art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
   art::PointerSize image_pointer_size = linker->GetImagePointerSize();
   const art::DexFile::TypeId& declaring_class_id = dex_file_->GetTypeId(class_def.class_idx_);
   const art::DexFile& old_dex_file = mclass->GetDexFile();
@@ -759,14 +960,14 @@
     method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
     method.SetDexCacheResolvedTypes(new_dex_cache->GetResolvedTypes(), image_pointer_size);
     // Notify the jit that this method is redefined.
-    art::jit::Jit* jit = runtime_->GetJit();
+    art::jit::Jit* jit = driver_->runtime_->GetJit();
     if (jit != nullptr) {
       jit->GetCodeCache()->NotifyMethodRedefined(&method);
     }
   }
 }
 
-void Redefiner::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) {
+void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) {
   // TODO The IFields & SFields pointers should be combined like the methods_ arrays were.
   for (auto fields_iter : {mclass->GetIFields(), mclass->GetSFields()}) {
     for (art::ArtField& field : fields_iter) {
@@ -787,10 +988,10 @@
 }
 
 // Performs updates to class that will allow us to verify it.
-void Redefiner::UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
-                            art::ObjPtr<art::mirror::DexCache> new_dex_cache) {
+void Redefiner::ClassRedefinition::UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
+                                               art::ObjPtr<art::mirror::DexCache> new_dex_cache) {
   const art::DexFile::ClassDef* class_def = art::OatFile::OatDexFile::FindClassDef(
-      *dex_file_, class_sig_, art::ComputeModifiedUtf8Hash(class_sig_));
+      *dex_file_, class_sig_.c_str(), art::ComputeModifiedUtf8Hash(class_sig_.c_str()));
   DCHECK(class_def != nullptr);
   UpdateMethods(mclass, new_dex_cache, *class_def);
   UpdateFields(mclass);
@@ -800,12 +1001,12 @@
   // to call GetReturnTypeDescriptor and GetParameterTypeList above).
   mclass->SetDexCache(new_dex_cache.Ptr());
   mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(*class_def));
-  mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_)));
+  mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str())));
 }
 
-void Redefiner::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
-                                  art::ObjPtr<art::mirror::LongArray> new_cookie,
-                                  /*out*/art::ObjPtr<art::mirror::LongArray>* original_cookie) {
+void Redefiner::ClassRedefinition::UpdateJavaDexFile(
+    art::ObjPtr<art::mirror::Object> java_dex_file,
+    art::ObjPtr<art::mirror::LongArray> new_cookie) {
   art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
       "mInternalCookie", "Ljava/lang/Object;");
   art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
@@ -816,7 +1017,6 @@
   art::ObjPtr<art::mirror::LongArray> orig_cookie(
       cookie_field->GetObject(java_dex_file)->AsLongArray());
   internal_cookie_field->SetObject<false>(java_dex_file, new_cookie);
-  *original_cookie = orig_internal_cookie;
   if (!orig_cookie.IsNull()) {
     cookie_field->SetObject<false>(java_dex_file, new_cookie);
   }
@@ -824,21 +1024,22 @@
 
 // This function does all (java) allocations we need to do for the Class being redefined.
 // TODO Change this name maybe?
-bool Redefiner::EnsureClassAllocationsFinished() {
-  art::StackHandleScope<2> hs(self_);
-  art::Handle<art::mirror::Class> klass(hs.NewHandle(self_->DecodeJObject(klass_)->AsClass()));
+bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished() {
+  art::StackHandleScope<2> hs(driver_->self_);
+  art::Handle<art::mirror::Class> klass(hs.NewHandle(
+      driver_->self_->DecodeJObject(klass_)->AsClass()));
   if (klass.Get() == nullptr) {
     RecordFailure(ERR(INVALID_CLASS), "Unable to decode class argument!");
     return false;
   }
   // Allocate the classExt
-  art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self_)));
+  art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(driver_->self_)));
   if (ext.Get() == nullptr) {
     // No memory. Clear exception (it's not useful) and return error.
     // TODO This doesn't need to be fatal. We could just not support obsolete methods after hitting
     // this case.
-    self_->AssertPendingOOMException();
-    self_->ClearException();
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
     RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt");
     return false;
   }
@@ -849,12 +1050,12 @@
   // TODO Clear these after we walk the stacks in order to free them in the (likely?) event there
   // are no obsolete methods.
   {
-    art::ObjectLock<art::mirror::ClassExt> lock(self_, ext);
+    art::ObjectLock<art::mirror::ClassExt> lock(driver_->self_, ext);
     if (!ext->ExtendObsoleteArrays(
-          self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
+          driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
       // OOM. Clear exception and return error.
-      self_->AssertPendingOOMException();
-      self_->ClearException();
+      driver_->self_->AssertPendingOOMException();
+      driver_->self_->ClearException();
       RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate/extend obsolete methods map");
       return false;
     }
diff --git a/runtime/openjdkjvmti/ti_redefine.h b/runtime/openjdkjvmti/ti_redefine.h
index 5852309..8626bc5 100644
--- a/runtime/openjdkjvmti/ti_redefine.h
+++ b/runtime/openjdkjvmti/ti_redefine.h
@@ -63,50 +63,154 @@
 
 namespace openjdkjvmti {
 
+class RedefinitionDataHolder;
+
 // Class that can redefine a single class's methods.
 // TODO We should really make this be driven by an outside class so we can do multiple classes at
 // the same time and have less required cleanup.
 class Redefiner {
  public:
-  // Redefine the given class with the given dex data. Note this function does not take ownership of
-  // the dex_data pointer. It is not used after this call however and may be freed if desired.
+  // Redefine the given classes with the given dex data. Note this function does not take ownership
+  // of the dex_data pointers. It is not used after this call however and may be freed if desired.
   // The caller is responsible for freeing it. The runtime makes its own copy of the data.
-  static jvmtiError RedefineClass(ArtJvmTiEnv* env,
-                                  art::Runtime* runtime,
-                                  art::Thread* self,
-                                  jclass klass,
-                                  const std::string& original_dex_location,
-                                  jint data_len,
-                                  unsigned char* dex_data,
-                                  std::string* error_msg);
+  static jvmtiError RedefineClasses(ArtJvmTiEnv* env,
+                                    art::Runtime* runtime,
+                                    art::Thread* self,
+                                    jint class_count,
+                                    const jvmtiClassDefinition* definitions,
+                                    std::string* error_msg);
 
   static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
 
  private:
+  class ClassRedefinition {
+   public:
+    ClassRedefinition(Redefiner* driver,
+                      jclass klass,
+                      const art::DexFile* redefined_dex_file,
+                      const char* class_sig)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor.
+    ~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS;
+
+    // Move constructor so we can put these into a vector.
+    ClassRedefinition(ClassRedefinition&& other)
+        : driver_(other.driver_),
+          klass_(other.klass_),
+          dex_file_(std::move(other.dex_file_)),
+          class_sig_(std::move(other.class_sig_)) {
+      other.driver_ = nullptr;
+    }
+
+    art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
+    art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath.
+    // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from.
+    art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Allocates and fills the new DexFileCookie
+    art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> j_dex_file_obj)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    void RecordFailure(jvmtiError e, const std::string& err) {
+      driver_->RecordFailure(e, class_sig_, err);
+    }
+
+    bool FinishRemainingAllocations(
+          /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader,
+          /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj,
+          /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie,
+          /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void FillObsoleteMethodMap(
+        art::mirror::Class* art_klass,
+        const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes)
+          REQUIRES(art::Locks::mutator_lock_);
+
+
+    // Checks that the dex file contains only the single expected class and that the top-level class
+    // data has not been modified in an incompatible manner.
+    bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Preallocates all needed allocations in klass so that we can pause execution safely.
+    // TODO We should be able to free the arrays if they end up not being used. Investigate doing
+    // this in the future. For now we will just take the memory hit.
+    bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // This will check that no constraints are violated (more than 1 class in dex file, any changes
+    // in number/declaration of methods & fields, changes in access flags, etc.)
+    bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Checks that the class can even be redefined.
+    bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Checks that the dex file does not add/remove methods.
+    bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      LOG(WARNING) << "methods are not checked for modification currently";
+      return true;
+    }
+
+    // Checks that the dex file does not modify fields
+    bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      LOG(WARNING) << "Fields are not checked for modification currently";
+      return true;
+    }
+
+    void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+                           art::ObjPtr<art::mirror::LongArray> new_cookie)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateFields(art::ObjPtr<art::mirror::Class> mclass)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
+                       art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+                       const art::DexFile::ClassDef& class_def)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
+                     art::ObjPtr<art::mirror::DexCache> new_dex_cache)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+   private:
+    Redefiner* driver_;
+    jclass klass_;
+    std::unique_ptr<const art::DexFile> dex_file_;
+    std::string class_sig_;
+  };
+
   jvmtiError result_;
   art::Runtime* runtime_;
   art::Thread* self_;
+  std::vector<ClassRedefinition> redefinitions_;
   // Kept as a jclass since we have weird run-state changes that make keeping it around as a
   // mirror::Class difficult and confusing.
-  jclass klass_;
-  std::unique_ptr<const art::DexFile> dex_file_;
   std::string* error_msg_;
-  char* class_sig_;
 
   // TODO Maybe change jclass to a mirror::Class
   Redefiner(art::Runtime* runtime,
             art::Thread* self,
-            jclass klass,
-            char* class_sig,
-            std::unique_ptr<const art::DexFile>& redefined_dex_file,
             std::string* error_msg)
       : result_(ERR(INTERNAL)),
         runtime_(runtime),
         self_(self),
-        klass_(klass),
-        dex_file_(std::move(redefined_dex_file)),
-        error_msg_(error_msg),
-        class_sig_(class_sig) { }
+        redefinitions_(),
+        error_msg_(error_msg) { }
+
+  jvmtiError AddRedefinition(ArtJvmTiEnv* env, const jvmtiClassDefinition& def)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
                                               /*out*/std::string* error_msg)
@@ -114,23 +218,17 @@
 
   static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location,
                                                        jint data_len,
-                                                       unsigned char* dex_data,
+                                                       const unsigned char* dex_data,
                                                        std::string* error_msg);
 
   // TODO Put on all the lock qualifiers.
   jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
-  bool FinishRemainingAllocations(
-        /*out*/art::MutableHandle<art::mirror::ClassLoader>* source_class_loader,
-        /*out*/art::MutableHandle<art::mirror::Object>* source_dex_file_obj,
-        /*out*/art::MutableHandle<art::mirror::LongArray>* new_dex_file_cookie,
-        /*out*/art::MutableHandle<art::mirror::DexCache>* new_dex_cache)
+  bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool EnsureAllClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  // Preallocates all needed allocations in klass so that we can pause execution safely.
-  // TODO We should be able to free the arrays if they end up not being used. Investigate doing this
-  // in the future. For now we will just take the memory hit.
-  bool EnsureClassAllocationsFinished() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_);
 
   // Ensure that obsolete methods are deoptimized. This is needed since optimized methods may have
   // pointers to their ArtMethods stashed in registers that they then use to attempt to hit the
@@ -140,70 +238,12 @@
       REQUIRES(!art::Locks::thread_list_lock_,
                !art::Locks::classlinker_classes_lock_);
 
-  art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  // This finds the java.lang.DexFile we will add the native DexFile to as part of the classpath.
-  // TODO Make sure the DexFile object returned is the one that the klass_ actually comes from.
-  art::mirror::Object* FindSourceDexFileObject(art::Handle<art::mirror::ClassLoader> loader)
-      REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  // Allocates and fills the new DexFileCookie
-  art::mirror::LongArray* AllocateDexFileCookie(art::Handle<art::mirror::Object> java_dex_file_obj)
-      REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader)
-      REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  void RecordFailure(jvmtiError result, const std::string& error_msg);
-
-  // This will check that no constraints are violated (more than 1 class in dex file, any changes in
-  // number/declaration of methods & fields, changes in access flags, etc.)
-  bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  // Checks that the class can even be redefined.
-  bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  // Checks that the dex file does not add/remove methods.
-  bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    LOG(WARNING) << "methods are not checked for modification currently";
-    return true;
+  void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg);
+  void RecordFailure(jvmtiError result, const std::string& error_msg) {
+    RecordFailure(result, "NO CLASS", error_msg);
   }
 
-  // Checks that the dex file does not modify fields
-  bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    LOG(WARNING) << "Fields are not checked for modification currently";
-    return true;
-  }
-
-  // Checks that the dex file contains only the single expected class and that the top-level class
-  // data has not been modified in an incompatible manner.
-  bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
-
-  void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
-                         art::ObjPtr<art::mirror::LongArray> new_cookie,
-                         /*out*/art::ObjPtr<art::mirror::LongArray>* original_cookie)
-      REQUIRES(art::Locks::mutator_lock_);
-
-  void UpdateFields(art::ObjPtr<art::mirror::Class> mclass)
-      REQUIRES(art::Locks::mutator_lock_);
-
-  void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
-                     art::ObjPtr<art::mirror::DexCache> new_dex_cache,
-                     const art::DexFile::ClassDef& class_def)
-      REQUIRES(art::Locks::mutator_lock_);
-
-  void UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
-                   art::ObjPtr<art::mirror::DexCache> new_dex_cache)
-      REQUIRES(art::Locks::mutator_lock_);
-
-  void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
-      REQUIRES(art::Locks::mutator_lock_);
-
-  void FillObsoleteMethodMap(art::mirror::Class* art_klass,
-                             const std::unordered_map<art::ArtMethod*, art::ArtMethod*>& obsoletes)
-      REQUIRES(art::Locks::mutator_lock_);
+  friend struct CallbackCtx;
 };
 
 }  // namespace openjdkjvmti
diff --git a/test/921-hello-failure/expected.txt b/test/921-hello-failure/expected.txt
index e2665ef..1c1d4d9 100644
--- a/test/921-hello-failure/expected.txt
+++ b/test/921-hello-failure/expected.txt
@@ -13,3 +13,11 @@
 hello2 - ReorderInterface
 Transformation error : java.lang.Exception(Failed to redefine class <LTransform2;> due to JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED)
 hello2 - ReorderInterface
+hello - MultiRedef
+hello2 - MultiRedef
+Transformation error : java.lang.Exception(Failed to redefine classes <LTransform2;, LTransform;> due to JVMTI_ERROR_NAMES_DONT_MATCH)
+hello - MultiRedef
+hello2 - MultiRedef
+Transformation error : java.lang.Exception(Failed to redefine classes <LTransform;, LTransform2;> due to JVMTI_ERROR_NAMES_DONT_MATCH)
+hello - MultiRedef
+hello2 - MultiRedef
diff --git a/test/921-hello-failure/src/CommonClassDefinition.java b/test/921-hello-failure/src/CommonClassDefinition.java
new file mode 100644
index 0000000..62602a0
--- /dev/null
+++ b/test/921-hello-failure/src/CommonClassDefinition.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+class CommonClassDefinition {
+  public final Class<?> target;
+  public final byte[] class_file_bytes;
+  public final byte[] dex_file_bytes;
+
+  CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+    this.target = target;
+    this.class_file_bytes = class_file_bytes;
+    this.dex_file_bytes = dex_file_bytes;
+  }
+}
diff --git a/test/921-hello-failure/src/Main.java b/test/921-hello-failure/src/Main.java
index 69c48e2..1fe2599 100644
--- a/test/921-hello-failure/src/Main.java
+++ b/test/921-hello-failure/src/Main.java
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+import java.util.ArrayList;
 public class Main {
 
   public static void main(String[] args) {
@@ -23,10 +24,30 @@
     NewInterface.doTest(new Transform2());
     MissingInterface.doTest(new Transform2());
     ReorderInterface.doTest(new Transform2());
+    MultiRedef.doTest(new Transform(), new Transform2());
   }
 
   // Transforms the class. This throws an exception if something goes wrong.
   public static native void doCommonClassRedefinition(Class<?> target,
                                                       byte[] classfile,
                                                       byte[] dexfile) throws Exception;
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) throws Exception {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles) throws Exception;
 }
diff --git a/test/921-hello-failure/src/MultiRedef.java b/test/921-hello-failure/src/MultiRedef.java
new file mode 100644
index 0000000..c64342c
--- /dev/null
+++ b/test/921-hello-failure/src/MultiRedef.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+import java.util.Base64;
+
+class MultiRedef {
+
+  // class NotTransform {
+  //   public void sayHi(String name) {
+  //     throw new Error("Should not be called!");
+  //   }
+  // }
+  private static CommonClassDefinition INVALID_DEFINITION_T1 = new CommonClassDefinition(
+      Transform.class,
+      Base64.getDecoder().decode(
+          "yv66vgAAADQAFQoABgAPBwAQCAARCgACABIHABMHABQBAAY8aW5pdD4BAAMoKVYBAARDb2RlAQAP" +
+          "TGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApTb3VyY2VG" +
+          "aWxlAQARTm90VHJhbnNmb3JtLmphdmEMAAcACAEAD2phdmEvbGFuZy9FcnJvcgEAFVNob3VsZCBu" +
+          "b3QgYmUgY2FsbGVkIQwABwAMAQAMTm90VHJhbnNmb3JtAQAQamF2YS9sYW5nL09iamVjdAAgAAUA" +
+          "BgAAAAAAAgAAAAcACAABAAkAAAAdAAEAAQAAAAUqtwABsQAAAAEACgAAAAYAAQAAAAEAAQALAAwA" +
+          "AQAJAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEACgAAAAYAAQAAAAMAAQANAAAAAgAO"),
+      Base64.getDecoder().decode(
+          "ZGV4CjAzNQDLV95i5xnv6iUi6uIeDoY5jP5Xe9NP1AiYAgAAcAAAAHhWNBIAAAAAAAAAAAQCAAAL" +
+          "AAAAcAAAAAUAAACcAAAAAgAAALAAAAAAAAAAAAAAAAQAAADIAAAAAQAAAOgAAACQAQAACAEAAEoB" +
+          "AABSAQAAYgEAAHUBAACJAQAAnQEAALABAADHAQAAygEAAM4BAADiAQAAAQAAAAIAAAADAAAABAAA" +
+          "AAcAAAAHAAAABAAAAAAAAAAIAAAABAAAAEQBAAAAAAAAAAAAAAAAAQAKAAAAAQABAAAAAAACAAAA" +
+          "AAAAAAAAAAAAAAAAAgAAAAAAAAAFAAAAAAAAAPQBAAAAAAAAAQABAAEAAADpAQAABAAAAHAQAwAA" +
+          "AA4ABAACAAIAAADuAQAACQAAACIAAQAbAQYAAABwIAIAEAAnAAAAAQAAAAMABjxpbml0PgAOTE5v" +
+          "dFRyYW5zZm9ybTsAEUxqYXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZh" +
+          "L2xhbmcvU3RyaW5nOwARTm90VHJhbnNmb3JtLmphdmEAFVNob3VsZCBub3QgYmUgY2FsbGVkIQAB" +
+          "VgACVkwAEmVtaXR0ZXI6IGphY2stNC4yMAAFc2F5SGkAAQAHDgADAQAHDgAAAAEBAICABIgCAQGg" +
+          "AgAADAAAAAAAAAABAAAAAAAAAAEAAAALAAAAcAAAAAIAAAAFAAAAnAAAAAMAAAACAAAAsAAAAAUA" +
+          "AAAEAAAAyAAAAAYAAAABAAAA6AAAAAEgAAACAAAACAEAAAEQAAABAAAARAEAAAIgAAALAAAASgEA" +
+          "AAMgAAACAAAA6QEAAAAgAAABAAAA9AEAAAAQAAABAAAABAIAAA=="));
+
+  // Valid redefinition of Transform2
+  // class Transform2 implements Iface1, Iface2 {
+  //   public void sayHi(String name) {
+  //     throw new Error("Should not be called!");
+  //   }
+  // }
+  private static CommonClassDefinition VALID_DEFINITION_T2 = new CommonClassDefinition(
+      Transform2.class,
+      Base64.getDecoder().decode(
+          "yv66vgAAADQAGQoABgARBwASCAATCgACABQHABUHABYHABcHABgBAAY8aW5pdD4BAAMoKVYBAARD" +
+          "b2RlAQAPTGluZU51bWJlclRhYmxlAQAFc2F5SGkBABUoTGphdmEvbGFuZy9TdHJpbmc7KVYBAApT" +
+          "b3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoBAA9qYXZhL2xhbmcvRXJyb3IBABVTaG91" +
+          "bGQgbm90IGJlIGNhbGxlZCEMAAkADgEAClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAG" +
+          "SWZhY2UxAQAGSWZhY2UyACAABQAGAAIABwAIAAAAAgAAAAkACgABAAsAAAAdAAEAAQAAAAUqtwAB" +
+          "sQAAAAEADAAAAAYAAQAAAAEAAQANAA4AAQALAAAAIgADAAIAAAAKuwACWRIDtwAEvwAAAAEADAAA" +
+          "AAYAAQAAAAMAAQAPAAAAAgAQ"),
+      Base64.getDecoder().decode(
+          "ZGV4CjAzNQDSWls05CPkX+gbTGMVRvx9dc9vozzVbu7AAgAAcAAAAHhWNBIAAAAAAAAAACwCAAAN" +
+          "AAAAcAAAAAcAAACkAAAAAgAAAMAAAAAAAAAAAAAAAAQAAADYAAAAAQAAAPgAAACoAQAAGAEAAGIB" +
+          "AABqAQAAdAEAAH4BAACMAQAAnwEAALMBAADHAQAA3gEAAO8BAADyAQAA9gEAAAoCAAABAAAAAgAA" +
+          "AAMAAAAEAAAABQAAAAYAAAAJAAAACQAAAAYAAAAAAAAACgAAAAYAAABcAQAAAgAAAAAAAAACAAEA" +
+          "DAAAAAMAAQAAAAAABAAAAAAAAAACAAAAAAAAAAQAAABUAQAACAAAAAAAAAAcAgAAAAAAAAEAAQAB" +
+          "AAAAEQIAAAQAAABwEAMAAAAOAAQAAgACAAAAFgIAAAkAAAAiAAMAGwEHAAAAcCACABAAJwAAAAIA" +
+          "AAAAAAEAAQAAAAUABjxpbml0PgAITElmYWNlMTsACExJZmFjZTI7AAxMVHJhbnNmb3JtMjsAEUxq" +
+          "YXZhL2xhbmcvRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAEkxqYXZhL2xhbmcvU3RyaW5nOwAV" +
+          "U2hvdWxkIG5vdCBiZSBjYWxsZWQhAA9UcmFuc2Zvcm0yLmphdmEAAVYAAlZMABJlbWl0dGVyOiBq" +
+          "YWNrLTQuMjAABXNheUhpAAEABw4AAwEABw4AAAABAQCAgASYAgEBsAIAAAwAAAAAAAAAAQAAAAAA" +
+          "AAABAAAADQAAAHAAAAACAAAABwAAAKQAAAADAAAAAgAAAMAAAAAFAAAABAAAANgAAAAGAAAAAQAA" +
+          "APgAAAABIAAAAgAAABgBAAABEAAAAgAAAFQBAAACIAAADQAAAGIBAAADIAAAAgAAABECAAAAIAAA" +
+          "AQAAABwCAAAAEAAAAQAAACwCAAA="));
+
+  public static void doTest(Transform t1, Transform2 t2) {
+    t1.sayHi("MultiRedef");
+    t2.sayHi("MultiRedef");
+    try {
+      Main.doMultiClassRedefinition(VALID_DEFINITION_T2, INVALID_DEFINITION_T1);
+    } catch (Exception e) {
+      System.out.println(
+          "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")");
+    }
+    t1.sayHi("MultiRedef");
+    t2.sayHi("MultiRedef");
+    try {
+      Main.doMultiClassRedefinition(INVALID_DEFINITION_T1, VALID_DEFINITION_T2);
+    } catch (Exception e) {
+      System.out.println(
+          "Transformation error : " + e.getClass().getName() + "(" + e.getMessage() + ")");
+    }
+    t1.sayHi("MultiRedef");
+    t2.sayHi("MultiRedef");
+  }
+}
diff --git a/test/926-multi-obsolescence/build b/test/926-multi-obsolescence/build
new file mode 100755
index 0000000..898e2e5
--- /dev/null
+++ b/test/926-multi-obsolescence/build
@@ -0,0 +1,17 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-build "$@" --experimental agents
diff --git a/test/926-multi-obsolescence/expected.txt b/test/926-multi-obsolescence/expected.txt
new file mode 100644
index 0000000..0546490
--- /dev/null
+++ b/test/926-multi-obsolescence/expected.txt
@@ -0,0 +1,15 @@
+hello
+hello - 2
+Not doing anything here
+goodbye - 2
+goodbye
+hello
+hello - 2
+transforming calling functions
+goodbye - 2
+goodbye
+Hello - Transformed
+Hello 2 - Transformed
+Not doing anything here
+Goodbye 2 - Transformed
+Goodbye - Transformed
diff --git a/test/926-multi-obsolescence/info.txt b/test/926-multi-obsolescence/info.txt
new file mode 100644
index 0000000..1399b96
--- /dev/null
+++ b/test/926-multi-obsolescence/info.txt
@@ -0,0 +1,2 @@
+Tests that we can redefine multiple classes at once using the RedefineClasses
+function.
diff --git a/test/926-multi-obsolescence/run b/test/926-multi-obsolescence/run
new file mode 100755
index 0000000..4379349
--- /dev/null
+++ b/test/926-multi-obsolescence/run
@@ -0,0 +1,19 @@
+#!/bin/bash
+#
+# Copyright 2016 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.
+
+./default-run "$@" --experimental agents \
+                   --experimental runtime-plugins \
+                   --jvmti
diff --git a/test/926-multi-obsolescence/src/CommonClassDefinition.java b/test/926-multi-obsolescence/src/CommonClassDefinition.java
new file mode 100644
index 0000000..62602a0
--- /dev/null
+++ b/test/926-multi-obsolescence/src/CommonClassDefinition.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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.
+ */
+
+class CommonClassDefinition {
+  public final Class<?> target;
+  public final byte[] class_file_bytes;
+  public final byte[] dex_file_bytes;
+
+  CommonClassDefinition(Class<?> target, byte[] class_file_bytes, byte[] dex_file_bytes) {
+    this.target = target;
+    this.class_file_bytes = class_file_bytes;
+    this.dex_file_bytes = dex_file_bytes;
+  }
+}
diff --git a/test/926-multi-obsolescence/src/Main.java b/test/926-multi-obsolescence/src/Main.java
new file mode 100644
index 0000000..8a6cf84
--- /dev/null
+++ b/test/926-multi-obsolescence/src/Main.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+import java.util.ArrayList;
+import java.util.Base64;
+
+public class Main {
+  // class Transform {
+  //   public void sayHi(Runnable r) {
+  //     System.out.println("Hello - Transformed");
+  //     r.run();
+  //     System.out.println("Goodbye - Transformed");
+  //   }
+  // }
+  private static CommonClassDefinition VALID_DEFINITION_T1 = new CommonClassDefinition(
+      Transform.class,
+      Base64.getDecoder().decode(
+        "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" +
+        "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+        "KVYBAApTb3VyY2VGaWxlAQAOVHJhbnNmb3JtLmphdmEMAAkACgcAHAwAHQAeAQATSGVsbG8gLSBU" +
+        "cmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABVHb29kYnllIC0gVHJhbnNmb3JtZWQBAAlUcmFu" +
+        "c2Zvcm0BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZh" +
+        "L2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZh" +
+        "L2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAAAAACAAAA" +
+        "CQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsAAAA7AAIA" +
+        "AgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4ABQAWAAYA" +
+        "AQAPAAAAAgAQ"),
+      Base64.getDecoder().decode(
+        "ZGV4CjAzNQAYeAMMXgYWxoeSHAS9EWKCCtVRSAGpqZVQAwAAcAAAAHhWNBIAAAAAAAAAALACAAAR" +
+        "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAMAgAARAEAAKIB" +
+        "AACqAQAAwQEAANYBAADjAQAA+gEAAA4CAAAkAgAAOAIAAEwCAABcAgAAXwIAAGMCAAB3AgAAfAIA" +
+        "AIUCAACKAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" +
+        "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" +
+        "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAJ8CAAAAAAAAAQABAAEAAACRAgAABAAAAHAQ" +
+        "AwAAAA4ABAACAAIAAACWAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" +
+        "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AFUdvb2RieWUgLSBUcmFuc2Zvcm1lZAATSGVsbG8g" +
+        "LSBUcmFuc2Zvcm1lZAALTFRyYW5zZm9ybTsAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwASTGphdmEv" +
+        "bGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJM" +
+        "amF2YS9sYW5nL1N5c3RlbTsADlRyYW5zZm9ybS5qYXZhAAFWAAJWTAASZW1pdHRlcjogamFjay00" +
+        "LjEzAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICABMQCAQHc" +
+        "AgAAAA0AAAAAAAAAAQAAAAAAAAABAAAAEQAAAHAAAAACAAAABwAAALQAAAADAAAAAwAAANAAAAAE" +
+        "AAAAAQAAAPQAAAAFAAAABQAAAPwAAAAGAAAAAQAAACQBAAABIAAAAgAAAEQBAAABEAAAAgAAAJQB" +
+        "AAACIAAAEQAAAKIBAAADIAAAAgAAAJECAAAAIAAAAQAAAJ8CAAAAEAAAAQAAALACAAA="));
+  // class Transform2 {
+  //   public void sayHi(Runnable r) {
+  //     System.out.println("Hello 2 - Transformed");
+  //     r.run();
+  //     System.out.println("Goodbye 2 - Transformed");
+  //   }
+  // }
+  private static CommonClassDefinition VALID_DEFINITION_T2 = new CommonClassDefinition(
+      Transform2.class,
+      Base64.getDecoder().decode(
+        "yv66vgAAADQAJAoACAARCQASABMIABQKABUAFgsAFwAYCAAZBwAaBwAbAQAGPGluaXQ+AQADKClW" +
+        "AQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEABXNheUhpAQAXKExqYXZhL2xhbmcvUnVubmFibGU7" +
+        "KVYBAApTb3VyY2VGaWxlAQAPVHJhbnNmb3JtMi5qYXZhDAAJAAoHABwMAB0AHgEAFUhlbGxvIDIg" +
+        "LSBUcmFuc2Zvcm1lZAcAHwwAIAAhBwAiDAAjAAoBABdHb29kYnllIDIgLSBUcmFuc2Zvcm1lZAEA" +
+        "ClRyYW5zZm9ybTIBABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEA" +
+        "FUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAV" +
+        "KExqYXZhL2xhbmcvU3RyaW5nOylWAQASamF2YS9sYW5nL1J1bm5hYmxlAQADcnVuACAABwAIAAAA" +
+        "AAACAAAACQAKAAEACwAAAB0AAQABAAAABSq3AAGxAAAAAQAMAAAABgABAAAAAQABAA0ADgABAAsA" +
+        "AAA7AAIAAgAAABeyAAISA7YABCu5AAUBALIAAhIGtgAEsQAAAAEADAAAABIABAAAAAMACAAEAA4A" +
+        "BQAWAAYAAQAPAAAAAgAQ"),
+      Base64.getDecoder().decode(
+        "ZGV4CjAzNQCee5Z6+AuFcjnPjjn7QYgZmKSmFQCO4nxUAwAAcAAAAHhWNBIAAAAAAAAAALQCAAAR" +
+        "AAAAcAAAAAcAAAC0AAAAAwAAANAAAAABAAAA9AAAAAUAAAD8AAAAAQAAACQBAAAQAgAARAEAAKIB" +
+        "AACqAQAAwwEAANoBAADoAQAA/wEAABMCAAApAgAAPQIAAFECAABiAgAAZQIAAGkCAAB9AgAAggIA" +
+        "AIsCAACQAgAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACgAAAAoAAAAGAAAAAAAAAAsAAAAGAAAA" +
+        "lAEAAAsAAAAGAAAAnAEAAAUAAQANAAAAAAAAAAAAAAAAAAEAEAAAAAEAAgAOAAAAAgAAAAAAAAAD" +
+        "AAAADwAAAAAAAAAAAAAAAgAAAAAAAAAJAAAAAAAAAKUCAAAAAAAAAQABAAEAAACXAgAABAAAAHAQ" +
+        "AwAAAA4ABAACAAIAAACcAgAAFAAAAGIAAAAbAQIAAABuIAIAEAByEAQAAwBiAAAAGwEBAAAAbiAC" +
+        "ABAADgABAAAAAwAAAAEAAAAEAAY8aW5pdD4AF0dvb2RieWUgMiAtIFRyYW5zZm9ybWVkABVIZWxs" +
+        "byAyIC0gVHJhbnNmb3JtZWQADExUcmFuc2Zvcm0yOwAVTGphdmEvaW8vUHJpbnRTdHJlYW07ABJM" +
+        "amF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmlu" +
+        "ZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAPVHJhbnNmb3JtMi5qYXZhAAFWAAJWTAASZW1pdHRlcjog" +
+        "amFjay00LjIwAANvdXQAB3ByaW50bG4AA3J1bgAFc2F5SGkAAQAHDgADAQAHDoc8hwAAAAEBAICA" +
+        "BMQCAQHcAgANAAAAAAAAAAEAAAAAAAAAAQAAABEAAABwAAAAAgAAAAcAAAC0AAAAAwAAAAMAAADQ" +
+        "AAAABAAAAAEAAAD0AAAABQAAAAUAAAD8AAAABgAAAAEAAAAkAQAAASAAAAIAAABEAQAAARAAAAIA" +
+        "AACUAQAAAiAAABEAAACiAQAAAyAAAAIAAACXAgAAACAAAAEAAAClAgAAABAAAAEAAAC0AgAA"));
+
+  public static void main(String[] args) {
+    System.loadLibrary(args[1]);
+    doTest(new Transform(), new Transform2());
+  }
+
+  public static void doTest(final Transform t1, final Transform2 t2) {
+    t1.sayHi(() -> { t2.sayHi(() -> { System.out.println("Not doing anything here"); }); });
+    t1.sayHi(() -> {
+      t2.sayHi(() -> {
+        System.out.println("transforming calling functions");
+        doMultiClassRedefinition(VALID_DEFINITION_T1, VALID_DEFINITION_T2);
+      });
+    });
+    t1.sayHi(() -> { t2.sayHi(() -> { System.out.println("Not doing anything here"); }); });
+  }
+
+  public static void doMultiClassRedefinition(CommonClassDefinition... defs) {
+    ArrayList<Class<?>> classes = new ArrayList<>();
+    ArrayList<byte[]> class_files = new ArrayList<>();
+    ArrayList<byte[]> dex_files = new ArrayList<>();
+
+    for (CommonClassDefinition d : defs) {
+      classes.add(d.target);
+      class_files.add(d.class_file_bytes);
+      dex_files.add(d.dex_file_bytes);
+    }
+    doCommonMultiClassRedefinition(classes.toArray(new Class<?>[0]),
+                                   class_files.toArray(new byte[0][]),
+                                   dex_files.toArray(new byte[0][]));
+  }
+
+  public static native void doCommonMultiClassRedefinition(Class<?>[] targets,
+                                                           byte[][] classfiles,
+                                                           byte[][] dexfiles);
+}
diff --git a/test/926-multi-obsolescence/src/Transform.java b/test/926-multi-obsolescence/src/Transform.java
new file mode 100644
index 0000000..8cda6cd
--- /dev/null
+++ b/test/926-multi-obsolescence/src/Transform.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+class Transform {
+  public void sayHi(Runnable r) {
+    // Use lower 'h' to make sure the string will have a different string id
+    // than the transformation (the transformation code is the same except
+    // the actual printed String, which was making the test inacurately passing
+    // in JIT mode when loading the string from the dex cache, as the string ids
+    // of the two different strings were the same).
+    // We know the string ids will be different because lexicographically:
+    // "Hello" < "LTransform;" < "hello".
+    System.out.println("hello");
+    r.run();
+    System.out.println("goodbye");
+  }
+}
diff --git a/test/926-multi-obsolescence/src/Transform2.java b/test/926-multi-obsolescence/src/Transform2.java
new file mode 100644
index 0000000..4877f84
--- /dev/null
+++ b/test/926-multi-obsolescence/src/Transform2.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+class Transform2 {
+  public void sayHi(Runnable r) {
+    System.out.println("hello - 2");
+    r.run();
+    System.out.println("goodbye - 2");
+  }
+}
diff --git a/test/Android.run-test.mk b/test/Android.run-test.mk
index a93efd2..4243370 100644
--- a/test/Android.run-test.mk
+++ b/test/Android.run-test.mk
@@ -298,6 +298,7 @@
   923-monitors \
   924-threads \
   925-threadgroups \
+  926-multi-obsolescence \
 
 ifneq (,$(filter target,$(TARGET_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,target,$(RUN_TYPES),$(PREBUILD_TYPES), \
@@ -566,6 +567,7 @@
   915-obsolete-2 \
   917-fields-transformation \
   919-obsolete-fields \
+  926-multi-obsolescence \
 
 ifneq (,$(filter jit,$(COMPILER_TYPES)))
   ART_TEST_KNOWN_BROKEN += $(call all-run-test-names,$(TARGET_TYPES),$(RUN_TYPES),$(PREBUILD_TYPES), \
diff --git a/test/ti-agent/common_helper.cc b/test/ti-agent/common_helper.cc
index 6f98f10..2c6d3ed 100644
--- a/test/ti-agent/common_helper.cc
+++ b/test/ti-agent/common_helper.cc
@@ -62,48 +62,64 @@
 
 namespace common_redefine {
 
-static void throwRedefinitionError(jvmtiEnv* jvmti, JNIEnv* env, jclass target, jvmtiError res) {
+static void throwRedefinitionError(jvmtiEnv* jvmti,
+                                   JNIEnv* env,
+                                   jint num_targets,
+                                   jclass* target,
+                                   jvmtiError res) {
   std::stringstream err;
-  char* signature = nullptr;
-  char* generic = nullptr;
-  jvmti->GetClassSignature(target, &signature, &generic);
   char* error = nullptr;
   jvmti->GetErrorName(res, &error);
-  err << "Failed to redefine class <" << signature << "> due to " << error;
+  err << "Failed to redefine class";
+  if (num_targets > 1) {
+    err << "es";
+  }
+  err << " <";
+  for (jint i = 0; i < num_targets; i++) {
+    char* signature = nullptr;
+    char* generic = nullptr;
+    jvmti->GetClassSignature(target[i], &signature, &generic);
+    if (i != 0) {
+      err << ", ";
+    }
+    err << signature;
+    jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature));
+    jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic));
+  }
+  err << "> due to " << error;
   std::string message = err.str();
-  jvmti->Deallocate(reinterpret_cast<unsigned char*>(signature));
-  jvmti->Deallocate(reinterpret_cast<unsigned char*>(generic));
   jvmti->Deallocate(reinterpret_cast<unsigned char*>(error));
   env->ThrowNew(env->FindClass("java/lang/Exception"), message.c_str());
 }
 
-using RedefineDirectFunction = jvmtiError (*)(jvmtiEnv*, jclass, jint, const unsigned char*);
-static void DoClassTransformation(jvmtiEnv* jvmti_env,
-                                  JNIEnv* env,
-                                  jclass target,
-                                  jbyteArray class_file_bytes,
-                                  jbyteArray dex_file_bytes) {
-  jbyteArray desired_array = IsJVM() ? class_file_bytes : dex_file_bytes;
-  jint len = static_cast<jint>(env->GetArrayLength(desired_array));
-  const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>(
-      env->GetByteArrayElements(desired_array, nullptr));
-  jvmtiError res;
-  if (IsJVM()) {
-    jvmtiClassDefinition def;
-    def.klass = target;
-    def.class_byte_count = static_cast<jint>(len);
-    def.class_bytes = redef_bytes;
-    res = jvmti_env->RedefineClasses(1, &def);
-  } else {
-    RedefineDirectFunction f =
-        reinterpret_cast<RedefineDirectFunction>(jvmti_env->functions->reserved3);
-    res = f(jvmti_env, target, len, redef_bytes);
+static void DoMultiClassRedefine(jvmtiEnv* jvmti_env,
+                                 JNIEnv* env,
+                                 jint num_redefines,
+                                 jclass* targets,
+                                 jbyteArray* class_file_bytes,
+                                 jbyteArray* dex_file_bytes) {
+  std::vector<jvmtiClassDefinition> defs;
+  for (jint i = 0; i < num_redefines; i++) {
+    jbyteArray desired_array = IsJVM() ? class_file_bytes[i] : dex_file_bytes[i];
+    jint len = static_cast<jint>(env->GetArrayLength(desired_array));
+    const unsigned char* redef_bytes = reinterpret_cast<const unsigned char*>(
+        env->GetByteArrayElements(desired_array, nullptr));
+    defs.push_back({targets[i], static_cast<jint>(len), redef_bytes});
   }
+  jvmtiError res = jvmti_env->RedefineClasses(num_redefines, defs.data());
   if (res != JVMTI_ERROR_NONE) {
-    throwRedefinitionError(jvmti_env, env, target, res);
+    throwRedefinitionError(jvmti_env, env, num_redefines, targets, res);
   }
 }
 
+static void DoClassRedefine(jvmtiEnv* jvmti_env,
+                            JNIEnv* env,
+                            jclass target,
+                            jbyteArray class_file_bytes,
+                            jbyteArray dex_file_bytes) {
+  return DoMultiClassRedefine(jvmti_env, env, 1, &target, &class_file_bytes, &dex_file_bytes);
+}
+
 // Magic JNI export that classes can use for redefining classes.
 // To use classes should declare this as a native function with signature (Ljava/lang/Class;[B[B)V
 extern "C" JNIEXPORT void JNICALL Java_Main_doCommonClassRedefinition(JNIEnv* env,
@@ -111,7 +127,38 @@
                                                                       jclass target,
                                                                       jbyteArray class_file_bytes,
                                                                       jbyteArray dex_file_bytes) {
-  DoClassTransformation(jvmti_env, env, target, class_file_bytes, dex_file_bytes);
+  DoClassRedefine(jvmti_env, env, target, class_file_bytes, dex_file_bytes);
+}
+
+// Magic JNI export that classes can use for redefining classes.
+// To use classes should declare this as a native function with signature
+// ([Ljava/lang/Class;[[B[[B)V
+extern "C" JNIEXPORT void JNICALL Java_Main_doCommonMultiClassRedefinition(
+    JNIEnv* env,
+    jclass,
+    jobjectArray targets,
+    jobjectArray class_file_bytes,
+    jobjectArray dex_file_bytes) {
+  std::vector<jclass> classes;
+  std::vector<jbyteArray> class_files;
+  std::vector<jbyteArray> dex_files;
+  jint len = env->GetArrayLength(targets);
+  if (len != env->GetArrayLength(class_file_bytes) || len != env->GetArrayLength(dex_file_bytes)) {
+    env->ThrowNew(env->FindClass("java/lang/IllegalArgumentException"),
+                  "the three array arguments passed to this function have different lengths!");
+    return;
+  }
+  for (jint i = 0; i < len; i++) {
+    classes.push_back(static_cast<jclass>(env->GetObjectArrayElement(targets, i)));
+    dex_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(dex_file_bytes, i)));
+    class_files.push_back(static_cast<jbyteArray>(env->GetObjectArrayElement(class_file_bytes, i)));
+  }
+  return DoMultiClassRedefine(jvmti_env,
+                              env,
+                              len,
+                              classes.data(),
+                              class_files.data(),
+                              dex_files.data());
 }
 
 // Don't do anything
diff --git a/test/ti-agent/common_load.cc b/test/ti-agent/common_load.cc
index 8abd063..521e672 100644
--- a/test/ti-agent/common_load.cc
+++ b/test/ti-agent/common_load.cc
@@ -65,6 +65,7 @@
   { "917-fields-transformation", common_redefine::OnLoad, nullptr },
   { "919-obsolete-fields", common_redefine::OnLoad, nullptr },
   { "921-hello-failure", common_redefine::OnLoad, nullptr },
+  { "926-multi-obsolescence", common_redefine::OnLoad, nullptr },
 };
 
 static AgentLib* FindAgent(char* name) {