Initial support for adding virtuals with structural redefinition

Initial implementation of adding virtual methods and non-static fields
using structural redefinition. Currently this is limited to 'final',
non-finalizable classes. These restrictions will be removed or
loosened in the future.

All non-collected instances of the redefined class will be made
obsolete and reallocated. This can cause significant GC load.

This feature does not work with any of the -quick opcodes and should
only be used with dex files that haven't undergone dex2dex
compilation (that is --debuggable and BCP dex files).

Test: ./test.py --host
Bug: 134162467
Bug: 144168550

Change-Id: Ia401d97395cfe498eb849a661ea9a900dfaa6da3
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
index e7306ba..37ae951 100644
--- a/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -26,6 +26,7 @@
     defaults: ["art_defaults"],
     host_supported: true,
     srcs: [
+        "alloc_manager.cc",
         "deopt_manager.cc",
         "events.cc",
         "fixed_up_dex_file.cc",
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index 665fa9f..4ce376f 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -40,6 +40,7 @@
 
 #include "jvmti.h"
 
+#include "alloc_manager.h"
 #include "art_jvmti.h"
 #include "base/logging.h"  // For gLogVerbosity.
 #include "base/mutex.h"
@@ -79,6 +80,7 @@
 // These should never be null.
 EventHandler* gEventHandler;
 DeoptManager* gDeoptManager;
+AllocationManager* gAllocManager;
 
 #define ENSURE_NON_NULL(n)      \
   do {                          \
@@ -1497,6 +1499,7 @@
 extern "C" bool ArtPlugin_Initialize() {
   art::Runtime* runtime = art::Runtime::Current();
 
+  gAllocManager = new AllocationManager;
   gDeoptManager = new DeoptManager;
   gEventHandler = new EventHandler;
 
diff --git a/openjdkjvmti/alloc_manager.cc b/openjdkjvmti/alloc_manager.cc
new file mode 100644
index 0000000..597ab05
--- /dev/null
+++ b/openjdkjvmti/alloc_manager.cc
@@ -0,0 +1,187 @@
+
+/* Copyright (C) 2019 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "alloc_manager.h"
+
+#include <atomic>
+#include <sstream>
+
+#include "base/logging.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "handle.h"
+#include "mirror/class-inl.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "scoped_thread_state_change.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "thread_pool.h"
+
+namespace openjdkjvmti {
+
+template<typename T>
+void AllocationManager::PauseForAllocation(art::Thread* self, T msg) {
+  // The suspension can pause us for arbitrary times. We need to do it to sleep unfortunately. So we
+  // do test, suspend, test again, sleep, repeat.
+  std::string cause;
+  const bool is_logging = VLOG_IS_ON(plugin);
+  while (true) {
+    // We always return when there is no pause and we are runnable.
+    art::Thread* pausing_thread = allocations_paused_thread_.load(std::memory_order_seq_cst);
+    if (LIKELY(pausing_thread == nullptr || pausing_thread == self)) {
+      return;
+    }
+    if (UNLIKELY(is_logging && cause.empty())) {
+      cause = msg();
+    }
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+    art::MutexLock mu(self, alloc_listener_mutex_);
+    pausing_thread = allocations_paused_thread_.load(std::memory_order_seq_cst);
+    CHECK_NE(pausing_thread, self) << "We should always be setting pausing_thread = self!"
+                                   << " How did this happen? " << *self;
+    if (pausing_thread != nullptr) {
+      VLOG(plugin) << "Suspending " << *self << " due to " << cause << ". Allocation pause "
+                   << "initiated by " << *pausing_thread;
+      alloc_pause_cv_.Wait(self);
+    }
+  }
+}
+
+extern AllocationManager* gAllocManager;
+AllocationManager* AllocationManager::Get() {
+  return gAllocManager;
+}
+
+void JvmtiAllocationListener::ObjectAllocated(art::Thread* self,
+                                              art::ObjPtr<art::mirror::Object>* obj,
+                                              size_t cnt) {
+  auto cb = manager_->callback_;
+  if (cb != nullptr && manager_->callback_enabled_.load(std::memory_order_seq_cst)) {
+    cb->ObjectAllocated(self, obj, cnt);
+  }
+}
+
+bool JvmtiAllocationListener::HasPreAlloc() const {
+  return manager_->allocations_paused_thread_.load(std::memory_order_seq_cst) != nullptr;
+}
+
+void JvmtiAllocationListener::PreObjectAllocated(art::Thread* self,
+                                                 art::MutableHandle<art::mirror::Class> type,
+                                                 size_t* byte_count) {
+  manager_->PauseForAllocation(self, [&]() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    std::ostringstream oss;
+    oss << "allocating " << *byte_count << " bytes of type " << type->PrettyClass();
+    return oss.str();
+  });
+  if (!type->IsVariableSize()) {
+    *byte_count = type->GetObjectSize();
+  }
+}
+
+AllocationManager::AllocationManager()
+    : alloc_listener_(nullptr),
+      alloc_listener_mutex_("JVMTI Alloc listener",
+                            art::LockLevel::kPostUserCodeSuspensionTopLevelLock),
+      alloc_pause_cv_("JVMTI Allocation Pause Condvar", alloc_listener_mutex_) {
+  alloc_listener_.reset(new JvmtiAllocationListener(this));
+}
+
+void AllocationManager::DisableAllocationCallback(art::Thread* self) {
+  callback_enabled_.store(false);
+  DecrListenerInstall(self);
+}
+
+void AllocationManager::EnableAllocationCallback(art::Thread* self) {
+  IncrListenerInstall(self);
+  callback_enabled_.store(true);
+}
+
+void AllocationManager::SetAllocListener(AllocationCallback* callback) {
+  CHECK(callback_ == nullptr) << "Already setup!";
+  callback_ = callback;
+  alloc_listener_.reset(new JvmtiAllocationListener(this));
+}
+
+void AllocationManager::RemoveAllocListener() {
+  callback_enabled_.store(false, std::memory_order_seq_cst);
+  callback_ = nullptr;
+}
+
+void AllocationManager::DecrListenerInstall(art::Thread* self) {
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+  art::MutexLock mu(self, alloc_listener_mutex_);
+  // We don't need any particular memory-order here since we're under the lock, they aren't
+  // changing.
+  if (--listener_refcount_ == 0) {
+    art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+  }
+}
+
+void AllocationManager::IncrListenerInstall(art::Thread* self) {
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+  art::MutexLock mu(self, alloc_listener_mutex_);
+  // We don't need any particular memory-order here since we're under the lock, they aren't
+  // changing.
+  if (listener_refcount_++ == 0) {
+    art::Runtime::Current()->GetHeap()->SetAllocationListener(alloc_listener_.get());
+  }
+}
+
+void AllocationManager::PauseAllocations(art::Thread* self) {
+  art::Thread* null_thr = nullptr;
+  IncrListenerInstall(self);
+  do {
+    PauseForAllocation(self, []() { return "request to pause allocations on other threads"; });
+  } while (allocations_paused_thread_.compare_exchange_strong(
+      null_thr, self, std::memory_order_seq_cst));
+  // Make sure everything else can see this and isn't in the middle of final allocation.
+  // Force every thread to either be suspended or pass through a barrier.
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+  art::Barrier barrier(0);
+  art::FunctionClosure fc([&](art::Thread* thr ATTRIBUTE_UNUSED) {
+    barrier.Pass(art::Thread::Current());
+  });
+  size_t requested = art::Runtime::Current()->GetThreadList()->RunCheckpoint(&fc);
+  barrier.Increment(self, requested);
+}
+
+void AllocationManager::ResumeAllocations(art::Thread* self) {
+  CHECK_EQ(allocations_paused_thread_.load(), self) << "not paused! ";
+  DecrListenerInstall(self);
+  art::ScopedThreadSuspension sts(self, art::ThreadState::kSuspended);
+  art::MutexLock mu(self, alloc_listener_mutex_);
+  allocations_paused_thread_.store(nullptr, std::memory_order_seq_cst);
+  alloc_pause_cv_.Broadcast(self);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/alloc_manager.h b/openjdkjvmti/alloc_manager.h
new file mode 100644
index 0000000..c89d9a6
--- /dev/null
+++ b/openjdkjvmti/alloc_manager.h
@@ -0,0 +1,114 @@
+/* Copyright (C) 2019 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_ALLOC_MANAGER_H_
+#define ART_OPENJDKJVMTI_ALLOC_MANAGER_H_
+
+#include <jvmti.h>
+
+#include <atomic>
+
+#include "base/locks.h"
+#include "base/mutex.h"
+#include "gc/allocation_listener.h"
+
+namespace art {
+template <typename T> class MutableHandle;
+template <typename T> class ObjPtr;
+class Thread;
+namespace mirror {
+class Class;
+class Object;
+}  // namespace mirror
+}  // namespace art
+
+namespace openjdkjvmti {
+
+class AllocationManager;
+
+class JvmtiAllocationListener : public art::gc::AllocationListener {
+ public:
+  explicit JvmtiAllocationListener(AllocationManager* manager) : manager_(manager) {}
+  void ObjectAllocated(art::Thread* self,
+                       art::ObjPtr<art::mirror::Object>* obj,
+                       size_t cnt) override REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool HasPreAlloc() const override REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void PreObjectAllocated(art::Thread* self,
+                          art::MutableHandle<art::mirror::Class> type,
+                          size_t* byte_count) override REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+  AllocationManager* manager_;
+};
+
+class AllocationManager {
+ public:
+  class AllocationCallback {
+   public:
+    virtual ~AllocationCallback() {}
+    virtual void ObjectAllocated(art::Thread* self,
+                                 art::ObjPtr<art::mirror::Object>* obj,
+                                 size_t byte_count) REQUIRES_SHARED(art::Locks::mutator_lock_) = 0;
+  };
+
+  AllocationManager();
+
+  void SetAllocListener(AllocationCallback* callback);
+  void RemoveAllocListener();
+
+  static AllocationManager* Get();
+
+  void PauseAllocations(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void ResumeAllocations(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  void EnableAllocationCallback(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void DisableAllocationCallback(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+  template<typename T>
+  void PauseForAllocation(art::Thread* self, T msg) REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void IncrListenerInstall(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void DecrListenerInstall(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  AllocationCallback* callback_ = nullptr;
+  uint32_t listener_refcount_ GUARDED_BY(alloc_listener_mutex_) = 0;
+  std::atomic<art::Thread*> allocations_paused_thread_ = nullptr;
+  std::atomic<bool> callback_enabled_ = false;
+  std::unique_ptr<JvmtiAllocationListener> alloc_listener_ = nullptr;
+  art::Mutex alloc_listener_mutex_ ACQUIRED_AFTER(art::Locks::user_code_suspension_lock_);
+  art::ConditionVariable alloc_pause_cv_;
+
+  friend class JvmtiAllocationListener;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_ALLOC_MANAGER_H_
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 56406fc..64a02e8 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -31,6 +31,7 @@
 
 #include <android-base/thread_annotations.h>
 
+#include "alloc_manager.h"
 #include "base/locks.h"
 #include "base/mutex.h"
 #include "events-inl.h"
@@ -312,9 +313,9 @@
   DISALLOW_COPY_AND_ASSIGN(JvmtiDdmChunkListener);
 };
 
-class JvmtiAllocationListener : public art::gc::AllocationListener {
+class JvmtiEventAllocationListener : public AllocationManager::AllocationCallback {
  public:
-  explicit JvmtiAllocationListener(EventHandler* handler) : handler_(handler) {}
+  explicit JvmtiEventAllocationListener(EventHandler* handler) : handler_(handler) {}
 
   void ObjectAllocated(art::Thread* self, art::ObjPtr<art::mirror::Object>* obj, size_t byte_count)
       override REQUIRES_SHARED(art::Locks::mutator_lock_) {
@@ -349,15 +350,14 @@
   EventHandler* handler_;
 };
 
-static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, bool enable) {
+static void SetupObjectAllocationTracking(bool enable) {
   // We must not hold the mutator lock here, but if we're in FastJNI, for example, we might. For
   // now, do a workaround: (possibly) acquire and release.
   art::ScopedObjectAccess soa(art::Thread::Current());
-  art::ScopedThreadSuspension sts(soa.Self(), art::ThreadState::kSuspended);
   if (enable) {
-    art::Runtime::Current()->GetHeap()->SetAllocationListener(listener);
+    AllocationManager::Get()->EnableAllocationCallback(soa.Self());
   } else {
-    art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+    AllocationManager::Get()->DisableAllocationCallback(soa.Self());
   }
 }
 
@@ -1327,7 +1327,7 @@
       SetupDdmTracking(ddm_listener_.get(), enable);
       return;
     case ArtJvmtiEvent::kVmObjectAlloc:
-      SetupObjectAllocationTracking(alloc_listener_.get(), enable);
+      SetupObjectAllocationTracking(enable);
       return;
     case ArtJvmtiEvent::kGarbageCollectionStart:
     case ArtJvmtiEvent::kGarbageCollectionFinish:
@@ -1665,13 +1665,15 @@
   art::ScopedSuspendAll ssa("jvmti method tracing uninstallation");
   // Just remove every possible event.
   art::Runtime::Current()->GetInstrumentation()->RemoveListener(method_trace_listener_.get(), ~0);
+  AllocationManager::Get()->RemoveAllocListener();
 }
 
 EventHandler::EventHandler()
   : envs_lock_("JVMTI Environment List Lock", art::LockLevel::kPostMutatorTopLockLevel),
     frame_pop_enabled(false),
     internal_event_refcount_({0}) {
-  alloc_listener_.reset(new JvmtiAllocationListener(this));
+  alloc_listener_.reset(new JvmtiEventAllocationListener(this));
+  AllocationManager::Get()->SetAllocListener(alloc_listener_.get());
   ddm_listener_.reset(new JvmtiDdmChunkListener(this));
   gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
   method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index c9d587a..d4eb171 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -34,7 +34,7 @@
 namespace openjdkjvmti {
 
 struct ArtJvmTiEnv;
-class JvmtiAllocationListener;
+class JvmtiEventAllocationListener;
 class JvmtiDdmChunkListener;
 class JvmtiGcPauseListener;
 class JvmtiMethodTraceListener;
@@ -425,7 +425,7 @@
   // A union of all enabled events, anywhere.
   EventMask global_mask;
 
-  std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
+  std::unique_ptr<JvmtiEventAllocationListener> alloc_listener_;
   std::unique_ptr<JvmtiDdmChunkListener> ddm_listener_;
   std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
   std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
index 1d18390..b25b4d1 100644
--- a/openjdkjvmti/ti_heap.cc
+++ b/openjdkjvmti/ti_heap.cc
@@ -17,6 +17,7 @@
 #include "ti_heap.h"
 
 #include <ios>
+#include <unordered_map>
 
 #include "android-base/logging.h"
 #include "android-base/thread_annotations.h"
@@ -1613,8 +1614,9 @@
 namespace {
 
 using ObjectPtr = art::ObjPtr<art::mirror::Object>;
+using ObjectMap = std::unordered_map<ObjectPtr, ObjectPtr, art::HashObjPtr>;
 
-static void ReplaceObjectReferences(ObjectPtr old_obj_ptr, ObjectPtr new_obj_ptr)
+static void ReplaceObjectReferences(const ObjectMap& map)
     REQUIRES(art::Locks::mutator_lock_,
              art::Roles::uninterruptible_) {
   art::Runtime::Current()->GetHeap()->VisitObjectsPaused(
@@ -1623,8 +1625,7 @@
         class ResizeReferenceVisitor {
          public:
           using CompressedObj = art::mirror::CompressedReference<art::mirror::Object>;
-          ResizeReferenceVisitor(ObjectPtr old_arr, ObjectPtr new_arr)
-              : old_obj_(old_arr), new_obj_(new_arr) {}
+          explicit ResizeReferenceVisitor(const ObjectMap& map) : map_(map) {}
 
           // Ignore class roots.
           void VisitRootIfNonNull(CompressedObj* root) const
@@ -1634,20 +1635,29 @@
             }
           }
           void VisitRoot(CompressedObj* root) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
-            if (root->AsMirrorPtr() == old_obj_) {
-              root->Assign(new_obj_);
-              art::WriteBarrier::ForEveryFieldWrite(new_obj_);
+            auto it = map_.find(root->AsMirrorPtr());
+            if (it != map_.end()) {
+              root->Assign(it->second);
+              art::WriteBarrier::ForEveryFieldWrite(it->second);
             }
           }
 
           void operator()(art::ObjPtr<art::mirror::Object> obj,
                           art::MemberOffset off,
-                          bool is_static ATTRIBUTE_UNUSED) const
+                          bool is_static) const
               REQUIRES_SHARED(art::Locks::mutator_lock_) {
-            if (obj->GetFieldObject<art::mirror::Object>(off) == old_obj_) {
+            auto it = map_.find(obj->GetFieldObject<art::mirror::Object>(off));
+            if (it != map_.end()) {
+              UNUSED(is_static);
+              if (UNLIKELY(!is_static && off == art::mirror::Object::ClassOffset())) {
+                // We don't want to update the declaring class of any objects. They will be replaced
+                // in the heap and we need the declaring class to know its size.
+                return;
+              }
               VLOG(plugin) << "Updating field at offset " << off.Uint32Value() << " of type "
                            << obj->GetClass()->PrettyClass();
-              obj->SetFieldObject</*transaction*/ false>(off, new_obj_);
+              obj->SetFieldObject</*transaction*/ false>(off, it->second);
+              art::WriteBarrier::ForEveryFieldWrite(obj);
             }
           }
 
@@ -1659,11 +1669,10 @@
           }
 
          private:
-          ObjectPtr old_obj_;
-          ObjectPtr new_obj_;
+          const ObjectMap& map_;
         };
 
-        ResizeReferenceVisitor rrv(old_obj_ptr, new_obj_ptr);
+        ResizeReferenceVisitor rrv(map);
         if (ref->IsClass()) {
           // Class object native roots are the ArtField and ArtMethod 'declaring_class_' fields
           // which we don't want to be messing with as it would break ref-visitor assumptions about
@@ -1678,13 +1687,12 @@
       });
 }
 
-static void ReplaceStrongRoots(art::Thread* self, ObjectPtr old_obj_ptr, ObjectPtr new_obj_ptr)
+static void ReplaceStrongRoots(art::Thread* self, const ObjectMap& map)
     REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
   // replace root references expcept java frames.
   struct ResizeRootVisitor : public art::RootVisitor {
    public:
-    ResizeRootVisitor(ObjectPtr new_val, ObjectPtr old_val)
-        : new_val_(new_val), old_val_(old_val) {}
+    explicit ResizeRootVisitor(const ObjectMap& map) : map_(map) {}
 
     // TODO It's somewhat annoying to have to have this function implemented twice. It might be
     // good/useful to implement operator= for CompressedReference to allow us to use a template to
@@ -1693,7 +1701,8 @@
         REQUIRES_SHARED(art::Locks::mutator_lock_) {
       art::mirror::Object*** end = roots + count;
       for (art::mirror::Object** obj = *roots; roots != end; obj = *(++roots)) {
-        if (*obj == old_val_) {
+        auto it = map_.find(*obj);
+        if (it != map_.end()) {
           // Java frames might have the JIT doing optimizations (for example loop-unrolling or
           // eliding bounds checks) so we need deopt them once we're done here.
           if (info.GetType() == art::RootType::kRootJavaFrame) {
@@ -1708,7 +1717,7 @@
               threads_with_roots_.insert(info.GetThreadId());
             }
           }
-          *obj = new_val_.Ptr();
+          *obj = it->second.Ptr();
         }
       }
     }
@@ -1719,7 +1728,8 @@
       art::mirror::CompressedReference<art::mirror::Object>** end = roots + count;
       for (art::mirror::CompressedReference<art::mirror::Object>* obj = *roots; roots != end;
            obj = *(++roots)) {
-        if (obj->AsMirrorPtr() == old_val_) {
+        auto it = map_.find(obj->AsMirrorPtr());
+        if (it != map_.end()) {
           // Java frames might have the JIT doing optimizations (for example loop-unrolling or
           // eliding bounds checks) so we need deopt them once we're done here.
           if (info.GetType() == art::RootType::kRootJavaFrame) {
@@ -1734,7 +1744,7 @@
               threads_with_roots_.insert(info.GetThreadId());
             }
           }
-          obj->Assign(new_val_);
+          obj->Assign(it->second);
         }
       }
     }
@@ -1744,11 +1754,10 @@
     }
 
    private:
-    ObjectPtr new_val_;
-    ObjectPtr old_val_;
+    const ObjectMap& map_;
     std::unordered_set<uint32_t> threads_with_roots_;
   };
-  ResizeRootVisitor rrv(new_obj_ptr, old_obj_ptr);
+  ResizeRootVisitor rrv(map);
   art::Runtime::Current()->VisitRoots(&rrv, art::VisitRootFlags::kVisitRootFlagAllRoots);
   // Handle java Frames. Annoyingly the JIT can embed information about the length of the array into
   // the compiled code. By changing the length of the array we potentially invalidate these
@@ -1773,8 +1782,7 @@
 
 static void ReplaceWeakRoots(art::Thread* self,
                              EventHandler* event_handler,
-                             ObjectPtr old_obj_ptr,
-                             ObjectPtr new_obj_ptr)
+                             const ObjectMap& map)
     REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
   // Handle tags. We want to do this seprately from other weak-refs (handled below) because we need
   // to send additional events and handle cases where the agent might have tagged the new
@@ -1786,25 +1794,33 @@
   // situations where the order of weak-ref visiting affects the final tagging state. Since we have
   // the mutator_lock_ and gc-paused throughout this whole process no threads should be able to see
   // the interval where the objects are not tagged.
-  std::unordered_map<ArtJvmTiEnv*, jlong> obsolete_tags;
-  std::unordered_map<ArtJvmTiEnv*, jlong> non_obsolete_tags;
+  struct NewTagValue {
+   public:
+    ObjectPtr obsolete_obj_;
+    jlong obsolete_tag_;
+    ObjectPtr new_obj_;
+    jlong new_tag_;
+  };
+
+  // Map from the environment to the list of <obsolete_tag, new_tag> pairs that were changed.
+  std::unordered_map<ArtJvmTiEnv*, std::vector<NewTagValue>> changed_tags;
   event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) {
     // Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it.
     art::Locks::mutator_lock_->AssertExclusiveHeld(self);
     env->object_tag_table->Lock();
     // Get the tags and clear them (so we don't need to special-case the normal weak-ref visitor)
-    jlong new_tag = 0;
-    jlong obsolete_tag = 0;
-    bool had_new_tag = env->object_tag_table->RemoveLocked(new_obj_ptr, &new_tag);
-    bool had_obsolete_tag = env->object_tag_table->RemoveLocked(old_obj_ptr, &obsolete_tag);
-    // Dispatch event.
-    if (had_obsolete_tag || had_new_tag) {
-      event_handler->DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>(env,
-                                                                               self,
-                                                                               &obsolete_tag,
-                                                                               &new_tag);
-      obsolete_tags[env] = obsolete_tag;
-      non_obsolete_tags[env] = new_tag;
+    for (auto it : map) {
+      jlong new_tag = 0;
+      jlong obsolete_tag = 0;
+      bool had_obsolete_tag = env->object_tag_table->RemoveLocked(it.first, &obsolete_tag);
+      bool had_new_tag = env->object_tag_table->RemoveLocked(it.second, &new_tag);
+      // Dispatch event.
+      if (had_obsolete_tag || had_new_tag) {
+        event_handler->DispatchEventOnEnv<ArtJvmtiEvent::kObsoleteObjectCreated>(
+            env, self, &obsolete_tag, &new_tag);
+        changed_tags.try_emplace(env).first->second.push_back(
+            { it.first, obsolete_tag, it.second, new_tag });
+      }
     }
     // After weak-ref update we need to go back and re-add obsoletes. We wait to avoid having to
     // deal with the visit-weaks overwriting the initial new_obj_ptr tag and generally making things
@@ -1814,34 +1830,34 @@
   // Handle weak-refs.
   struct ReplaceWeaksVisitor : public art::IsMarkedVisitor {
    public:
-    ReplaceWeaksVisitor(ObjectPtr old_obj, ObjectPtr new_obj)
-        : old_obj_(old_obj), new_obj_(new_obj) {}
+    ReplaceWeaksVisitor(const ObjectMap& map) : map_(map) {}
 
     art::mirror::Object* IsMarked(art::mirror::Object* obj)
         REQUIRES_SHARED(art::Locks::mutator_lock_) {
-      if (obj == old_obj_) {
-        return new_obj_.Ptr();
+      auto it = map_.find(obj);
+      if (it != map_.end()) {
+        return it->second.Ptr();
       } else {
         return obj;
       }
     }
 
    private:
-    ObjectPtr old_obj_;
-    ObjectPtr new_obj_;
+    const ObjectMap& map_;
   };
-  ReplaceWeaksVisitor rwv(old_obj_ptr, new_obj_ptr);
+  ReplaceWeaksVisitor rwv(map);
   art::Runtime::Current()->SweepSystemWeaks(&rwv);
   // Re-add the object tags. At this point all weak-references to the old_obj_ptr are gone.
   event_handler->ForEachEnv(self, [&](ArtJvmTiEnv* env) {
     // Cannot have REQUIRES(art::Locks::mutator_lock_) since ForEachEnv doesn't require it.
     art::Locks::mutator_lock_->AssertExclusiveHeld(self);
     env->object_tag_table->Lock();
-    if (obsolete_tags.find(env) != obsolete_tags.end()) {
-      env->object_tag_table->SetLocked(old_obj_ptr, obsolete_tags[env]);
-    }
-    if (non_obsolete_tags.find(env) != non_obsolete_tags.end()) {
-      env->object_tag_table->SetLocked(new_obj_ptr, non_obsolete_tags[env]);
+    auto it = changed_tags.find(env);
+    if (it != changed_tags.end()) {
+      for (const NewTagValue& v : it->second) {
+        env->object_tag_table->SetLocked(v.obsolete_obj_, v.obsolete_tag_);
+        env->object_tag_table->SetLocked(v.new_obj_, v.new_tag_);
+      }
     }
     env->object_tag_table->Unlock();
   });
@@ -1852,9 +1868,14 @@
 void HeapExtensions::ReplaceReference(art::Thread* self,
                                       art::ObjPtr<art::mirror::Object> old_obj_ptr,
                                       art::ObjPtr<art::mirror::Object> new_obj_ptr) {
-  ReplaceObjectReferences(old_obj_ptr, new_obj_ptr);
-  ReplaceStrongRoots(self, old_obj_ptr, new_obj_ptr);
-  ReplaceWeakRoots(self, HeapExtensions::gEventHandler, old_obj_ptr, new_obj_ptr);
+  ObjectMap map { { old_obj_ptr, new_obj_ptr } };
+  ReplaceReferences(self, map);
+}
+
+void HeapExtensions::ReplaceReferences(art::Thread* self, const ObjectMap& map) {
+  ReplaceObjectReferences(map);
+  ReplaceStrongRoots(self, map);
+  ReplaceWeakRoots(self, HeapExtensions::gEventHandler, map);
 }
 
 jvmtiError HeapExtensions::ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size) {
diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
index 2e27cc7..ee8b4d6 100644
--- a/openjdkjvmti/ti_heap.h
+++ b/openjdkjvmti/ti_heap.h
@@ -17,6 +17,8 @@
 #ifndef ART_OPENJDKJVMTI_TI_HEAP_H_
 #define ART_OPENJDKJVMTI_TI_HEAP_H_
 
+#include <unordered_map>
+
 #include "jvmti.h"
 
 #include "base/locks.h"
@@ -24,6 +26,7 @@
 namespace art {
 class Thread;
 template<typename T> class ObjPtr;
+class HashObjPtr;
 namespace mirror {
 class Object;
 }  // namespace mirror
@@ -88,6 +91,13 @@
 
   static jvmtiError JNICALL ChangeArraySize(jvmtiEnv* env, jobject arr, jsize new_size);
 
+  static void ReplaceReferences(
+      art::Thread* self,
+      const std::unordered_map<art::ObjPtr<art::mirror::Object>,
+                               art::ObjPtr<art::mirror::Object>,
+                               art::HashObjPtr>& refs)
+        REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_);
+
   static void ReplaceReference(art::Thread* self,
                                art::ObjPtr<art::mirror::Object> original,
                                art::ObjPtr<art::mirror::Object> replacement)
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
index 87080ca..ebbe6ac 100644
--- a/openjdkjvmti/ti_redefine.cc
+++ b/openjdkjvmti/ti_redefine.cc
@@ -42,6 +42,8 @@
 #include <android-base/logging.h>
 #include <android-base/stringprintf.h>
 
+#include "alloc_manager.h"
+#include "android-base/macros.h"
 #include "android-base/thread_annotations.h"
 #include "art_field-inl.h"
 #include "art_field.h"
@@ -115,6 +117,7 @@
 #include "reflective_value_visitor.h"
 #include "runtime.h"
 #include "runtime_globals.h"
+#include "scoped_thread_state_change.h"
 #include "stack.h"
 #include "thread.h"
 #include "thread_list.h"
@@ -460,30 +463,33 @@
     }
     // Check for already existing non-static fields/methods.
     // TODO Remove this once we support generic method/field addition.
-    bool non_static_method = false;
-    klass->VisitMethods([&](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-      // Since direct-methods (ie privates + <init> are not in any vtable/iftable we can update
-      // them).
-      if (!m->IsDirect()) {
-        non_static_method = true;
-        *error_msg = StringPrintf("%s has a non-direct function %s",
-                                  klass->PrettyClass().c_str(),
-                                  m->PrettyMethod().c_str());
+    if (!klass->IsFinal()) {
+      bool non_static_method = false;
+      klass->VisitMethods([&](art::ArtMethod* m) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        // Since direct-methods (ie privates + <init> are not in any vtable/iftable we can update
+        // them).
+        if (!m->IsDirect()) {
+          non_static_method = true;
+          *error_msg = StringPrintf("%s has a non-direct function %s",
+                                    klass->PrettyClass().c_str(),
+                                    m->PrettyMethod().c_str());
+        }
+      }, art::kRuntimePointerSize);
+      if (non_static_method) {
+        return ERR(UNMODIFIABLE_CLASS);
       }
-    }, art::kRuntimePointerSize);
-    if (non_static_method) {
-      return ERR(UNMODIFIABLE_CLASS);
-    }
-    bool non_static_field = false;
-    klass->VisitFields([&](art::ArtField* f) REQUIRES_SHARED(art::Locks::mutator_lock_) {
-      if (!f->IsStatic()) {
-        non_static_field = true;
-        *error_msg = StringPrintf(
-            "%s has a non-static field %s", klass->PrettyClass().c_str(), f->PrettyField().c_str());
+      bool non_static_field = false;
+      klass->VisitFields([&](art::ArtField* f) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        if (!f->IsStatic()) {
+          non_static_field = true;
+          *error_msg = StringPrintf("%s has a non-static field %s",
+                                    klass->PrettyClass().c_str(),
+                                    f->PrettyField().c_str());
+        }
+      });
+      if (non_static_field) {
+        return ERR(UNMODIFIABLE_CLASS);
       }
-    });
-    if (non_static_field) {
-      return ERR(UNMODIFIABLE_CLASS);
     }
     // Check for fields/methods which were returned before moving to index jni id type.
     // TODO We might want to rework how this is done. Once full redefinition is implemented we will
@@ -985,9 +991,12 @@
           return old_method_id == new_method_id;
         });
 
+    if (!new_method.IsStaticOrDirect()) {
+      RecordHasVirtualMembers();
+    }
     if (old_iter == old_methods.cend()) {
       // TODO Support adding non-static methods.
-      if (is_structural && new_method.IsStaticOrDirect()) {
+      if (is_structural && (new_method.IsStaticOrDirect() || h_klass->IsFinal())) {
         RecordNewMethodAdded();
       } else {
         RecordFailure(
@@ -1046,9 +1055,12 @@
           FieldNameAndSignature old_field_id(&old_dex_file, old_iter.GetIndex());
           return old_field_id == new_field_id;
         });
+    if (!new_field.IsStatic()) {
+      RecordHasVirtualMembers();
+    }
     if (old_iter == old_fields.cend()) {
       // TODO Support adding non-static fields.
-      if (driver_->IsStructuralRedefinition() && new_field.IsStatic()) {
+      if (driver_->IsStructuralRedefinition() && (new_field.IsStatic() || h_klass->IsFinal())) {
         RecordNewFieldAdded();
       } else {
         RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
@@ -1169,6 +1181,10 @@
   jvmtiError res;
   if (driver_->type_ == RedefinitionType::kStructural && this->IsStructuralRedefinition()) {
     res = Redefiner::GetClassRedefinitionError<RedefinitionType::kStructural>(h_klass, &err);
+    if (res == OK && HasVirtualMembers() && h_klass->IsFinalizable()) {
+      res = ERR(INTERNAL);
+      err = "Cannot redefine finalizable objects at this time.";
+    }
   } else {
     res = Redefiner::GetClassRedefinitionError<RedefinitionType::kNormal>(h_klass, &err);
   }
@@ -1201,9 +1217,11 @@
     kSlotOldObsoleteMethods = 6,
     kSlotOldDexCaches = 7,
     kSlotNewClassObject = 8,
+    kSlotOldInstanceObjects = 9,
+    kSlotNewInstanceObjects = 10,
 
     // Must be last one.
-    kNumSlots = 9,
+    kNumSlots = 11,
   };
 
   // This needs to have a HandleScope passed in that is capable of creating a new Handle without
@@ -1269,6 +1287,18 @@
     return art::ObjPtr<art::mirror::Class>::DownCast(GetSlot(klass_index, kSlotNewClassObject));
   }
 
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetOldInstanceObjects(
+      jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>>::DownCast(
+        GetSlot(klass_index, kSlotOldInstanceObjects));
+  }
+
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetNewInstanceObjects(
+      jint klass_index) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>>::DownCast(
+        GetSlot(klass_index, kSlotNewInstanceObjects));
+  }
+
   void SetSourceClassLoader(jint klass_index, art::ObjPtr<art::mirror::ClassLoader> loader)
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     SetSlot(klass_index, kSlotSourceClassLoader, loader);
@@ -1308,6 +1338,16 @@
     SetSlot(klass_index, kSlotNewClassObject, klass);
   }
 
+  void SetOldInstanceObjects(jint klass_index,
+                             art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotOldInstanceObjects, objs);
+  }
+  void SetNewInstanceObjects(jint klass_index,
+                             art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewInstanceObjects, objs);
+  }
   int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return arr_->GetLength() / kNumSlots;
   }
@@ -1392,6 +1432,11 @@
     return *this;
   }
 
+  // Compat for STL iterators.
+  RedefinitionDataIter& operator*() {
+    return *this;
+  }
+
   Redefiner::ClassRedefinition& GetRedefinition() REQUIRES_SHARED(art::Locks::mutator_lock_) {
     return (*holder_.GetRedefinitions())[idx_];
   }
@@ -1438,6 +1483,14 @@
     return holder_.GetNewClassObject(idx_);
   }
 
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetOldInstanceObjects() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetOldInstanceObjects(idx_);
+  }
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> GetNewInstanceObjects() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetNewInstanceObjects(idx_);
+  }
   int32_t GetIndex() const {
     return idx_;
   }
@@ -1478,6 +1531,14 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_) {
     holder_.SetNewClassObject(idx_, klass);
   }
+  void SetOldInstanceObjects(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetOldInstanceObjects(idx_, objs);
+  }
+  void SetNewInstanceObjects(art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> objs)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetNewInstanceObjects(idx_, objs);
+  }
 
  private:
   int32_t idx_;
@@ -1580,6 +1641,75 @@
   return true;
 }
 
+bool Redefiner::ClassRedefinition::CollectAndCreateNewInstances(
+    /*out*/ RedefinitionDataIter* cur_data) {
+  if (!IsStructuralRedefinition()) {
+    return true;
+  }
+  art::VariableSizedHandleScope hs(driver_->self_);
+  art::Handle<art::mirror::Class> old_klass(hs.NewHandle(cur_data->GetMirrorClass()));
+  std::vector<art::Handle<art::mirror::Object>> old_instances;
+  art::gc::Heap* heap = driver_->runtime_->GetHeap();
+  auto is_instance = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (HasVirtualMembers()) {
+      return old_klass->IsAssignableFrom(obj->GetClass());
+    } else {
+      // We don't need to deal with objects of subtypes when we don't modify virtuals since the
+      // vtable + field layout will remain the same.
+      return old_klass.Get() == obj->GetClass();
+    }
+  };
+  heap->VisitObjects([&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (is_instance(obj)) {
+      CHECK(old_klass.Get() == obj->GetClass()) << "No support for subtypes yet!";
+      old_instances.push_back(hs.NewHandle(obj));
+    }
+  });
+  VLOG(plugin) << "Collected " << old_instances.size() << " instances to recreate!";
+
+  art::Handle<art::mirror::Class> obj_array_class(
+      hs.NewHandle(art::GetClassRoot<art::mirror::ObjectArray<art::mirror::Object>>(
+          driver_->runtime_->GetClassLinker())));
+  art::Handle<art::mirror::ObjectArray<art::mirror::Object>> old_instances_arr(
+      hs.NewHandle(art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+          driver_->self_, obj_array_class.Get(), old_instances.size())));
+  if (old_instances_arr.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate old_instance arrays!");
+    return false;
+  }
+  for (uint32_t i = 0; i < old_instances.size(); ++i) {
+    old_instances_arr->Set(i, old_instances[i].Get());
+  }
+  cur_data->SetOldInstanceObjects(old_instances_arr.Get());
+
+  art::Handle<art::mirror::ObjectArray<art::mirror::Object>> new_instances_arr(
+      hs.NewHandle(art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+          driver_->self_, obj_array_class.Get(), old_instances.size())));
+  if (new_instances_arr.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate new_instance arrays!");
+    return false;
+  }
+  art::Handle<art::mirror::Class> new_klass(hs.NewHandle(cur_data->GetNewClassObject()));
+  for (uint32_t i = 0; i < old_instances.size(); ++i) {
+    art::ObjPtr<art::mirror::Object> new_instance(new_klass->AllocObject(driver_->self_));
+    if (new_instance.IsNull()) {
+      driver_->self_->AssertPendingOOMException();
+      driver_->self_->ClearException();
+      std::string msg(
+          StringPrintf("Could not allocate instance %d of %zu", i, old_instances.size()));
+      RecordFailure(ERR(OUT_OF_MEMORY), msg);
+      return false;
+    }
+    new_instances_arr->Set(i, new_instance);
+  }
+  cur_data->SetNewInstanceObjects(new_instances_arr.Get());
+  return true;
+}
+
 bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
     /*out*/RedefinitionDataIter* cur_data) {
   art::ScopedObjectAccessUnchecked soa(driver_->self_);
@@ -1801,6 +1931,16 @@
   return true;
 }
 
+bool Redefiner::CollectAndCreateNewInstances(RedefinitionDataHolder& holder) {
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    // Allocate the data this redefinition requires.
+    if (!data.GetRedefinition().CollectAndCreateNewInstances(&data)) {
+      return false;
+    }
+  }
+  return true;
+}
+
 bool Redefiner::FinishAllRemainingAllocations(RedefinitionDataHolder& holder) {
   for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
     // Allocate the data this redefinition requires.
@@ -1849,6 +1989,36 @@
   art::Thread* self_;
 };
 
+class ScopedSuspendAllocations {
+ public:
+  ScopedSuspendAllocations(art::Runtime* runtime, RedefinitionDataHolder& h)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : paused_(false) {
+    if (std::any_of(h.begin(),
+                    h.end(),
+                    [](auto r) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+                      return r.GetRedefinition().IsStructuralRedefinition();
+                    })) {
+      VLOG(plugin) << "Pausing allocations for structural redefinition.";
+      paused_ = true;
+      AllocationManager::Get()->PauseAllocations(art::Thread::Current());
+      // Collect garbage so we don't need to recreate as much.
+      runtime->GetHeap()->CollectGarbage(/*clear_soft_references=*/false);
+    }
+  }
+
+  ~ScopedSuspendAllocations() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (paused_) {
+      AllocationManager::Get()->ResumeAllocations(art::Thread::Current());
+    }
+  }
+
+ private:
+  bool paused_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedSuspendAllocations);
+};
+
 jvmtiError Redefiner::Run() {
   art::StackHandleScope<1> hs(self_);
   // Allocate an array to hold onto all java temporary objects associated with this redefinition.
@@ -1873,6 +2043,11 @@
     return result_;
   }
 
+  ScopedSuspendAllocations suspend_alloc(runtime_, holder);
+  if (!CollectAndCreateNewInstances(holder)) {
+    return result_;
+  }
+
   // At this point we can no longer fail without corrupting the runtime state.
   for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
     art::ClassLinker* cl = runtime_->GetClassLinker();
@@ -2023,6 +2198,120 @@
   }
 }
 
+static void CopyField(art::ObjPtr<art::mirror::Object> target,
+                      art::ArtField* new_field,
+                      art::ObjPtr<art::mirror::Object> source,
+                      art::ArtField& old_field) REQUIRES(art::Locks::mutator_lock_) {
+  art::Primitive::Type ftype = old_field.GetTypeAsPrimitiveType();
+  CHECK_EQ(ftype, new_field->GetTypeAsPrimitiveType())
+      << old_field.PrettyField() << " vs " << new_field->PrettyField();
+  if (ftype == art::Primitive::kPrimNot) {
+    new_field->SetObject<false>(target, old_field.GetObject(source));
+  } else {
+    switch (ftype) {
+#define UPDATE_FIELD(TYPE)                                            \
+  case art::Primitive::kPrim##TYPE:                                   \
+    new_field->Set##TYPE<false>(target, old_field.Get##TYPE(source)); \
+    break
+      UPDATE_FIELD(Int);
+      UPDATE_FIELD(Float);
+      UPDATE_FIELD(Long);
+      UPDATE_FIELD(Double);
+      UPDATE_FIELD(Short);
+      UPDATE_FIELD(Char);
+      UPDATE_FIELD(Byte);
+      UPDATE_FIELD(Boolean);
+      case art::Primitive::kPrimNot:
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
+        UNREACHABLE();
+#undef UPDATE_FIELD
+    }
+  }
+}
+
+static void CopyFields(bool is_static,
+                       art::ObjPtr<art::mirror::Object> target,
+                       art::ObjPtr<art::mirror::Class> target_class,
+                       art::ObjPtr<art::mirror::Object> source,
+                       art::ObjPtr<art::mirror::Class> source_class)
+    REQUIRES(art::Locks::mutator_lock_) {
+  DCHECK(!source_class->IsObjectClass() && !target_class->IsObjectClass())
+      << "Should not be overriding object class fields. Target: " << target_class->PrettyClass()
+      << " Source: " << source_class->PrettyClass();
+  for (art::ArtField& f : (is_static ? source_class->GetSFields() : source_class->GetIFields())) {
+    art::ArtField* new_field =
+        (is_static ? target_class->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor())
+                   : target_class->FindDeclaredInstanceField(f.GetName(), f.GetTypeDescriptor()));
+    CHECK(new_field != nullptr) << "could not find new version of " << f.PrettyField();
+    CopyField(target, new_field, source, f);
+  }
+  if (!is_static && !target_class->GetSuperClass()->IsObjectClass()) {
+    CopyFields(
+        is_static, target, target_class->GetSuperClass(), source, source_class->GetSuperClass());
+  }
+}
+
+static void ClearField(art::ObjPtr<art::mirror::Object> target, art::ArtField& field)
+    REQUIRES(art::Locks::mutator_lock_) {
+  art::Primitive::Type ftype = field.GetTypeAsPrimitiveType();
+  if (ftype == art::Primitive::kPrimNot) {
+    field.SetObject<false>(target, nullptr);
+  } else {
+    switch (ftype) {
+#define UPDATE_FIELD(TYPE)             \
+  case art::Primitive::kPrim##TYPE:    \
+    field.Set##TYPE<false>(target, 0); \
+    break
+      UPDATE_FIELD(Int);
+      UPDATE_FIELD(Float);
+      UPDATE_FIELD(Long);
+      UPDATE_FIELD(Double);
+      UPDATE_FIELD(Short);
+      UPDATE_FIELD(Char);
+      UPDATE_FIELD(Byte);
+      UPDATE_FIELD(Boolean);
+      case art::Primitive::kPrimNot:
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
+        UNREACHABLE();
+#undef UPDATE_FIELD
+    }
+  }
+}
+
+static void ClearFields(bool is_static,
+                        art::ObjPtr<art::mirror::Object> target,
+                        art::ObjPtr<art::mirror::Class> target_class)
+    REQUIRES(art::Locks::mutator_lock_) {
+  DCHECK(!target_class->IsObjectClass());
+  for (art::ArtField& f : (is_static ? target_class->GetSFields() : target_class->GetIFields())) {
+    ClearField(target, f);
+  }
+  if (!is_static && !target_class->GetSuperClass()->IsObjectClass()) {
+    ClearFields(is_static, target, target_class->GetSuperClass());
+  }
+}
+
+static void CopyAndClearFields(bool is_static,
+                               art::ObjPtr<art::mirror::Object> target,
+                               art::ObjPtr<art::mirror::Class> target_class,
+                               art::ObjPtr<art::mirror::Object> source,
+                               art::ObjPtr<art::mirror::Class> source_class)
+    REQUIRES(art::Locks::mutator_lock_) {
+  // Copy all non-j.l.Object fields
+  CopyFields(is_static, target, target_class, source, source_class);
+  // Copy the lock-word.
+  target->SetLockWord(source->GetLockWord(false), false);
+  // Clear (reset) the old one.
+  source->SetLockWord(art::LockWord::Default(), false);
+  art::WriteBarrier::ForEveryFieldWrite(target);
+
+  // Clear the fields from the old class. We don't need it anymore.
+  ClearFields(is_static, source, source_class);
+  art::WriteBarrier::ForEveryFieldWrite(source);
+}
+
 void Redefiner::ClassRedefinition::UpdateClassStructurally(const RedefinitionDataIter& holder) {
   DCHECK(IsStructuralRedefinition());
   // LETS GO. We've got all new class structures so no need to do all the updating of the stacks.
@@ -2036,40 +2325,24 @@
   std::map<art::ArtMethod*, art::ArtMethod*> method_map;
   std::map<art::ArtField*, art::ArtField*> field_map;
   CollectNewFieldAndMethodMappings(holder, &method_map, &field_map);
-  // Copy over the fields of the object.
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> new_instances(
+      holder.GetNewInstanceObjects());
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> old_instances(
+      holder.GetOldInstanceObjects());
   CHECK(!orig.IsNull());
   CHECK(!replacement.IsNull());
-  for (art::ArtField& f : orig->GetSFields()) {
-    art::ArtField* new_field =
-        replacement->FindDeclaredStaticField(f.GetName(), f.GetTypeDescriptor());
-    CHECK(new_field != nullptr) << "could not find new version of " << f.PrettyField();
-    art::Primitive::Type ftype = f.GetTypeAsPrimitiveType();
-    CHECK_EQ(ftype, new_field->GetTypeAsPrimitiveType())
-        << f.PrettyField() << " vs " << new_field->PrettyField();
-    if (ftype == art::Primitive::kPrimNot) {
-      new_field->SetObject<false>(replacement, f.GetObject(orig));
-    } else {
-      switch (ftype) {
-#define UPDATE_FIELD(TYPE)                                       \
-  case art::Primitive::kPrim##TYPE:                              \
-    new_field->Set##TYPE<false>(replacement, f.Get##TYPE(orig)); \
-    break
+  // Copy over the static fields of the class and all the instance fields.
+  CopyAndClearFields(/*is_static=*/true, replacement, replacement, orig, orig);
 
-        UPDATE_FIELD(Int);
-        UPDATE_FIELD(Float);
-        UPDATE_FIELD(Long);
-        UPDATE_FIELD(Double);
-        UPDATE_FIELD(Short);
-        UPDATE_FIELD(Char);
-        UPDATE_FIELD(Byte);
-        UPDATE_FIELD(Boolean);
-        case art::Primitive::kPrimNot:
-        case art::Primitive::kPrimVoid:
-          LOG(FATAL) << "Unexpected field with type " << ftype << " found!";
-          UNREACHABLE();
-#undef UPDATE_FIELD
-      }
-    }
+  // Copy and clear the fields of the old-instances.
+  for (int32_t i = 0; i < old_instances->GetLength(); i++) {
+    art::ObjPtr<art::mirror::Object> old_instance(old_instances->Get(i));
+    art::ObjPtr<art::mirror::Object> new_instance(new_instances->Get(i));
+    CopyAndClearFields(/*is_static=*/false,
+                       new_instance,
+                       new_instance->GetClass(),
+                       old_instance,
+                       old_instance->GetClass());
   }
   // Mark old class obsolete.
   orig->SetObsoleteObject();
@@ -2079,9 +2352,6 @@
     m.SetDontCompile();
     DCHECK_EQ(orig, m.GetDeclaringClass());
   }
-  // Copy the lock-word
-  replacement->SetLockWord(orig->GetLockWord(false), false);
-  orig->SetLockWord(art::LockWord::Default(), false);
   // Update live pointers in ART code.
   auto could_change_resolution_of = [&](auto* field_or_method,
                                         const auto& info) REQUIRES(art::Locks::mutator_lock_) {
@@ -2166,39 +2436,23 @@
   // Force every frame of every thread to deoptimize (any frame might have eg offsets compiled in).
   driver_->runtime_->GetInstrumentation()->DeoptimizeAllThreadFrames();
 
-  // Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields.
-  // This replaces the mirror::Class in 'holder' as well. It's magic!
-  HeapExtensions::ReplaceReference(driver_->self_, orig, replacement);
+  std::unordered_map<art::ObjPtr<art::mirror::Object>,
+                     art::ObjPtr<art::mirror::Object>,
+                     art::HashObjPtr> map;
+  map.emplace(orig, replacement);
+  for (int32_t i = 0; i < old_instances->GetLength(); i++) {
+    map.emplace(old_instances->Get(i), new_instances->Get(i));
+  }
+
+  // Actually perform the general replacement. This doesn't affect ArtMethod/ArtFields. It does
+  // affect the declaring_class field of all the obsolete objects, which is unfortunate and needs to
+  // be undone. This replaces the mirror::Class in 'holder' as well. It's magic!
+  HeapExtensions::ReplaceReferences(driver_->self_, map);
 
   // Save the old class so that the JIT gc doesn't get confused by it being collected before the
   // jit code. This is also needed to keep the dex-caches of any obsolete methods live.
   replacement->GetExtData()->SetObsoleteClass(orig);
 
-  // Clear the static fields of the old-class.
-  for (art::ArtField& f : orig->GetSFields()) {
-    switch (f.GetTypeAsPrimitiveType()) {
-    #define UPDATE_FIELD(TYPE)            \
-      case art::Primitive::kPrim ## TYPE: \
-        f.Set ## TYPE <false>(orig, 0);   \
-        break
-
-      UPDATE_FIELD(Int);
-      UPDATE_FIELD(Float);
-      UPDATE_FIELD(Long);
-      UPDATE_FIELD(Double);
-      UPDATE_FIELD(Short);
-      UPDATE_FIELD(Char);
-      UPDATE_FIELD(Byte);
-      UPDATE_FIELD(Boolean);
-      case art::Primitive::kPrimNot:
-        f.SetObject<false>(orig, nullptr);
-        break;
-      case art::Primitive::kPrimVoid:
-        LOG(FATAL) << "Unexpected field with type void found!";
-        UNREACHABLE();
-    #undef UPDATE_FIELD
-    }
-  }
 
   art::jit::Jit* jit = driver_->runtime_->GetJit();
   if (jit != nullptr) {
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
index 58a688c..cedce92 100644
--- a/openjdkjvmti/ti_redefine.h
+++ b/openjdkjvmti/ti_redefine.h
@@ -41,6 +41,7 @@
 #include "art_jvmti.h"
 #include "base/array_ref.h"
 #include "base/globals.h"
+#include "dex/class_accessor.h"
 #include "dex/dex_file.h"
 #include "dex/dex_file_structs.h"
 #include "jni/jni_env_ext-inl.h"
@@ -155,6 +156,9 @@
     bool FinishRemainingAllocations(/*out*/RedefinitionDataIter* cur_data)
         REQUIRES_SHARED(art::Locks::mutator_lock_);
 
+    bool CollectAndCreateNewInstances(/*out*/RedefinitionDataIter* cur_data)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
     bool AllocateAndRememberNewDexFileCookie(
         art::Handle<art::mirror::ClassLoader> source_class_loader,
         art::Handle<art::mirror::Object> dex_file_obj,
@@ -234,8 +238,14 @@
 
     void RecordNewMethodAdded();
     void RecordNewFieldAdded();
+    void RecordHasVirtualMembers() {
+      has_virtuals_ = true;
+    }
 
-   private:
+    bool HasVirtualMembers() const {
+      return has_virtuals_;
+    }
+
     bool IsStructuralRedefinition() const {
       DCHECK(!(added_fields_ || added_methods_) || driver_->IsStructuralRedefinition())
           << "added_fields_: " << added_fields_ << " added_methods_: " << added_methods_
@@ -243,6 +253,7 @@
       return driver_->IsStructuralRedefinition() && (added_fields_ || added_methods_);
     }
 
+   private:
     void UpdateClassStructurally(const RedefinitionDataIter& cur_data)
         REQUIRES(art::Locks::mutator_lock_);
 
@@ -257,6 +268,7 @@
 
     bool added_fields_ = false;
     bool added_methods_ = false;
+    bool has_virtuals_ = false;
 
     // Does the class need to be reverified due to verification soft-fails possibly forcing
     // interpreter or lock-counting?
@@ -311,6 +323,8 @@
       REQUIRES_SHARED(art::Locks::mutator_lock_);
   bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
       REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool CollectAndCreateNewInstances(RedefinitionDataHolder& holder)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
   void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_);
   void ReverifyClasses(RedefinitionDataHolder& holder) REQUIRES_SHARED(art::Locks::mutator_lock_);
   void UnregisterAllBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
diff --git a/runtime/base/locks.h b/runtime/base/locks.h
index 4b85df0..c3518f3 100644
--- a/runtime/base/locks.h
+++ b/runtime/base/locks.h
@@ -129,6 +129,9 @@
 
   kMutatorLock,
   kInstrumentEntrypointsLock,
+  // This is a generic lock level for a top-level lock meant to be gained after having the
+  // UserCodeSuspensionLock.
+  kPostUserCodeSuspensionTopLevelLock,
   kUserCodeSuspensionLock,
   kZygoteCreationLock,
 
diff --git a/runtime/gc/allocation_listener.h b/runtime/gc/allocation_listener.h
index a578252..376b524 100644
--- a/runtime/gc/allocation_listener.h
+++ b/runtime/gc/allocation_listener.h
@@ -23,11 +23,13 @@
 #include "base/locks.h"
 #include "base/macros.h"
 #include "gc_root.h"
+#include "handle.h"
 #include "obj_ptr.h"
 
 namespace art {
 
 namespace mirror {
+class Class;
 class Object;
 }  // namespace mirror
 
@@ -39,6 +41,26 @@
  public:
   virtual ~AllocationListener() {}
 
+  // An event to allow a listener to intercept and modify an allocation before it takes place.
+  // The listener can change the byte_count and type as they see fit. Extreme caution should be used
+  // when doing so. This can also be used to control allocation occurring on another thread.
+  //
+  // Concurrency guarantees: This might be called multiple times for each single allocation. It's
+  // guaranteed that, between the final call to the callback and the object being visible to
+  // heap-walks there are no suspensions. If a suspension was allowed between these events the
+  // callback will be invoked again after passing the suspend point.
+  //
+  // If the alloc succeeds it is guaranteed there are no suspend-points between the last return of
+  // PreObjectAlloc and the newly allocated object being visible to heap-walks.
+  //
+  // This can also be used to make any last-minute changes to the type or size of the allocation.
+  virtual void PreObjectAllocated(Thread* self ATTRIBUTE_UNUSED,
+                                  MutableHandle<mirror::Class> type ATTRIBUTE_UNUSED,
+                                  size_t* byte_count ATTRIBUTE_UNUSED)
+      REQUIRES(!Roles::uninterruptible_) REQUIRES_SHARED(Locks::mutator_lock_) {}
+  // Fast check if we want to get the PreObjectAllocated callback, to avoid the expense of creating
+  // handles. Defaults to false.
+  virtual bool HasPreAlloc() const { return false; }
   virtual void ObjectAllocated(Thread* self, ObjPtr<mirror::Object>* obj, size_t byte_count)
       REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index c1b3a63..04632ef 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -65,10 +65,30 @@
     HandleWrapperObjPtr<mirror::Class> h = hs.NewHandleWrapper(&klass);
     self->PoisonObjectPointers();
   }
+  auto send_pre_object_allocated = [&]() REQUIRES_SHARED(Locks::mutator_lock_)
+                                         ACQUIRE(Roles::uninterruptible_) {
+    if constexpr (kInstrumented) {
+      AllocationListener* l = nullptr;
+      l = alloc_listener_.load(std::memory_order_seq_cst);
+      if (UNLIKELY(l != nullptr) && UNLIKELY(l->HasPreAlloc())) {
+        StackHandleScope<1> hs(self);
+        HandleWrapperObjPtr<mirror::Class> h_klass(hs.NewHandleWrapper(&klass));
+        l->PreObjectAllocated(self, h_klass, &byte_count);
+      }
+    }
+    return self->StartAssertNoThreadSuspension("Called PreObjectAllocated, no suspend until alloc");
+  };
+  // Do the initial pre-alloc
+  const char* old_cause = send_pre_object_allocated();
+  // We shouldn't have any NoThreadSuspension here!
+  DCHECK(old_cause == nullptr) << old_cause;
+
   // Need to check that we aren't the large object allocator since the large object allocation code
   // path includes this function. If we didn't check we would have an infinite loop.
   ObjPtr<mirror::Object> obj;
   if (kCheckLargeObject && UNLIKELY(ShouldAllocLargeObject(klass, byte_count))) {
+    // AllocLargeObject can suspend and will recall PreObjectAllocated if needed.
+    self->EndAssertNoThreadSuspension(old_cause);
     obj = AllocLargeObject<kInstrumented, PreFenceVisitor>(self, &klass, byte_count,
                                                            pre_fence_visitor);
     if (obj != nullptr) {
@@ -80,6 +100,8 @@
     // If the large object allocation failed, try to use the normal spaces (main space,
     // non moving space). This can happen if there is significant virtual address space
     // fragmentation.
+    // We need to send the PreObjectAllocated again, we might have suspended during our failure.
+    old_cause = send_pre_object_allocated();
   }
   // bytes allocated for the (individual) object.
   size_t bytes_allocated;
@@ -100,6 +122,7 @@
     usable_size = bytes_allocated;
     no_suspend_pre_fence_visitor(obj, usable_size);
     QuasiAtomic::ThreadFenceForConstructor();
+    self->EndAssertNoThreadSuspension(old_cause);
   } else if (
       !kInstrumented && allocator == kAllocatorTypeRosAlloc &&
       (obj = rosalloc_space_->AllocThreadLocal(self, byte_count, &bytes_allocated)) != nullptr &&
@@ -112,6 +135,7 @@
     usable_size = bytes_allocated;
     no_suspend_pre_fence_visitor(obj, usable_size);
     QuasiAtomic::ThreadFenceForConstructor();
+    self->EndAssertNoThreadSuspension(old_cause);
   } else {
     // Bytes allocated that includes bulk thread-local buffer allocations in addition to direct
     // non-TLAB object allocations.
@@ -121,14 +145,19 @@
     if (UNLIKELY(obj == nullptr)) {
       // AllocateInternalWithGc can cause thread suspension, if someone instruments the entrypoints
       // or changes the allocator in a suspend point here, we need to retry the allocation.
+      // It will send the pre-alloc event again.
+      self->EndAssertNoThreadSuspension(old_cause);
       obj = AllocateInternalWithGc(self,
                                    allocator,
                                    kInstrumented,
                                    byte_count,
                                    &bytes_allocated,
                                    &usable_size,
-                                   &bytes_tl_bulk_allocated, &klass);
+                                   &bytes_tl_bulk_allocated,
+                                   &klass,
+                                   &old_cause);
       if (obj == nullptr) {
+        self->EndAssertNoThreadSuspension(old_cause);
         // The only way that we can get a null return if there is no pending exception is if the
         // allocator or instrumentation changed.
         if (!self->IsExceptionPending()) {
@@ -156,6 +185,7 @@
     }
     no_suspend_pre_fence_visitor(obj, usable_size);
     QuasiAtomic::ThreadFenceForConstructor();
+    self->EndAssertNoThreadSuspension(old_cause);
     if (bytes_tl_bulk_allocated > 0) {
       size_t num_bytes_allocated_before =
           num_bytes_allocated_.fetch_add(bytes_tl_bulk_allocated, std::memory_order_relaxed);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 85b79da..a97ff98 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -17,6 +17,7 @@
 #include "heap.h"
 
 #include <limits>
+#include "android-base/thread_annotations.h"
 #if defined(__BIONIC__) || defined(__GLIBC__)
 #include <malloc.h>  // For mallinfo()
 #endif
@@ -1723,11 +1724,37 @@
                                              size_t* bytes_allocated,
                                              size_t* usable_size,
                                              size_t* bytes_tl_bulk_allocated,
-                                             ObjPtr<mirror::Class>* klass) {
+                                             ObjPtr<mirror::Class>* klass,
+                                             /*out*/const char** old_no_thread_suspend_cause) {
   bool was_default_allocator = allocator == GetCurrentAllocator();
   // Make sure there is no pending exception since we may need to throw an OOME.
   self->AssertNoPendingException();
   DCHECK(klass != nullptr);
+  auto release_no_suspend = [&]() RELEASE(Roles::uninterruptible_) {
+    self->EndAssertNoThreadSuspension(*old_no_thread_suspend_cause);
+  };
+  auto send_object_pre_alloc = [&]() ACQUIRE(Roles::uninterruptible_)
+                                     REQUIRES_SHARED(Locks::mutator_lock_) {
+  if (UNLIKELY(instrumented)) {
+    AllocationListener* l = nullptr;
+    l = alloc_listener_.load(std::memory_order_seq_cst);
+    if (UNLIKELY(l != nullptr) && UNLIKELY(l->HasPreAlloc())) {
+      StackHandleScope<1> hs(self);
+      HandleWrapperObjPtr<mirror::Class> h_klass(hs.NewHandleWrapper(klass));
+      l->PreObjectAllocated(self, h_klass, &alloc_size);
+    }
+  }
+  *old_no_thread_suspend_cause =
+      self->StartAssertNoThreadSuspension("Called PreObjectAllocated, no suspend until alloc");
+};
+#define PERFORM_SUSPENDING_OPERATION(op)                                          \
+  [&]() REQUIRES(Roles::uninterruptible_) REQUIRES_SHARED(Locks::mutator_lock_) { \
+    release_no_suspend();                                                         \
+    auto res = (op);                                                              \
+    send_object_pre_alloc();                                                      \
+    return res;                                                                   \
+  }()
+
   StackHandleScope<1> hs(self);
   HandleWrapperObjPtr<mirror::Class> h(hs.NewHandleWrapper(klass));
   // The allocation failed. If the GC is running, block until it completes, and then retry the
@@ -1735,6 +1762,8 @@
   collector::GcType last_gc = WaitForGcToComplete(kGcCauseForAlloc, self);
   // If we were the default allocator but the allocator changed while we were suspended,
   // abort the allocation.
+  // We just waited, call the pre-alloc again.
+  send_object_pre_alloc();
   if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
       (!instrumented && EntrypointsInstrumented())) {
     return nullptr;
@@ -1749,8 +1778,9 @@
   }
 
   collector::GcType tried_type = next_gc_type_;
-  const bool gc_ran =
-      CollectGarbageInternal(tried_type, kGcCauseForAlloc, false) != collector::kGcTypeNone;
+  const bool gc_ran = PERFORM_SUSPENDING_OPERATION(
+      CollectGarbageInternal(tried_type, kGcCauseForAlloc, false) != collector::kGcTypeNone);
+
   if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
       (!instrumented && EntrypointsInstrumented())) {
     return nullptr;
@@ -1769,8 +1799,8 @@
       continue;
     }
     // Attempt to run the collector, if we succeed, re-try the allocation.
-    const bool plan_gc_ran =
-        CollectGarbageInternal(gc_type, kGcCauseForAlloc, false) != collector::kGcTypeNone;
+    const bool plan_gc_ran = PERFORM_SUSPENDING_OPERATION(
+        CollectGarbageInternal(gc_type, kGcCauseForAlloc, false) != collector::kGcTypeNone);
     if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
         (!instrumented && EntrypointsInstrumented())) {
       return nullptr;
@@ -1800,7 +1830,7 @@
   // TODO: Run finalization, but this may cause more allocations to occur.
   // We don't need a WaitForGcToComplete here either.
   DCHECK(!gc_plan_.empty());
-  CollectGarbageInternal(gc_plan_.back(), kGcCauseForAlloc, true);
+  PERFORM_SUSPENDING_OPERATION(CollectGarbageInternal(gc_plan_.back(), kGcCauseForAlloc, true));
   if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
       (!instrumented && EntrypointsInstrumented())) {
     return nullptr;
@@ -1817,7 +1847,8 @@
             current_time - last_time_homogeneous_space_compaction_by_oom_ >
             min_interval_homogeneous_space_compaction_by_oom_) {
           last_time_homogeneous_space_compaction_by_oom_ = current_time;
-          HomogeneousSpaceCompactResult result = PerformHomogeneousSpaceCompact();
+          HomogeneousSpaceCompactResult result =
+              PERFORM_SUSPENDING_OPERATION(PerformHomogeneousSpaceCompact());
           // Thread suspension could have occurred.
           if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
               (!instrumented && EntrypointsInstrumented())) {
@@ -1862,9 +1893,13 @@
       }
     }
   }
+#undef PERFORM_SUSPENDING_OPERATION
   // If the allocation hasn't succeeded by this point, throw an OOM error.
   if (ptr == nullptr) {
+    release_no_suspend();
     ThrowOutOfMemoryError(self, alloc_size, allocator);
+    *old_no_thread_suspend_cause =
+        self->StartAssertNoThreadSuspension("Failed allocation fallback");
   }
   return ptr;
 }
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 9ef6af5..6f6cfd1 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -1011,8 +1011,10 @@
                                          size_t* bytes_allocated,
                                          size_t* usable_size,
                                          size_t* bytes_tl_bulk_allocated,
-                                         ObjPtr<mirror::Class>* klass)
+                                         ObjPtr<mirror::Class>* klass,
+                                         /*out*/const char** old_no_thread_suspend_cause)
       REQUIRES(!Locks::thread_suspend_count_lock_, !*gc_complete_lock_, !*pending_task_lock_)
+      ACQUIRE(Roles::uninterruptible_)
       REQUIRES_SHARED(Locks::mutator_lock_);
 
   // Allocate into a specific space.
diff --git a/runtime/offsets.h b/runtime/offsets.h
index 6d1a8e0..2f36fe6 100644
--- a/runtime/offsets.h
+++ b/runtime/offsets.h
@@ -37,6 +37,9 @@
   constexpr size_t SizeValue() const {
     return val_;
   }
+  constexpr bool operator==(Offset o) const {
+    return SizeValue() == o.SizeValue();
+  }
 
  protected:
   size_t val_;
diff --git a/test/1983-structural-redefinition-failures/expected.txt b/test/1983-structural-redefinition-failures/expected.txt
index 54e1bcc..40a0914 100644
--- a/test/1983-structural-redefinition-failures/expected.txt
+++ b/test/1983-structural-redefinition-failures/expected.txt
@@ -28,7 +28,7 @@
 Is Structurally modifiable class java.util.Objects true
 Is Structurally modifiable class java.util.Arrays true
 Is Structurally modifiable class [Ljava.lang.Object; false
-Is Structurally modifiable class java.lang.Integer false
+Is Structurally modifiable class java.lang.Integer true
 Is Structurally modifiable class java.lang.Number false
 Is Structurally modifiable class art.Test1983$NoVirtuals true
 Is Structurally modifiable class art.Test1983$WithVirtuals false
diff --git a/test/1994-final-virtual-structural/expected.txt b/test/1994-final-virtual-structural/expected.txt
new file mode 100644
index 0000000..9b74d30
--- /dev/null
+++ b/test/1994-final-virtual-structural/expected.txt
@@ -0,0 +1,5 @@
+Hi!
+Hello world!
+Hej Verden!
+Bonjour le monde!
+こんにちは世界!
diff --git a/test/1994-final-virtual-structural/info.txt b/test/1994-final-virtual-structural/info.txt
new file mode 100644
index 0000000..606c984
--- /dev/null
+++ b/test/1994-final-virtual-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition can add new virtual methods and fields.
diff --git a/test/1994-final-virtual-structural/run b/test/1994-final-virtual-structural/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1994-final-virtual-structural/run
@@ -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-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1994-final-virtual-structural/src/Main.java b/test/1994-final-virtual-structural/src/Main.java
new file mode 100644
index 0000000..3f0cb14
--- /dev/null
+++ b/test/1994-final-virtual-structural/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1994.run();
+  }
+}
diff --git a/test/1994-final-virtual-structural/src/art/Redefinition.java b/test/1994-final-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1994-final-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1994-final-virtual-structural/src/art/Test1994.java b/test/1994-final-virtual-structural/src/art/Test1994.java
new file mode 100644
index 0000000..9ae7772
--- /dev/null
+++ b/test/1994-final-virtual-structural/src/art/Test1994.java
@@ -0,0 +1,88 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test1994 {
+
+  public static final class Transform {
+    public void sayHi() {
+      System.out.println("Hi!");
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * public static final class Transform {
+   *   public void sayHi() {
+   *     sayHiEnglish();
+   *     sayHiDanish();
+   *     sayHiFrance();
+   *     sayHiJapan();
+   *   }
+   *   public void sayHiEnglish() {
+   *     System.out.println("Hello world!");
+   *   }
+   *   public void sayHiDanish() {
+   *     System.out.println("Hej Verden!");
+   *   }
+   *   public void sayHiJapan() {
+   *     System.out.println("こんにちは世界!");
+   *   }
+   *   public void sayHiFrance() {
+   *     System.out.println("Bonjour le monde!");
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+    "ZGV4CjAzNQA87tn3VIDgMrF+Md2W4r58elaMPcSfk2CMBQAAcAAAAHhWNBIAAAAAAAAAAMgEAAAc" +
+    "AAAAcAAAAAkAAADgAAAAAgAAAAQBAAABAAAAHAEAAAgAAAAkAQAAAQAAAGQBAAAIBAAAhAEAAG4C" +
+    "AAB2AgAAiQIAAJYCAACkAgAAvgIAAM4CAADyAgAAEgMAACkDAAA9AwAAUQMAAGUDAAB0AwAAfwMA" +
+    "AIIDAACGAwAAkwMAAJkDAACeAwAApwMAAK4DAAC7AwAAyQMAANYDAADiAwAA6QMAAGEEAAAEAAAA" +
+    "BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAAA4AAAAOAAAACAAAAAAAAAAPAAAACAAAAGgCAAAH" +
+    "AAQAEgAAAAAAAAAAAAAAAAAAABQAAAAAAAAAFQAAAAAAAAAWAAAAAAAAABcAAAAAAAAAGAAAAAQA" +
+    "AQATAAAABQAAAAAAAAAAAAAAEQAAAAUAAAAAAAAADAAAALgEAACIBAAAAAAAAAEAAQABAAAASAIA" +
+    "AAQAAABwEAcAAAAOAAEAAQABAAAATAIAAA0AAABuEAMAAABuEAIAAABuEAQAAABuEAUAAAAOAAAA" +
+    "AwABAAIAAABUAgAACAAAAGIAAAAaAQIAbiAGABAADgADAAEAAgAAAFkCAAAIAAAAYgAAABoBAwBu" +
+    "IAYAEAAOAAMAAQACAAAAXgIAAAgAAABiAAAAGgEBAG4gBgAQAA4AAwABAAIAAABjAgAACAAAAGIA" +
+    "AAAaARsAbiAGABAADgADAA4ABQAOPDw8PAAOAA54AAsADngAFAAOeAARAA54AAEAAAAGAAY8aW5p" +
+    "dD4AEUJvbmpvdXIgbGUgbW9uZGUhAAtIZWogVmVyZGVuIQAMSGVsbG8gd29ybGQhABhMYXJ0L1Rl" +
+    "c3QxOTk0JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5OTQ7ACJMZGFsdmlrL2Fubm90YXRpb24vRW5j" +
+    "bG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsAFUxqYXZhL2lvL1By" +
+    "aW50U3RyZWFtOwASTGphdmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZh" +
+    "L2xhbmcvU3lzdGVtOwANVGVzdDE5OTQuamF2YQAJVHJhbnNmb3JtAAFWAAJWTAALYWNjZXNzRmxh" +
+    "Z3MABG5hbWUAA291dAAHcHJpbnRsbgAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gA" +
+    "C3NheUhpRnJhbmNlAApzYXlIaUphcGFuAAV2YWx1ZQB2fn5EOHsiY29tcGlsYXRpb24tbW9kZSI6" +
+    "ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiJjZDkwMDIzOTMwZDk3M2Y1NzcxMWYxZDRmZGFh" +
+    "ZDdhM2U0NzE0NjM3IiwidmVyc2lvbiI6IjEuNy4xNC1kZXYifQAI44GT44KT44Gr44Gh44Gv5LiW" +
+    "55WMIQACAgEZGAECAwIQBBkRFw0AAAEFAIGABIQDAQGcAwEByAMBAegDAQGIBAEBqAQAAAAAAAAC" +
+    "AAAAeQQAAH8EAACsBAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAAcAAAAAIA" +
+    "AAAJAAAA4AAAAAMAAAACAAAABAEAAAQAAAABAAAAHAEAAAUAAAAIAAAAJAEAAAYAAAABAAAAZAEA" +
+    "AAEgAAAGAAAAhAEAAAMgAAAGAAAASAIAAAEQAAABAAAAaAIAAAIgAAAcAAAAbgIAAAQgAAACAAAA" +
+    "eQQAAAAgAAABAAAAiAQAAAMQAAACAAAAqAQAAAYgAAABAAAAuAQAAAAQAAABAAAAyAQAAA==");
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest(new Transform());
+  }
+
+  public static void doTest(Transform t) {
+    t.sayHi();
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    t.sayHi();
+  }
+}
diff --git a/test/1995-final-virtual-structural-multithread/expected.txt b/test/1995-final-virtual-structural-multithread/expected.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/expected.txt
diff --git a/test/1995-final-virtual-structural-multithread/info.txt b/test/1995-final-virtual-structural-multithread/info.txt
new file mode 100644
index 0000000..f9b7bdd
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/info.txt
@@ -0,0 +1,4 @@
+Tests structural redefinition with multiple threads.
+
+Tests that using the structural redefinition while concurrently using the class being redefined
+doesn't cause any unexpected problems.
diff --git a/test/1995-final-virtual-structural-multithread/run b/test/1995-final-virtual-structural-multithread/run
new file mode 100755
index 0000000..421f7b0
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/run
@@ -0,0 +1,21 @@
+#!/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.
+
+# TODO(b/144168550) This test uses access patterns that can be replaced by
+# iget-object-quick during dex2dex compilation. This breaks the test since the
+# -quick opcode encodes the exact byte offset of fields. Since this test changes
+# the offset this causes problems.
+./default-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true -Xcompiler-option --debuggable
diff --git a/test/1995-final-virtual-structural-multithread/src/Main.java b/test/1995-final-virtual-structural-multithread/src/Main.java
new file mode 100644
index 0000000..f19358d
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1995.run();
+  }
+}
diff --git a/test/1995-final-virtual-structural-multithread/src/art/Redefinition.java b/test/1995-final-virtual-structural-multithread/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1995-final-virtual-structural-multithread/src/art/Test1995.java b/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
new file mode 100644
index 0000000..1ffee60
--- /dev/null
+++ b/test/1995-final-virtual-structural-multithread/src/art/Test1995.java
@@ -0,0 +1,168 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.concurrent.CountDownLatch;
+public class Test1995 {
+  private static final int NUM_THREADS = 20;
+
+  public static final class Transform {
+    public String greetingEnglish;
+    public Transform() {
+      this.greetingEnglish = "Hello";
+    }
+    public String sayHi() {
+      return greetingEnglish + " from " + Thread.currentThread().getName();
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * public static final class Transform {
+   *   public String greetingEnglish;
+   *   public String greetingFrench;
+   *   public String greetingDanish;
+   *   public String greetingJapanese;
+   *
+   *   public Transform() {
+   *     this.greetingEnglish = "Hello World";
+   *     this.greetingFrench = "Bonjour le Monde";
+   *     this.greetingDanish = "Hej Verden";
+   *     this.greetingJapanese = "こんにちは世界";
+   *   }
+   *   public String sayHi() {
+   *     return sayHiEnglish() + ", " + sayHiFrench() + ", " + sayHiDanish() + ", " + sayHiJapanese() + " from " + Thread.currentThread().getName();
+   *   }
+   *   public String sayHiEnglish() {
+   *     return greetingEnglish;
+   *   }
+   *   public String sayHiDanish() {
+   *     return greetingDanish;
+   *   }
+   *   public String sayHiJapanese() {
+   *     return greetingJapanese;
+   *   }
+   *   public String sayHiFrench() {
+   *     return greetingFrench;
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQCsHrUqkb8cYgT2oYN7HlVbeOxJT/kONRvgBgAAcAAAAHhWNBIAAAAAAAAAABwGAAAl" +
+"AAAAcAAAAAkAAAAEAQAABAAAACgBAAAEAAAAWAEAAAwAAAB4AQAAAQAAANgBAADoBAAA+AEAAEoD" +
+"AABSAwAAVgMAAF4DAABwAwAAfAMAAIkDAACMAwAAkAMAAKoDAAC6AwAA3gMAAP4DAAASBAAAJgQA" +
+"AEEEAABVBAAAZAQAAG8EAAByBAAAfwQAAIcEAACWBAAAnwQAAK8EAADABAAA0AQAAOIEAADoBAAA" +
+"7wQAAPwEAAAKBQAAFwUAACYFAAAwBQAANwUAAK8FAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAO" +
+"AAAADwAAABIAAAAGAAAABQAAAAAAAAAHAAAABgAAAEQDAAAGAAAABwAAAAAAAAASAAAACAAAAAAA" +
+"AAAAAAUAFwAAAAAABQAYAAAAAAAFABkAAAAAAAUAGgAAAAAAAwACAAAAAAAAABwAAAAAAAAAHQAA" +
+"AAAAAAAeAAAAAAAAAB8AAAAAAAAAIAAAAAQAAwACAAAABgADAAIAAAAGAAEAFAAAAAYAAAAhAAAA" +
+"BwACABUAAAAHAAAAFgAAAAAAAAARAAAABAAAAAAAAAAQAAAADAYAANUFAAAAAAAABwABAAIAAAAt" +
+"AwAAQQAAAG4QAwAGAAwAbhAEAAYADAFuEAIABgAMAm4QBQAGAAwDcQAKAAAADARuEAsABAAMBCIF" +
+"BgBwEAcABQBuIAgABQAaAAEAbiAIAAUAbiAIABUAbiAIAAUAbiAIACUAbiAIAAUAbiAIADUAGgAA" +
+"AG4gCAAFAG4gCABFAG4QCQAFAAwAEQAAAAIAAQAAAAAAMQMAAAMAAABUEAAAEQAAAAIAAQAAAAAA" +
+"NQMAAAMAAABUEAEAEQAAAAIAAQAAAAAAOQMAAAMAAABUEAIAEQAAAAIAAQAAAAAAPQMAAAMAAABU" +
+"EAMAEQAAAAIAAQABAAAAJAMAABQAAABwEAYAAQAaAAUAWxABABoAAwBbEAIAGgAEAFsQAAAaACQA" +
+"WxADAA4ACQAOPEtLS0sAEAAOABYADgATAA4AHAAOABkADgAAAAABAAAABQAGIGZyb20gAAIsIAAG" +
+"PGluaXQ+ABBCb25qb3VyIGxlIE1vbmRlAApIZWogVmVyZGVuAAtIZWxsbyBXb3JsZAABTAACTEwA" +
+"GExhcnQvVGVzdDE5OTUkVHJhbnNmb3JtOwAOTGFydC9UZXN0MTk5NTsAIkxkYWx2aWsvYW5ub3Rh" +
+"dGlvbi9FbmNsb3NpbmdDbGFzczsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwASTGph" +
+"dmEvbGFuZy9PYmplY3Q7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xhbmcvU3RyaW5nQnVp" +
+"bGRlcjsAEkxqYXZhL2xhbmcvVGhyZWFkOwANVGVzdDE5OTUuamF2YQAJVHJhbnNmb3JtAAFWAAth" +
+"Y2Nlc3NGbGFncwAGYXBwZW5kAA1jdXJyZW50VGhyZWFkAAdnZXROYW1lAA5ncmVldGluZ0Rhbmlz" +
+"aAAPZ3JlZXRpbmdFbmdsaXNoAA5ncmVldGluZ0ZyZW5jaAAQZ3JlZXRpbmdKYXBhbmVzZQAEbmFt" +
+"ZQAFc2F5SGkAC3NheUhpRGFuaXNoAAxzYXlIaUVuZ2xpc2gAC3NheUhpRnJlbmNoAA1zYXlIaUph" +
+"cGFuZXNlAAh0b1N0cmluZwAFdmFsdWUAdn5+RDh7ImNvbXBpbGF0aW9uLW1vZGUiOiJkZWJ1ZyIs" +
+"Im1pbi1hcGkiOjEsInNoYS0xIjoiNjBkYTRkNjdiMzgxYzQyNDY3NzU3YzQ5ZmI2ZTU1NzU2ZDg4" +
+"YTJmMyIsInZlcnNpb24iOiIxLjcuMTItZGV2In0AB+OBk+OCk+OBq+OBoeOBr+S4lueVjAACAgEi" +
+"GAECAwITBBkbFxEABAEFAAEBAQEBAQEAgYAE7AUBAfgDAQGMBQEBpAUBAbwFAQHUBQAAAAAAAgAA" +
+"AMYFAADMBQAAAAYAAAAAAAAAAAAAAAAAABAAAAAAAAAAAQAAAAAAAAABAAAAJQAAAHAAAAACAAAA" +
+"CQAAAAQBAAADAAAABAAAACgBAAAEAAAABAAAAFgBAAAFAAAADAAAAHgBAAAGAAAAAQAAANgBAAAB" +
+"IAAABgAAAPgBAAADIAAABgAAACQDAAABEAAAAQAAAEQDAAACIAAAJQAAAEoDAAAEIAAAAgAAAMYF" +
+"AAAAIAAAAQAAANUFAAADEAAAAgAAAPwFAAAGIAAAAQAAAAwGAAAAEAAAAQAAABwGAAA=");
+
+
+  public static void run() throws Exception {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest();
+  }
+
+  public static final class MyThread extends Thread {
+    public MyThread(CountDownLatch delay, int id) {
+      super("Thread: " + id);
+      this.thr_id = id;
+      this.results = new ArrayList<>(1000);
+      this.finish = false;
+      this.delay = delay;
+    }
+
+    public void run() {
+      delay.countDown();
+      while (!finish) {
+        Transform t = new Transform();
+        results.add(t.sayHi());
+      }
+    }
+
+    public void finish() throws Exception {
+      finish = true;
+      this.join();
+    }
+
+    public void Check() throws Exception {
+      for (String s : results) {
+        if (!s.equals("Hello from " + getName()) &&
+            !s.equals("Hello, null, null, null from " + getName()) &&
+            !s.equals("Hello World, Bonjour le Monde, Hej Verden, こんにちは世界 from " + getName())) {
+          System.out.println("FAIL " + thr_id + ": Unexpected result: " + s);
+        }
+      }
+    }
+
+    public ArrayList<String> results;
+    public volatile boolean finish;
+    public int thr_id;
+    public CountDownLatch delay;
+  }
+
+  public static MyThread[] startThreads(int num_threads) throws Exception {
+    CountDownLatch cdl = new CountDownLatch(num_threads);
+    MyThread[] res = new MyThread[num_threads];
+    for (int i = 0; i < num_threads; i++) {
+      res[i] = new MyThread(cdl, i);
+      res[i].start();
+    }
+    cdl.await();
+    return res;
+  }
+  public static void finishThreads(MyThread[] thrs) throws Exception {
+    for (MyThread t : thrs) {
+      t.finish();
+    }
+    for (MyThread t : thrs) {
+      t.Check();
+    }
+  }
+
+  public static void doTest() throws Exception {
+    MyThread[] threads = startThreads(NUM_THREADS);
+    Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+    finishThreads(threads);
+  }
+}
diff --git a/test/1996-final-override-virtual-structural/expected.txt b/test/1996-final-override-virtual-structural/expected.txt
new file mode 100644
index 0000000..20cd98f
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/expected.txt
@@ -0,0 +1,6 @@
+Not doing anything
+super: Hi this: Hi
+Redefining calling class
+super: Hi this: SALUTATIONS
+Not doing anything
+super: Hi and then this: SALUTATIONS
diff --git a/test/1996-final-override-virtual-structural/info.txt b/test/1996-final-override-virtual-structural/info.txt
new file mode 100644
index 0000000..55adf7c
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/info.txt
@@ -0,0 +1,3 @@
+Tests basic functions in the jvmti plugin.
+
+Tests that using the structural redefinition allows one to override a superclass method.
diff --git a/test/1996-final-override-virtual-structural/run b/test/1996-final-override-virtual-structural/run
new file mode 100755
index 0000000..03e41a5
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/run
@@ -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-run "$@" --jvmti --runtime-option -Xopaque-jni-ids:true
diff --git a/test/1996-final-override-virtual-structural/src/Main.java b/test/1996-final-override-virtual-structural/src/Main.java
new file mode 100644
index 0000000..ade69cf
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/src/Main.java
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+public class Main {
+  public static void main(String[] args) throws Exception {
+    art.Test1996.run();
+  }
+}
diff --git a/test/1996-final-override-virtual-structural/src/art/Redefinition.java b/test/1996-final-override-virtual-structural/src/art/Redefinition.java
new file mode 120000
index 0000000..81eaf31
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/src/art/Redefinition.java
@@ -0,0 +1 @@
+../../../jvmti-common/Redefinition.java
\ No newline at end of file
diff --git a/test/1996-final-override-virtual-structural/src/art/Test1996.java b/test/1996-final-override-virtual-structural/src/art/Test1996.java
new file mode 100644
index 0000000..c2b1125
--- /dev/null
+++ b/test/1996-final-override-virtual-structural/src/art/Test1996.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+package art;
+
+import java.util.Base64;
+public class Test1996 {
+
+  public static class SuperTransform {
+    public String hiValue = "Hi";
+    public String sayHi() {
+      return this.hiValue;
+    }
+  }
+  public static final class Transform extends SuperTransform {
+    public void PostTransform() { }
+    public String sayHiTwice(Runnable run) {
+      run.run();
+      return "super: " + super.sayHi() + " this: " + sayHi();
+    }
+  }
+
+  /**
+   * base64 encoded class/dex file for
+   * public static final class Transform extends SuperTransform {
+   *   public String myGreeting;
+   *   public void PostTransform() {
+   *     myGreeting = "SALUTATIONS";
+   *   }
+   *   public String sayHiTwice(Runnable run) {
+   *     run.run();
+   *     return "super: " + super.sayHi() + " and then this: " + sayHi();
+   *   }
+   *   public String sayHi() {
+   *     return myGreeting;
+   *   }
+   * }
+   */
+  private static final byte[] DEX_BYTES = Base64.getDecoder().decode(
+"ZGV4CjAzNQAO4Dwurw97RcUtfH7np7S5RR8gsJYOfmeABQAAcAAAAHhWNBIAAAAAAAAAALwEAAAc" +
+"AAAAcAAAAAkAAADgAAAABAAAAAQBAAABAAAANAEAAAoAAAA8AQAAAQAAAIwBAADUAwAArAEAAHYC" +
+"AACIAgAAkAIAAJMCAACXAgAAtgIAANACAADgAgAABAMAACQDAAA6AwAATgMAAGkDAAB4AwAAhQMA" +
+"AJQDAACfAwAAogMAAK8DAAC3AwAAwwMAAMkDAADOAwAA1QMAAOEDAADqAwAA9AMAAPsDAAAEAAAA" +
+"BQAAAAYAAAAHAAAACAAAAAkAAAAKAAAACwAAABAAAAACAAAABgAAAAAAAAADAAAABgAAAGgCAAAD" +
+"AAAABwAAAHACAAAQAAAACAAAAAAAAAABAAYAEwAAAAAAAwABAAAAAAAAABYAAAABAAMAAQAAAAEA" +
+"AwAMAAAAAQAAABYAAAABAAEAFwAAAAUAAwAVAAAABwADAAEAAAAHAAIAEgAAAAcAAAAZAAAAAQAA" +
+"ABEAAAAAAAAAAAAAAA4AAACsBAAAggQAAAAAAAACAAEAAAAAAFsCAAADAAAAVBAAABEAAAAFAAIA" +
+"AgAAAF8CAAAlAAAAchAGAAQAbxABAAMADARuEAQAAwAMACIBBwBwEAcAAQAaAhgAbiAIACEAbiAI" +
+"AEEAGgQAAG4gCABBAG4gCAABAG4QCQABAAwEEQQAAAEAAQABAAAAUgIAAAQAAABwEAAAAAAOAAIA" +
+"AQAAAAAAVgIAAAUAAAAaAA0AWxAAAA4ACgAOAA0ADksAFAAOABABAA48AAAAAAEAAAAFAAAAAQAA" +
+"AAYAECBhbmQgdGhlbiB0aGlzOiAABjxpbml0PgABTAACTEwAHUxhcnQvVGVzdDE5OTYkU3VwZXJU" +
+"cmFuc2Zvcm07ABhMYXJ0L1Rlc3QxOTk2JFRyYW5zZm9ybTsADkxhcnQvVGVzdDE5OTY7ACJMZGFs" +
+"dmlrL2Fubm90YXRpb24vRW5jbG9zaW5nQ2xhc3M7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJD" +
+"bGFzczsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAGUxqYXZhL2xh" +
+"bmcvU3RyaW5nQnVpbGRlcjsADVBvc3RUcmFuc2Zvcm0AC1NBTFVUQVRJT05TAA1UZXN0MTk5Ni5q" +
+"YXZhAAlUcmFuc2Zvcm0AAVYAC2FjY2Vzc0ZsYWdzAAZhcHBlbmQACm15R3JlZXRpbmcABG5hbWUA" +
+"A3J1bgAFc2F5SGkACnNheUhpVHdpY2UAB3N1cGVyOiAACHRvU3RyaW5nAAV2YWx1ZQB2fn5EOHsi" +
+"Y29tcGlsYXRpb24tbW9kZSI6ImRlYnVnIiwibWluLWFwaSI6MSwic2hhLTEiOiI2MGRhNGQ2N2Iz" +
+"ODFjNDI0Njc3NTdjNDlmYjZlNTU3NTZkODhhMmYzIiwidmVyc2lvbiI6IjEuNy4xMi1kZXYifQAC" +
+"AwEaGAICBAIRBBkUFw8AAQEDAAECgYAEoAQDAbgEAQGsAwEBxAMAAAAAAAACAAAAcwQAAHkEAACg" +
+"BAAAAAAAAAAAAAAAAAAAEAAAAAAAAAABAAAAAAAAAAEAAAAcAAAAcAAAAAIAAAAJAAAA4AAAAAMA" +
+"AAAEAAAABAEAAAQAAAABAAAANAEAAAUAAAAKAAAAPAEAAAYAAAABAAAAjAEAAAEgAAAEAAAArAEA" +
+"AAMgAAAEAAAAUgIAAAEQAAACAAAAaAIAAAIgAAAcAAAAdgIAAAQgAAACAAAAcwQAAAAgAAABAAAA" +
+"ggQAAAMQAAACAAAAnAQAAAYgAAABAAAArAQAAAAQAAABAAAAvAQAAA==");
+
+  public static void run() {
+    Redefinition.setTestConfiguration(Redefinition.Config.COMMON_REDEFINE);
+    doTest(new Transform());
+  }
+
+  public static void doTest(final Transform t) {
+    System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
+    System.out.println(t.sayHiTwice(
+      () -> {
+        System.out.println("Redefining calling class");
+        Redefinition.doCommonStructuralClassRedefinition(Transform.class, DEX_BYTES);
+        t.PostTransform();
+      }));
+    System.out.println(t.sayHiTwice(() -> { System.out.println("Not doing anything"); }));
+  }
+}
diff --git a/test/knownfailures.json b/test/knownfailures.json
index cd66472..069cecb 100644
--- a/test/knownfailures.json
+++ b/test/knownfailures.json
@@ -1146,6 +1146,9 @@
                   "1991-hello-structural-retransform",
                   "1992-retransform-no-such-field",
                   "1993-fallback-non-structural",
+                  "1994-final-virtual-structural",
+                  "1995-final-virtual-structural-multithread",
+                  "1996-final-override-virtual-structural",
                   "1997-structural-shadow-method",
                   "1998-structural-shadow-field"
                 ],