ART: Move openjdkjvmti to art/

Move libopenjdkjvmti out of the runtime directory. Let's not
pollute the runtime library.

Test: m test-art-host
Change-Id: Idb6b9cebcd61777bd3200437a2ae584a63a4a341
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
new file mode 100644
index 0000000..b6b1b56
--- /dev/null
+++ b/openjdkjvmti/Android.bp
@@ -0,0 +1,77 @@
+//
+// 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.
+
+cc_library_headers {
+    name: "libopenjdkjvmti_headers",
+    host_supported: true,
+    export_include_dirs: ["include"],
+}
+
+cc_defaults {
+    name: "libopenjdkjvmti_defaults",
+    defaults: ["art_defaults"],
+    host_supported: true,
+    srcs: ["events.cc",
+           "fixed_up_dex_file.cc",
+           "object_tagging.cc",
+           "OpenjdkJvmTi.cc",
+           "ti_allocator.cc",
+           "ti_breakpoint.cc",
+           "ti_class.cc",
+           "ti_class_definition.cc",
+           "ti_class_loader.cc",
+           "ti_dump.cc",
+           "ti_field.cc",
+           "ti_heap.cc",
+           "ti_jni.cc",
+           "ti_method.cc",
+           "ti_monitor.cc",
+           "ti_object.cc",
+           "ti_phase.cc",
+           "ti_properties.cc",
+           "ti_search.cc",
+           "ti_stack.cc",
+           "ti_redefine.cc",
+           "ti_thread.cc",
+           "ti_threadgroup.cc",
+           "ti_timers.cc",
+           "transform.cc"],
+    header_libs: ["libopenjdkjvmti_headers"],
+    shared_libs: [
+        "libbase",
+        "libnativehelper",
+    ],
+}
+
+art_cc_library {
+    name: "libopenjdkjvmti",
+    defaults: ["libopenjdkjvmti_defaults"],
+    shared_libs: [
+        "libart",
+        "libart-compiler",
+    ],
+}
+
+art_cc_library {
+    name: "libopenjdkjvmtid",
+    defaults: [
+        "art_debug_defaults",
+        "libopenjdkjvmti_defaults",
+    ],
+    shared_libs: [
+        "libartd",
+        "libartd-compiler",
+    ],
+}
diff --git a/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION b/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/openjdkjvmti/MODULE_LICENSE_GPL_WITH_CLASSPATH_EXCEPTION
diff --git a/openjdkjvmti/NOTICE b/openjdkjvmti/NOTICE
new file mode 100644
index 0000000..6ec62cd
--- /dev/null
+++ b/openjdkjvmti/NOTICE
@@ -0,0 +1,29 @@
+Copyright (C) 2016 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) 1997, 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.
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
new file mode 100644
index 0000000..af77072
--- /dev/null
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -0,0 +1,1903 @@
+/* Copyright (C) 2016 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 <string>
+#include <type_traits>
+#include <vector>
+
+#include <jni.h>
+
+#include "jvmti.h"
+
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "jni_env_ext-inl.h"
+#include "obj_ptr-inl.h"
+#include "object_tagging.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_allocator.h"
+#include "ti_breakpoint.h"
+#include "ti_class.h"
+#include "ti_dump.h"
+#include "ti_field.h"
+#include "ti_heap.h"
+#include "ti_jni.h"
+#include "ti_method.h"
+#include "ti_monitor.h"
+#include "ti_object.h"
+#include "ti_phase.h"
+#include "ti_properties.h"
+#include "ti_redefine.h"
+#include "ti_search.h"
+#include "ti_stack.h"
+#include "ti_thread.h"
+#include "ti_threadgroup.h"
+#include "ti_timers.h"
+#include "transform.h"
+
+namespace openjdkjvmti {
+
+EventHandler gEventHandler;
+
+#define ENSURE_NON_NULL(n)      \
+  do {                          \
+    if ((n) == nullptr) {       \
+      return ERR(NULL_POINTER); \
+    }                           \
+  } while (false)
+
+class JvmtiFunctions {
+ private:
+  static jvmtiError getEnvironmentError(jvmtiEnv* env) {
+    if (env == nullptr) {
+      return ERR(INVALID_ENVIRONMENT);
+    } else if (art::Thread::Current() == nullptr) {
+      return ERR(UNATTACHED_THREAD);
+    } else {
+      return OK;
+    }
+  }
+
+#define ENSURE_VALID_ENV(env)                                            \
+  do {                                                                   \
+    jvmtiError ensure_valid_env_ ## __LINE__ = getEnvironmentError(env); \
+    if (ensure_valid_env_ ## __LINE__ != OK) {                           \
+      return ensure_valid_env_ ## __LINE__ ;                             \
+    }                                                                    \
+  } while (false)
+
+#define ENSURE_HAS_CAP(env, cap) \
+  do { \
+    if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.cap != 1) { \
+      return ERR(MUST_POSSESS_CAPABILITY); \
+    } \
+  } while (false)
+
+ public:
+  static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_NON_NULL(mem_ptr);
+    return AllocUtil::Allocate(env, size, mem_ptr);
+  }
+
+  static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem) {
+    ENSURE_VALID_ENV(env);
+    return AllocUtil::Deallocate(env, mem);
+  }
+
+  static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::GetThreadState(env, thread, thread_state_ptr);
+  }
+
+  static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::GetCurrentThread(env, thread_ptr);
+  }
+
+  static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::GetAllThreads(env, threads_count_ptr, threads_ptr);
+  }
+
+  static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_suspend);
+    return ThreadUtil::SuspendThread(env, thread);
+  }
+
+  static jvmtiError SuspendThreadList(jvmtiEnv* env,
+                                      jint request_count,
+                                      const jthread* request_list,
+                                      jvmtiError* results) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_suspend);
+    return ThreadUtil::SuspendThreadList(env, request_count, request_list, results);
+  }
+
+  static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_suspend);
+    return ThreadUtil::ResumeThread(env, thread);
+  }
+
+  static jvmtiError ResumeThreadList(jvmtiEnv* env,
+                                     jint request_count,
+                                     const jthread* request_list,
+                                     jvmtiError* results) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_suspend);
+    return ThreadUtil::ResumeThreadList(env, request_count, request_list, results);
+  }
+
+  static jvmtiError StopThread(jvmtiEnv* env,
+                               jthread thread ATTRIBUTE_UNUSED,
+                               jobject exception ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_signal_thread);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError InterruptThread(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_signal_thread);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::GetThreadInfo(env, thread, info_ptr);
+  }
+
+  static jvmtiError GetOwnedMonitorInfo(jvmtiEnv* env,
+                                        jthread thread ATTRIBUTE_UNUSED,
+                                        jint* owned_monitor_count_ptr ATTRIBUTE_UNUSED,
+                                        jobject** owned_monitors_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_owned_monitor_info);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetOwnedMonitorStackDepthInfo(
+      jvmtiEnv* env,
+      jthread thread ATTRIBUTE_UNUSED,
+      jint* monitor_info_count_ptr ATTRIBUTE_UNUSED,
+      jvmtiMonitorStackDepthInfo** monitor_info_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_owned_monitor_stack_depth_info);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetCurrentContendedMonitor(jvmtiEnv* env,
+                                               jthread thread ATTRIBUTE_UNUSED,
+                                               jobject* monitor_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_current_contended_monitor);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError RunAgentThread(jvmtiEnv* env,
+                                   jthread thread,
+                                   jvmtiStartFunction proc,
+                                   const void* arg,
+                                   jint priority) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::RunAgentThread(env, thread, proc, arg, priority);
+  }
+
+  static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::SetThreadLocalStorage(env, thread, data);
+  }
+
+  static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadUtil::GetThreadLocalStorage(env, thread, data_ptr);
+  }
+
+  static jvmtiError GetTopThreadGroups(jvmtiEnv* env,
+                                       jint* group_count_ptr,
+                                       jthreadGroup** groups_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadGroupUtil::GetTopThreadGroups(env, group_count_ptr, groups_ptr);
+  }
+
+  static jvmtiError GetThreadGroupInfo(jvmtiEnv* env,
+                                       jthreadGroup group,
+                                       jvmtiThreadGroupInfo* info_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadGroupUtil::GetThreadGroupInfo(env, group, info_ptr);
+  }
+
+  static jvmtiError GetThreadGroupChildren(jvmtiEnv* env,
+                                           jthreadGroup group,
+                                           jint* thread_count_ptr,
+                                           jthread** threads_ptr,
+                                           jint* group_count_ptr,
+                                           jthreadGroup** groups_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ThreadGroupUtil::GetThreadGroupChildren(env,
+                                                   group,
+                                                   thread_count_ptr,
+                                                   threads_ptr,
+                                                   group_count_ptr,
+                                                   groups_ptr);
+  }
+
+  static jvmtiError GetStackTrace(jvmtiEnv* env,
+                                  jthread thread,
+                                  jint start_depth,
+                                  jint max_frame_count,
+                                  jvmtiFrameInfo* frame_buffer,
+                                  jint* count_ptr) {
+    ENSURE_VALID_ENV(env);
+    return StackUtil::GetStackTrace(env,
+                                    thread,
+                                    start_depth,
+                                    max_frame_count,
+                                    frame_buffer,
+                                    count_ptr);
+  }
+
+  static jvmtiError GetAllStackTraces(jvmtiEnv* env,
+                                      jint max_frame_count,
+                                      jvmtiStackInfo** stack_info_ptr,
+                                      jint* thread_count_ptr) {
+    ENSURE_VALID_ENV(env);
+    return StackUtil::GetAllStackTraces(env, max_frame_count, stack_info_ptr, thread_count_ptr);
+  }
+
+  static jvmtiError GetThreadListStackTraces(jvmtiEnv* env,
+                                             jint thread_count,
+                                             const jthread* thread_list,
+                                             jint max_frame_count,
+                                             jvmtiStackInfo** stack_info_ptr) {
+    ENSURE_VALID_ENV(env);
+    return StackUtil::GetThreadListStackTraces(env,
+                                               thread_count,
+                                               thread_list,
+                                               max_frame_count,
+                                               stack_info_ptr);
+  }
+
+  static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr) {
+    ENSURE_VALID_ENV(env);
+    return StackUtil::GetFrameCount(env, thread, count_ptr);
+  }
+
+  static jvmtiError PopFrame(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_pop_frame);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetFrameLocation(jvmtiEnv* env,
+                                     jthread thread,
+                                     jint depth,
+                                     jmethodID* method_ptr,
+                                     jlocation* location_ptr) {
+    ENSURE_VALID_ENV(env);
+    return StackUtil::GetFrameLocation(env, thread, depth, method_ptr, location_ptr);
+  }
+
+  static jvmtiError NotifyFramePop(jvmtiEnv* env,
+                                   jthread thread ATTRIBUTE_UNUSED,
+                                   jint depth ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_frame_pop_events);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError ForceEarlyReturnObject(jvmtiEnv* env,
+                                           jthread thread ATTRIBUTE_UNUSED,
+                                           jobject value ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_force_early_return);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError ForceEarlyReturnInt(jvmtiEnv* env,
+                                        jthread thread ATTRIBUTE_UNUSED,
+                                        jint value ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_force_early_return);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError ForceEarlyReturnLong(jvmtiEnv* env,
+                                         jthread thread ATTRIBUTE_UNUSED,
+                                         jlong value ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_force_early_return);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError ForceEarlyReturnFloat(jvmtiEnv* env,
+                                          jthread thread ATTRIBUTE_UNUSED,
+                                          jfloat value ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_force_early_return);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError ForceEarlyReturnDouble(jvmtiEnv* env,
+                                           jthread thread ATTRIBUTE_UNUSED,
+                                           jdouble value ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_force_early_return);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError ForceEarlyReturnVoid(jvmtiEnv* env, jthread thread ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_force_early_return);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError FollowReferences(jvmtiEnv* env,
+                                     jint heap_filter,
+                                     jclass klass,
+                                     jobject initial_object,
+                                     const jvmtiHeapCallbacks* callbacks,
+                                     const void* user_data) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+    HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+    return heap_util.FollowReferences(env,
+                                      heap_filter,
+                                      klass,
+                                      initial_object,
+                                      callbacks,
+                                      user_data);
+  }
+
+  static jvmtiError IterateThroughHeap(jvmtiEnv* env,
+                                       jint heap_filter,
+                                       jclass klass,
+                                       const jvmtiHeapCallbacks* callbacks,
+                                       const void* user_data) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+    HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+    return heap_util.IterateThroughHeap(env, heap_filter, klass, callbacks, user_data);
+  }
+
+  static jvmtiError GetTag(jvmtiEnv* env, jobject object, jlong* tag_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+
+    JNIEnv* jni_env = GetJniEnv(env);
+    if (jni_env == nullptr) {
+      return ERR(INTERNAL);
+    }
+
+    art::ScopedObjectAccess soa(jni_env);
+    art::ObjPtr<art::mirror::Object> obj = soa.Decode<art::mirror::Object>(object);
+    if (!ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table->GetTag(obj.Ptr(), tag_ptr)) {
+      *tag_ptr = 0;
+    }
+
+    return ERR(NONE);
+  }
+
+  static jvmtiError SetTag(jvmtiEnv* env, jobject object, jlong tag) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+
+    if (object == nullptr) {
+      return ERR(NULL_POINTER);
+    }
+
+    JNIEnv* jni_env = GetJniEnv(env);
+    if (jni_env == nullptr) {
+      return ERR(INTERNAL);
+    }
+
+    art::ScopedObjectAccess soa(jni_env);
+    art::ObjPtr<art::mirror::Object> obj = soa.Decode<art::mirror::Object>(object);
+    ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table->Set(obj.Ptr(), tag);
+
+    return ERR(NONE);
+  }
+
+  static jvmtiError GetObjectsWithTags(jvmtiEnv* env,
+                                       jint tag_count,
+                                       const jlong* tags,
+                                       jint* count_ptr,
+                                       jobject** object_result_ptr,
+                                       jlong** tag_result_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+
+    JNIEnv* jni_env = GetJniEnv(env);
+    if (jni_env == nullptr) {
+      return ERR(INTERNAL);
+    }
+
+    art::ScopedObjectAccess soa(jni_env);
+    return ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table->GetTaggedObjects(env,
+                                                                               tag_count,
+                                                                               tags,
+                                                                               count_ptr,
+                                                                               object_result_ptr,
+                                                                               tag_result_ptr);
+  }
+
+  static jvmtiError ForceGarbageCollection(jvmtiEnv* env) {
+    ENSURE_VALID_ENV(env);
+    return HeapUtil::ForceGarbageCollection(env);
+  }
+
+  static jvmtiError IterateOverObjectsReachableFromObject(
+      jvmtiEnv* env,
+      jobject object ATTRIBUTE_UNUSED,
+      jvmtiObjectReferenceCallback object_reference_callback ATTRIBUTE_UNUSED,
+      const void* user_data ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError IterateOverReachableObjects(
+      jvmtiEnv* env,
+      jvmtiHeapRootCallback heap_root_callback ATTRIBUTE_UNUSED,
+      jvmtiStackReferenceCallback stack_ref_callback ATTRIBUTE_UNUSED,
+      jvmtiObjectReferenceCallback object_ref_callback ATTRIBUTE_UNUSED,
+      const void* user_data ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError IterateOverHeap(jvmtiEnv* env,
+                                    jvmtiHeapObjectFilter object_filter ATTRIBUTE_UNUSED,
+                                    jvmtiHeapObjectCallback heap_object_callback ATTRIBUTE_UNUSED,
+                                    const void* user_data ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError IterateOverInstancesOfClass(
+      jvmtiEnv* env,
+      jclass klass ATTRIBUTE_UNUSED,
+      jvmtiHeapObjectFilter object_filter ATTRIBUTE_UNUSED,
+      jvmtiHeapObjectCallback heap_object_callback ATTRIBUTE_UNUSED,
+      const void* user_data ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_tag_objects);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetLocalObject(jvmtiEnv* env,
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jobject* value_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+  }
+
+  static jvmtiError GetLocalInstance(jvmtiEnv* env,
+                                     jthread thread,
+                                     jint depth,
+                                     jobject* value_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalInstance(env, thread, depth, value_ptr);
+  }
+
+  static jvmtiError GetLocalInt(jvmtiEnv* env,
+                                jthread thread,
+                                jint depth,
+                                jint slot,
+                                jint* value_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+  }
+
+  static jvmtiError GetLocalLong(jvmtiEnv* env,
+                                 jthread thread,
+                                 jint depth,
+                                 jint slot,
+                                 jlong* value_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+  }
+
+  static jvmtiError GetLocalFloat(jvmtiEnv* env,
+                                  jthread thread,
+                                  jint depth,
+                                  jint slot,
+                                  jfloat* value_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+  }
+
+  static jvmtiError GetLocalDouble(jvmtiEnv* env,
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jdouble* value_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalVariable(env, thread, depth, slot, value_ptr);
+  }
+
+  static jvmtiError SetLocalObject(jvmtiEnv* env,
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jobject value) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+  }
+
+  static jvmtiError SetLocalInt(jvmtiEnv* env,
+                                jthread thread,
+                                jint depth,
+                                jint slot,
+                                jint value) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+  }
+
+  static jvmtiError SetLocalLong(jvmtiEnv* env,
+                                 jthread thread,
+                                 jint depth,
+                                 jint slot,
+                                 jlong value) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+  }
+
+  static jvmtiError SetLocalFloat(jvmtiEnv* env,
+                                  jthread thread,
+                                  jint depth,
+                                  jint slot,
+                                  jfloat value) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+  }
+
+  static jvmtiError SetLocalDouble(jvmtiEnv* env,
+                                   jthread thread,
+                                   jint depth,
+                                   jint slot,
+                                   jdouble value) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::SetLocalVariable(env, thread, depth, slot, value);
+  }
+
+
+  static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_breakpoint_events);
+    return BreakpointUtil::SetBreakpoint(env, method, location);
+  }
+
+  static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_breakpoint_events);
+    return BreakpointUtil::ClearBreakpoint(env, method, location);
+  }
+
+  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_field_access_events);
+    return FieldUtil::SetFieldAccessWatch(env, klass, field);
+  }
+
+  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_field_access_events);
+    return FieldUtil::ClearFieldAccessWatch(env, klass, field);
+  }
+
+  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_field_modification_events);
+    return FieldUtil::SetFieldModificationWatch(env, klass, field);
+  }
+
+  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_generate_field_modification_events);
+    return FieldUtil::ClearFieldModificationWatch(env, klass, field);
+  }
+
+  static jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr) {
+    ENSURE_VALID_ENV(env);
+    HeapUtil heap_util(ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+    return heap_util.GetLoadedClasses(env, class_count_ptr, classes_ptr);
+  }
+
+  static jvmtiError GetClassLoaderClasses(jvmtiEnv* env,
+                                          jobject initiating_loader,
+                                          jint* class_count_ptr,
+                                          jclass** classes_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassLoaderClasses(env, initiating_loader, class_count_ptr, classes_ptr);
+  }
+
+  static jvmtiError GetClassSignature(jvmtiEnv* env,
+                                      jclass klass,
+                                      char** signature_ptr,
+                                      char** generic_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassSignature(env, klass, signature_ptr, generic_ptr);
+  }
+
+  static jvmtiError GetClassStatus(jvmtiEnv* env, jclass klass, jint* status_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassStatus(env, klass, status_ptr);
+  }
+
+  static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_source_file_name);
+    return ClassUtil::GetSourceFileName(env, klass, source_name_ptr);
+  }
+
+  static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassModifiers(env, klass, modifiers_ptr);
+  }
+
+  static jvmtiError GetClassMethods(jvmtiEnv* env,
+                                    jclass klass,
+                                    jint* method_count_ptr,
+                                    jmethodID** methods_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassMethods(env, klass, method_count_ptr, methods_ptr);
+  }
+
+  static jvmtiError GetClassFields(jvmtiEnv* env,
+                                   jclass klass,
+                                   jint* field_count_ptr,
+                                   jfieldID** fields_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassFields(env, klass, field_count_ptr, fields_ptr);
+  }
+
+  static jvmtiError GetImplementedInterfaces(jvmtiEnv* env,
+                                             jclass klass,
+                                             jint* interface_count_ptr,
+                                             jclass** interfaces_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetImplementedInterfaces(env, klass, interface_count_ptr, interfaces_ptr);
+  }
+
+  static jvmtiError GetClassVersionNumbers(jvmtiEnv* env,
+                                           jclass klass,
+                                           jint* minor_version_ptr,
+                                           jint* major_version_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassVersionNumbers(env, klass, minor_version_ptr, major_version_ptr);
+  }
+
+  static jvmtiError GetConstantPool(jvmtiEnv* env,
+                                    jclass klass ATTRIBUTE_UNUSED,
+                                    jint* constant_pool_count_ptr ATTRIBUTE_UNUSED,
+                                    jint* constant_pool_byte_count_ptr ATTRIBUTE_UNUSED,
+                                    unsigned char** constant_pool_bytes_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_constant_pool);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::IsInterface(env, klass, is_interface_ptr);
+  }
+
+  static jvmtiError IsArrayClass(jvmtiEnv* env,
+                                 jclass klass,
+                                 jboolean* is_array_class_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::IsArrayClass(env, klass, is_array_class_ptr);
+  }
+
+  static jvmtiError IsModifiableClass(jvmtiEnv* env,
+                                      jclass klass,
+                                      jboolean* is_modifiable_class_ptr) {
+    ENSURE_VALID_ENV(env);
+    return Redefiner::IsModifiableClass(env, klass, is_modifiable_class_ptr);
+  }
+
+  static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ClassUtil::GetClassLoader(env, klass, classloader_ptr);
+  }
+
+  static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
+                                            jclass klass,
+                                            char** source_debug_extension_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_source_debug_extension);
+    return ClassUtil::GetSourceDebugExtension(env, klass, source_debug_extension_ptr);
+  }
+
+  static jvmtiError RetransformClasses(jvmtiEnv* env, jint class_count, const jclass* classes) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_retransform_classes);
+    std::string error_msg;
+    jvmtiError res = Transformer::RetransformClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                                                     &gEventHandler,
+                                                     art::Runtime::Current(),
+                                                     art::Thread::Current(),
+                                                     class_count,
+                                                     classes,
+                                                     &error_msg);
+    if (res != OK) {
+      LOG(WARNING) << "FAILURE TO RETRANFORM " << error_msg;
+    }
+    return res;
+  }
+
+  static jvmtiError RedefineClasses(jvmtiEnv* env,
+                                    jint class_count,
+                                    const jvmtiClassDefinition* class_definitions) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_redefine_classes);
+    std::string error_msg;
+    jvmtiError res = Redefiner::RedefineClasses(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                                                &gEventHandler,
+                                                art::Runtime::Current(),
+                                                art::Thread::Current(),
+                                                class_count,
+                                                class_definitions,
+                                                &error_msg);
+    if (res != OK) {
+      LOG(WARNING) << "FAILURE TO REDEFINE " << error_msg;
+    }
+    return res;
+  }
+
+  static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ObjectUtil::GetObjectSize(env, object, size_ptr);
+  }
+
+  static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr) {
+    ENSURE_VALID_ENV(env);
+    return ObjectUtil::GetObjectHashCode(env, object, hash_code_ptr);
+  }
+
+  static jvmtiError GetObjectMonitorUsage(jvmtiEnv* env,
+                                          jobject object ATTRIBUTE_UNUSED,
+                                          jvmtiMonitorUsage* info_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_monitor_info);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetFieldName(jvmtiEnv* env,
+                                 jclass klass,
+                                 jfieldID field,
+                                 char** name_ptr,
+                                 char** signature_ptr,
+                                 char** generic_ptr) {
+    ENSURE_VALID_ENV(env);
+    return FieldUtil::GetFieldName(env, klass, field, name_ptr, signature_ptr, generic_ptr);
+  }
+
+  static jvmtiError GetFieldDeclaringClass(jvmtiEnv* env,
+                                           jclass klass,
+                                           jfieldID field,
+                                           jclass* declaring_class_ptr) {
+    ENSURE_VALID_ENV(env);
+    return FieldUtil::GetFieldDeclaringClass(env, klass, field, declaring_class_ptr);
+  }
+
+  static jvmtiError GetFieldModifiers(jvmtiEnv* env,
+                                      jclass klass,
+                                      jfieldID field,
+                                      jint* modifiers_ptr) {
+    ENSURE_VALID_ENV(env);
+    return FieldUtil::GetFieldModifiers(env, klass, field, modifiers_ptr);
+  }
+
+  static jvmtiError IsFieldSynthetic(jvmtiEnv* env,
+                                     jclass klass,
+                                     jfieldID field,
+                                     jboolean* is_synthetic_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_synthetic_attribute);
+    return FieldUtil::IsFieldSynthetic(env, klass, field, is_synthetic_ptr);
+  }
+
+  static jvmtiError GetMethodName(jvmtiEnv* env,
+                                  jmethodID method,
+                                  char** name_ptr,
+                                  char** signature_ptr,
+                                  char** generic_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::GetMethodName(env, method, name_ptr, signature_ptr, generic_ptr);
+  }
+
+  static jvmtiError GetMethodDeclaringClass(jvmtiEnv* env,
+                                            jmethodID method,
+                                            jclass* declaring_class_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::GetMethodDeclaringClass(env, method, declaring_class_ptr);
+  }
+
+  static jvmtiError GetMethodModifiers(jvmtiEnv* env,
+                                       jmethodID method,
+                                       jint* modifiers_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::GetMethodModifiers(env, method, modifiers_ptr);
+  }
+
+  static jvmtiError GetMaxLocals(jvmtiEnv* env,
+                                 jmethodID method,
+                                 jint* max_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::GetMaxLocals(env, method, max_ptr);
+  }
+
+  static jvmtiError GetArgumentsSize(jvmtiEnv* env,
+                                     jmethodID method,
+                                     jint* size_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::GetArgumentsSize(env, method, size_ptr);
+  }
+
+  static jvmtiError GetLineNumberTable(jvmtiEnv* env,
+                                       jmethodID method,
+                                       jint* entry_count_ptr,
+                                       jvmtiLineNumberEntry** table_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_line_numbers);
+    return MethodUtil::GetLineNumberTable(env, method, entry_count_ptr, table_ptr);
+  }
+
+  static jvmtiError GetMethodLocation(jvmtiEnv* env,
+                                      jmethodID method,
+                                      jlocation* start_location_ptr,
+                                      jlocation* end_location_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::GetMethodLocation(env, method, start_location_ptr, end_location_ptr);
+  }
+
+  static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLocalVariableEntry** table_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_access_local_variables);
+    return MethodUtil::GetLocalVariableTable(env, method, entry_count_ptr, table_ptr);
+  }
+
+  static jvmtiError GetBytecodes(jvmtiEnv* env,
+                                 jmethodID method,
+                                 jint* bytecode_count_ptr,
+                                 unsigned char** bytecodes_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_bytecodes);
+    return MethodUtil::GetBytecodes(env, method, bytecode_count_ptr, bytecodes_ptr);
+  }
+
+  static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::IsMethodNative(env, method, is_native_ptr);
+  }
+
+  static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_synthetic_attribute);
+    return MethodUtil::IsMethodSynthetic(env, method, is_synthetic_ptr);
+  }
+
+  static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MethodUtil::IsMethodObsolete(env, method, is_obsolete_ptr);
+  }
+
+  static jvmtiError SetNativeMethodPrefix(jvmtiEnv* env, const char* prefix ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_set_native_method_prefix);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError SetNativeMethodPrefixes(jvmtiEnv* env,
+                                            jint prefix_count ATTRIBUTE_UNUSED,
+                                            char** prefixes ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_set_native_method_prefix);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::CreateRawMonitor(env, name, monitor_ptr);
+  }
+
+  static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::DestroyRawMonitor(env, monitor);
+  }
+
+  static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::RawMonitorEnter(env, monitor);
+  }
+
+  static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::RawMonitorExit(env, monitor);
+  }
+
+  static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::RawMonitorWait(env, monitor, millis);
+  }
+
+  static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::RawMonitorNotify(env, monitor);
+  }
+
+  static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor) {
+    ENSURE_VALID_ENV(env);
+    return MonitorUtil::RawMonitorNotifyAll(env, monitor);
+  }
+
+  static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table) {
+    ENSURE_VALID_ENV(env);
+    return JNIUtil::SetJNIFunctionTable(env, function_table);
+  }
+
+  static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) {
+    ENSURE_VALID_ENV(env);
+    return JNIUtil::GetJNIFunctionTable(env, function_table);
+  }
+
+  // TODO: This will require locking, so that an agent can't remove callbacks when we're dispatching
+  //       an event.
+  static jvmtiError SetEventCallbacks(jvmtiEnv* env,
+                                      const jvmtiEventCallbacks* callbacks,
+                                      jint size_of_callbacks) {
+    ENSURE_VALID_ENV(env);
+    if (size_of_callbacks < 0) {
+      return ERR(ILLEGAL_ARGUMENT);
+    }
+
+    if (callbacks == nullptr) {
+      ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks.reset();
+      return ERR(NONE);
+    }
+
+    std::unique_ptr<jvmtiEventCallbacks> tmp(new jvmtiEventCallbacks());
+    memset(tmp.get(), 0, sizeof(jvmtiEventCallbacks));
+    size_t copy_size = std::min(sizeof(jvmtiEventCallbacks),
+                                static_cast<size_t>(size_of_callbacks));
+    copy_size = art::RoundDown(copy_size, sizeof(void*));
+    memcpy(tmp.get(), callbacks, copy_size);
+
+    ArtJvmTiEnv::AsArtJvmTiEnv(env)->event_callbacks = std::move(tmp);
+
+    return ERR(NONE);
+  }
+
+  static jvmtiError SetEventNotificationMode(jvmtiEnv* env,
+                                             jvmtiEventMode mode,
+                                             jvmtiEvent event_type,
+                                             jthread event_thread,
+                                             ...) {
+    ENSURE_VALID_ENV(env);
+    art::Thread* art_thread = nullptr;
+    if (event_thread != nullptr) {
+      // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD.
+      art::ScopedObjectAccess soa(art::Thread::Current());
+      art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+      art_thread = art::Thread::FromManagedThread(soa, event_thread);
+
+      if (art_thread == nullptr ||  // The thread hasn't been started or is already dead.
+          art_thread->IsStillStarting()) {
+        // TODO: We may want to let the EventHandler know, so it could clean up masks, potentially.
+        return ERR(THREAD_NOT_ALIVE);
+      }
+    }
+
+    ArtJvmTiEnv* art_env = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+    return gEventHandler.SetEvent(art_env, art_thread, GetArtJvmtiEvent(art_env, event_type), mode);
+  }
+
+  static jvmtiError GenerateEvents(jvmtiEnv* env,
+                                   jvmtiEvent event_type ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    return OK;
+  }
+
+  static jvmtiError GetExtensionFunctions(jvmtiEnv* env,
+                                          jint* extension_count_ptr,
+                                          jvmtiExtensionFunctionInfo** extensions) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_NON_NULL(extension_count_ptr);
+    ENSURE_NON_NULL(extensions);
+
+    std::vector<jvmtiExtensionFunctionInfo> ext_vector;
+
+    // Holders for allocated values.
+    std::vector<JvmtiUniquePtr<char[]>> char_buffers;
+    std::vector<JvmtiUniquePtr<jvmtiParamInfo[]>> param_buffers;
+    std::vector<JvmtiUniquePtr<jvmtiError[]>> error_buffers;
+
+    // Add a helper struct that takes an arbitrary const char*. add_extension will use Allocate
+    // appropriately.
+    struct CParamInfo {
+      const char* name;
+      jvmtiParamKind kind;
+      jvmtiParamTypes base_type;
+      jboolean null_ok;
+    };
+
+    auto add_extension = [&](jvmtiExtensionFunction func,
+                             const char* id,
+                             const char* short_description,
+                             jint param_count,
+                             const std::vector<CParamInfo>& params,
+                             jint error_count,
+                             const std::vector<jvmtiError>& errors) {
+      jvmtiExtensionFunctionInfo func_info;
+      jvmtiError error;
+
+      func_info.func = func;
+
+      JvmtiUniquePtr<char[]> id_ptr = CopyString(env, id, &error);
+      if (id_ptr == nullptr) {
+        return error;
+      }
+      func_info.id = id_ptr.get();
+      char_buffers.push_back(std::move(id_ptr));
+
+      JvmtiUniquePtr<char[]> descr = CopyString(env, short_description, &error);
+      if (descr == nullptr) {
+        return error;
+      }
+      func_info.short_description = descr.get();
+      char_buffers.push_back(std::move(descr));
+
+      func_info.param_count = param_count;
+      if (param_count > 0) {
+        JvmtiUniquePtr<jvmtiParamInfo[]> params_ptr =
+            AllocJvmtiUniquePtr<jvmtiParamInfo[]>(env, param_count, &error);
+        if (params_ptr == nullptr) {
+          return error;
+        }
+        func_info.params = params_ptr.get();
+        param_buffers.push_back(std::move(params_ptr));
+
+        for (jint i = 0; i != param_count; ++i) {
+          JvmtiUniquePtr<char[]> param_name = CopyString(env, params[i].name, &error);
+          if (param_name == nullptr) {
+            return error;
+          }
+          func_info.params[i].name = param_name.get();
+          char_buffers.push_back(std::move(param_name));
+
+          func_info.params[i].kind = params[i].kind;
+          func_info.params[i].base_type = params[i].base_type;
+          func_info.params[i].null_ok = params[i].null_ok;
+        }
+      } else {
+        func_info.params = nullptr;
+      }
+
+      func_info.error_count = error_count;
+      if (error_count > 0) {
+        JvmtiUniquePtr<jvmtiError[]> errors_ptr =
+            AllocJvmtiUniquePtr<jvmtiError[]>(env, error_count, &error);
+        if (errors_ptr == nullptr) {
+          return error;
+        }
+        func_info.errors = errors_ptr.get();
+        error_buffers.push_back(std::move(errors_ptr));
+
+        for (jint i = 0; i != error_count; ++i) {
+          func_info.errors[i] = errors[i];
+        }
+      } else {
+        func_info.errors = nullptr;
+      }
+
+      ext_vector.push_back(func_info);
+
+      return ERR(NONE);
+    };
+
+    jvmtiError error;
+
+    // Heap extensions.
+    error = add_extension(
+        reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::GetObjectHeapId),
+        "com.android.art.heap.get_object_heap_id",
+        "Retrieve the heap id of the the object tagged with the given argument. An "
+            "arbitrary object is chosen if multiple objects exist with the same tag.",
+        2,
+        {                                                          // NOLINT [whitespace/braces] [4]
+            { "tag", JVMTI_KIND_IN, JVMTI_TYPE_JLONG, false},
+            { "heap_id", JVMTI_KIND_OUT, JVMTI_TYPE_JINT, false}
+        },
+        1,
+        { JVMTI_ERROR_NOT_FOUND });
+    if (error != ERR(NONE)) {
+      return error;
+    }
+
+    error = add_extension(
+        reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::GetHeapName),
+        "com.android.art.heap.get_heap_name",
+        "Retrieve the name of the heap with the given id.",
+        2,
+        {                                                          // NOLINT [whitespace/braces] [4]
+            { "heap_id", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false},
+            { "heap_name", JVMTI_KIND_ALLOC_BUF, JVMTI_TYPE_CCHAR, false}
+        },
+        1,
+        { JVMTI_ERROR_ILLEGAL_ARGUMENT });
+    if (error != ERR(NONE)) {
+      return error;
+    }
+
+    error = add_extension(
+        reinterpret_cast<jvmtiExtensionFunction>(HeapExtensions::IterateThroughHeapExt),
+        "com.android.art.heap.iterate_through_heap_ext",
+        "Iterate through a heap. This is equivalent to the standard IterateThroughHeap function,"
+        " except for additionally passing the heap id of the current object. The jvmtiHeapCallbacks"
+        " structure is reused, with the callbacks field overloaded to a signature of "
+        "jint (*)(jlong, jlong, jlong*, jint length, void*, jint).",
+        4,
+        {                                                          // NOLINT [whitespace/braces] [4]
+            { "heap_filter", JVMTI_KIND_IN, JVMTI_TYPE_JINT, false},
+            { "klass", JVMTI_KIND_IN, JVMTI_TYPE_JCLASS, true},
+            { "callbacks", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CVOID, false},
+            { "user_data", JVMTI_KIND_IN_PTR, JVMTI_TYPE_CVOID, true}
+        },
+        3,
+        {                                                          // NOLINT [whitespace/braces] [4]
+            JVMTI_ERROR_MUST_POSSESS_CAPABILITY,
+            JVMTI_ERROR_INVALID_CLASS,
+            JVMTI_ERROR_NULL_POINTER
+        });
+    if (error != ERR(NONE)) {
+      return error;
+    }
+
+    error = add_extension(
+        reinterpret_cast<jvmtiExtensionFunction>(AllocUtil::GetGlobalJvmtiAllocationState),
+        "com.android.art.alloc.get_global_jvmti_allocation_state",
+        "Returns the total amount of memory currently allocated by all jvmtiEnvs through the"
+        " 'Allocate' jvmti function. This does not include any memory that has been deallocated"
+        " through the 'Deallocate' function. This number is approximate and might not correspond"
+        " exactly to the sum of the sizes of all not freed allocations.",
+        1,
+        {                                                          // NOLINT [whitespace/braces] [4]
+            { "currently_allocated", JVMTI_KIND_OUT, JVMTI_TYPE_JLONG, false},
+        },
+        1,
+        { ERR(NULL_POINTER) });
+    if (error != ERR(NONE)) {
+      return error;
+    }
+
+    // Copy into output buffer.
+
+    *extension_count_ptr = ext_vector.size();
+    JvmtiUniquePtr<jvmtiExtensionFunctionInfo[]> out_data =
+        AllocJvmtiUniquePtr<jvmtiExtensionFunctionInfo[]>(env, ext_vector.size(), &error);
+    if (out_data == nullptr) {
+      return error;
+    }
+    memcpy(out_data.get(),
+           ext_vector.data(),
+           ext_vector.size() * sizeof(jvmtiExtensionFunctionInfo));
+    *extensions = out_data.release();
+
+    // Release all the buffer holders, we're OK now.
+    for (auto& holder : char_buffers) {
+      holder.release();
+    }
+    for (auto& holder : param_buffers) {
+      holder.release();
+    }
+    for (auto& holder : error_buffers) {
+      holder.release();
+    }
+
+    return ERR(NONE);
+  }
+
+  static jvmtiError GetExtensionEvents(jvmtiEnv* env,
+                                       jint* extension_count_ptr,
+                                       jvmtiExtensionEventInfo** extensions) {
+    ENSURE_VALID_ENV(env);
+    // We do not have any extension events.
+    *extension_count_ptr = 0;
+    *extensions = nullptr;
+
+    return ERR(NONE);
+  }
+
+  static jvmtiError SetExtensionEventCallback(jvmtiEnv* env,
+                                              jint extension_event_index ATTRIBUTE_UNUSED,
+                                              jvmtiExtensionEvent callback ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    // We do not have any extension events, so any call is illegal.
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  static jvmtiError GetPotentialCapabilities(jvmtiEnv* env, jvmtiCapabilities* capabilities_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_NON_NULL(capabilities_ptr);
+    *capabilities_ptr = kPotentialCapabilities;
+    return OK;
+  }
+
+  static jvmtiError AddCapabilities(jvmtiEnv* env, const jvmtiCapabilities* capabilities_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_NON_NULL(capabilities_ptr);
+    ArtJvmTiEnv* art_env = static_cast<ArtJvmTiEnv*>(env);
+    jvmtiError ret = OK;
+    jvmtiCapabilities changed = {};
+    jvmtiCapabilities potential_capabilities = {};
+    ret = env->GetPotentialCapabilities(&potential_capabilities);
+    if (ret != OK) {
+      return ret;
+    }
+#define ADD_CAPABILITY(e) \
+    do { \
+      if (capabilities_ptr->e == 1) { \
+        if (potential_capabilities.e == 1) { \
+          if (art_env->capabilities.e != 1) { \
+            art_env->capabilities.e = 1; \
+            changed.e = 1; \
+          }\
+        } else { \
+          ret = ERR(NOT_AVAILABLE); \
+        } \
+      } \
+    } while (false)
+
+    ADD_CAPABILITY(can_tag_objects);
+    ADD_CAPABILITY(can_generate_field_modification_events);
+    ADD_CAPABILITY(can_generate_field_access_events);
+    ADD_CAPABILITY(can_get_bytecodes);
+    ADD_CAPABILITY(can_get_synthetic_attribute);
+    ADD_CAPABILITY(can_get_owned_monitor_info);
+    ADD_CAPABILITY(can_get_current_contended_monitor);
+    ADD_CAPABILITY(can_get_monitor_info);
+    ADD_CAPABILITY(can_pop_frame);
+    ADD_CAPABILITY(can_redefine_classes);
+    ADD_CAPABILITY(can_signal_thread);
+    ADD_CAPABILITY(can_get_source_file_name);
+    ADD_CAPABILITY(can_get_line_numbers);
+    ADD_CAPABILITY(can_get_source_debug_extension);
+    ADD_CAPABILITY(can_access_local_variables);
+    ADD_CAPABILITY(can_maintain_original_method_order);
+    ADD_CAPABILITY(can_generate_single_step_events);
+    ADD_CAPABILITY(can_generate_exception_events);
+    ADD_CAPABILITY(can_generate_frame_pop_events);
+    ADD_CAPABILITY(can_generate_breakpoint_events);
+    ADD_CAPABILITY(can_suspend);
+    ADD_CAPABILITY(can_redefine_any_class);
+    ADD_CAPABILITY(can_get_current_thread_cpu_time);
+    ADD_CAPABILITY(can_get_thread_cpu_time);
+    ADD_CAPABILITY(can_generate_method_entry_events);
+    ADD_CAPABILITY(can_generate_method_exit_events);
+    ADD_CAPABILITY(can_generate_all_class_hook_events);
+    ADD_CAPABILITY(can_generate_compiled_method_load_events);
+    ADD_CAPABILITY(can_generate_monitor_events);
+    ADD_CAPABILITY(can_generate_vm_object_alloc_events);
+    ADD_CAPABILITY(can_generate_native_method_bind_events);
+    ADD_CAPABILITY(can_generate_garbage_collection_events);
+    ADD_CAPABILITY(can_generate_object_free_events);
+    ADD_CAPABILITY(can_force_early_return);
+    ADD_CAPABILITY(can_get_owned_monitor_stack_depth_info);
+    ADD_CAPABILITY(can_get_constant_pool);
+    ADD_CAPABILITY(can_set_native_method_prefix);
+    ADD_CAPABILITY(can_retransform_classes);
+    ADD_CAPABILITY(can_retransform_any_class);
+    ADD_CAPABILITY(can_generate_resource_exhaustion_heap_events);
+    ADD_CAPABILITY(can_generate_resource_exhaustion_threads_events);
+#undef ADD_CAPABILITY
+    gEventHandler.HandleChangedCapabilities(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                                            changed,
+                                            /*added*/true);
+    return ret;
+  }
+
+  static jvmtiError RelinquishCapabilities(jvmtiEnv* env,
+                                           const jvmtiCapabilities* capabilities_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_NON_NULL(capabilities_ptr);
+    ArtJvmTiEnv* art_env = reinterpret_cast<ArtJvmTiEnv*>(env);
+    jvmtiCapabilities changed = {};
+#define DEL_CAPABILITY(e) \
+    do { \
+      if (capabilities_ptr->e == 1) { \
+        if (art_env->capabilities.e == 1) { \
+          art_env->capabilities.e = 0;\
+          changed.e = 1; \
+        } \
+      } \
+    } while (false)
+
+    DEL_CAPABILITY(can_tag_objects);
+    DEL_CAPABILITY(can_generate_field_modification_events);
+    DEL_CAPABILITY(can_generate_field_access_events);
+    DEL_CAPABILITY(can_get_bytecodes);
+    DEL_CAPABILITY(can_get_synthetic_attribute);
+    DEL_CAPABILITY(can_get_owned_monitor_info);
+    DEL_CAPABILITY(can_get_current_contended_monitor);
+    DEL_CAPABILITY(can_get_monitor_info);
+    DEL_CAPABILITY(can_pop_frame);
+    DEL_CAPABILITY(can_redefine_classes);
+    DEL_CAPABILITY(can_signal_thread);
+    DEL_CAPABILITY(can_get_source_file_name);
+    DEL_CAPABILITY(can_get_line_numbers);
+    DEL_CAPABILITY(can_get_source_debug_extension);
+    DEL_CAPABILITY(can_access_local_variables);
+    DEL_CAPABILITY(can_maintain_original_method_order);
+    DEL_CAPABILITY(can_generate_single_step_events);
+    DEL_CAPABILITY(can_generate_exception_events);
+    DEL_CAPABILITY(can_generate_frame_pop_events);
+    DEL_CAPABILITY(can_generate_breakpoint_events);
+    DEL_CAPABILITY(can_suspend);
+    DEL_CAPABILITY(can_redefine_any_class);
+    DEL_CAPABILITY(can_get_current_thread_cpu_time);
+    DEL_CAPABILITY(can_get_thread_cpu_time);
+    DEL_CAPABILITY(can_generate_method_entry_events);
+    DEL_CAPABILITY(can_generate_method_exit_events);
+    DEL_CAPABILITY(can_generate_all_class_hook_events);
+    DEL_CAPABILITY(can_generate_compiled_method_load_events);
+    DEL_CAPABILITY(can_generate_monitor_events);
+    DEL_CAPABILITY(can_generate_vm_object_alloc_events);
+    DEL_CAPABILITY(can_generate_native_method_bind_events);
+    DEL_CAPABILITY(can_generate_garbage_collection_events);
+    DEL_CAPABILITY(can_generate_object_free_events);
+    DEL_CAPABILITY(can_force_early_return);
+    DEL_CAPABILITY(can_get_owned_monitor_stack_depth_info);
+    DEL_CAPABILITY(can_get_constant_pool);
+    DEL_CAPABILITY(can_set_native_method_prefix);
+    DEL_CAPABILITY(can_retransform_classes);
+    DEL_CAPABILITY(can_retransform_any_class);
+    DEL_CAPABILITY(can_generate_resource_exhaustion_heap_events);
+    DEL_CAPABILITY(can_generate_resource_exhaustion_threads_events);
+#undef DEL_CAPABILITY
+    gEventHandler.HandleChangedCapabilities(ArtJvmTiEnv::AsArtJvmTiEnv(env),
+                                            changed,
+                                            /*added*/false);
+    return OK;
+  }
+
+  static jvmtiError GetCapabilities(jvmtiEnv* env, jvmtiCapabilities* capabilities_ptr) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_NON_NULL(capabilities_ptr);
+    ArtJvmTiEnv* artenv = reinterpret_cast<ArtJvmTiEnv*>(env);
+    *capabilities_ptr = artenv->capabilities;
+    return OK;
+  }
+
+  static jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiEnv* env,
+                                                 jvmtiTimerInfo* info_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetCurrentThreadCpuTime(jvmtiEnv* env, jlong* nanos_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_current_thread_cpu_time);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetThreadCpuTimerInfo(jvmtiEnv* env,
+                                          jvmtiTimerInfo* info_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_thread_cpu_time);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetThreadCpuTime(jvmtiEnv* env,
+                                     jthread thread ATTRIBUTE_UNUSED,
+                                     jlong* nanos_ptr ATTRIBUTE_UNUSED) {
+    ENSURE_VALID_ENV(env);
+    ENSURE_HAS_CAP(env, can_get_thread_cpu_time);
+    return ERR(NOT_IMPLEMENTED);
+  }
+
+  static jvmtiError GetTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr) {
+    ENSURE_VALID_ENV(env);
+    return TimerUtil::GetTimerInfo(env, info_ptr);
+  }
+
+  static jvmtiError GetTime(jvmtiEnv* env, jlong* nanos_ptr) {
+    ENSURE_VALID_ENV(env);
+    return TimerUtil::GetTime(env, nanos_ptr);
+  }
+
+  static jvmtiError GetAvailableProcessors(jvmtiEnv* env, jint* processor_count_ptr) {
+    ENSURE_VALID_ENV(env);
+    return TimerUtil::GetAvailableProcessors(env, processor_count_ptr);
+  }
+
+  static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment) {
+    ENSURE_VALID_ENV(env);
+    return SearchUtil::AddToBootstrapClassLoaderSearch(env, segment);
+  }
+
+  static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment) {
+    ENSURE_VALID_ENV(env);
+    return SearchUtil::AddToSystemClassLoaderSearch(env, segment);
+  }
+
+  static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr) {
+    ENSURE_VALID_ENV(env);
+    return PropertiesUtil::GetSystemProperties(env, count_ptr, property_ptr);
+  }
+
+  static jvmtiError GetSystemProperty(jvmtiEnv* env, const char* property, char** value_ptr) {
+    ENSURE_VALID_ENV(env);
+    return PropertiesUtil::GetSystemProperty(env, property, value_ptr);
+  }
+
+  static jvmtiError SetSystemProperty(jvmtiEnv* env, const char* property, const char* value) {
+    ENSURE_VALID_ENV(env);
+    return PropertiesUtil::SetSystemProperty(env, property, value);
+  }
+
+  static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr) {
+    ENSURE_VALID_ENV(env);
+    return PhaseUtil::GetPhase(env, phase_ptr);
+  }
+
+  static jvmtiError DisposeEnvironment(jvmtiEnv* env) {
+    ENSURE_VALID_ENV(env);
+    ArtJvmTiEnv* tienv = ArtJvmTiEnv::AsArtJvmTiEnv(env);
+    gEventHandler.RemoveArtJvmTiEnv(tienv);
+    art::Runtime::Current()->RemoveSystemWeakHolder(tienv->object_tag_table.get());
+    ThreadUtil::RemoveEnvironment(tienv);
+    delete tienv;
+    return OK;
+  }
+
+  static jvmtiError SetEnvironmentLocalStorage(jvmtiEnv* env, const void* data) {
+    ENSURE_VALID_ENV(env);
+    reinterpret_cast<ArtJvmTiEnv*>(env)->local_data = const_cast<void*>(data);
+    return OK;
+  }
+
+  static jvmtiError GetEnvironmentLocalStorage(jvmtiEnv* env, void** data_ptr) {
+    ENSURE_VALID_ENV(env);
+    *data_ptr = reinterpret_cast<ArtJvmTiEnv*>(env)->local_data;
+    return OK;
+  }
+
+  static jvmtiError GetVersionNumber(jvmtiEnv* env, jint* version_ptr) {
+    ENSURE_VALID_ENV(env);
+    *version_ptr = JVMTI_VERSION;
+    return OK;
+  }
+
+  static jvmtiError GetErrorName(jvmtiEnv* env, jvmtiError error,  char** name_ptr) {
+    ENSURE_NON_NULL(name_ptr);
+    auto copy_fn = [&](const char* name_cstr) {
+      jvmtiError res;
+      JvmtiUniquePtr<char[]> copy = CopyString(env, name_cstr, &res);
+      if (copy == nullptr) {
+        *name_ptr = nullptr;
+        return res;
+      } else {
+        *name_ptr = copy.release();
+        return OK;
+      }
+    };
+    switch (error) {
+#define ERROR_CASE(e) case (JVMTI_ERROR_ ## e) : \
+        return copy_fn("JVMTI_ERROR_"#e);
+      ERROR_CASE(NONE);
+      ERROR_CASE(INVALID_THREAD);
+      ERROR_CASE(INVALID_THREAD_GROUP);
+      ERROR_CASE(INVALID_PRIORITY);
+      ERROR_CASE(THREAD_NOT_SUSPENDED);
+      ERROR_CASE(THREAD_SUSPENDED);
+      ERROR_CASE(THREAD_NOT_ALIVE);
+      ERROR_CASE(INVALID_OBJECT);
+      ERROR_CASE(INVALID_CLASS);
+      ERROR_CASE(CLASS_NOT_PREPARED);
+      ERROR_CASE(INVALID_METHODID);
+      ERROR_CASE(INVALID_LOCATION);
+      ERROR_CASE(INVALID_FIELDID);
+      ERROR_CASE(NO_MORE_FRAMES);
+      ERROR_CASE(OPAQUE_FRAME);
+      ERROR_CASE(TYPE_MISMATCH);
+      ERROR_CASE(INVALID_SLOT);
+      ERROR_CASE(DUPLICATE);
+      ERROR_CASE(NOT_FOUND);
+      ERROR_CASE(INVALID_MONITOR);
+      ERROR_CASE(NOT_MONITOR_OWNER);
+      ERROR_CASE(INTERRUPT);
+      ERROR_CASE(INVALID_CLASS_FORMAT);
+      ERROR_CASE(CIRCULAR_CLASS_DEFINITION);
+      ERROR_CASE(FAILS_VERIFICATION);
+      ERROR_CASE(UNSUPPORTED_REDEFINITION_METHOD_ADDED);
+      ERROR_CASE(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED);
+      ERROR_CASE(INVALID_TYPESTATE);
+      ERROR_CASE(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED);
+      ERROR_CASE(UNSUPPORTED_REDEFINITION_METHOD_DELETED);
+      ERROR_CASE(UNSUPPORTED_VERSION);
+      ERROR_CASE(NAMES_DONT_MATCH);
+      ERROR_CASE(UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED);
+      ERROR_CASE(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED);
+      ERROR_CASE(UNMODIFIABLE_CLASS);
+      ERROR_CASE(NOT_AVAILABLE);
+      ERROR_CASE(MUST_POSSESS_CAPABILITY);
+      ERROR_CASE(NULL_POINTER);
+      ERROR_CASE(ABSENT_INFORMATION);
+      ERROR_CASE(INVALID_EVENT_TYPE);
+      ERROR_CASE(ILLEGAL_ARGUMENT);
+      ERROR_CASE(NATIVE_METHOD);
+      ERROR_CASE(CLASS_LOADER_UNSUPPORTED);
+      ERROR_CASE(OUT_OF_MEMORY);
+      ERROR_CASE(ACCESS_DENIED);
+      ERROR_CASE(WRONG_PHASE);
+      ERROR_CASE(INTERNAL);
+      ERROR_CASE(UNATTACHED_THREAD);
+      ERROR_CASE(INVALID_ENVIRONMENT);
+#undef ERROR_CASE
+    }
+
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  static jvmtiError SetVerboseFlag(jvmtiEnv* env,
+                                   jvmtiVerboseFlag flag,
+                                   jboolean value) {
+    ENSURE_VALID_ENV(env);
+    if (flag == jvmtiVerboseFlag::JVMTI_VERBOSE_OTHER) {
+      // OTHER is special, as it's 0, so can't do a bit check.
+      bool val = (value == JNI_TRUE) ? true : false;
+
+      art::gLogVerbosity.collector = val;
+      art::gLogVerbosity.compiler = val;
+      art::gLogVerbosity.deopt = val;
+      art::gLogVerbosity.heap = val;
+      art::gLogVerbosity.jdwp = val;
+      art::gLogVerbosity.jit = val;
+      art::gLogVerbosity.monitor = val;
+      art::gLogVerbosity.oat = val;
+      art::gLogVerbosity.profiler = val;
+      art::gLogVerbosity.signals = val;
+      art::gLogVerbosity.simulator = val;
+      art::gLogVerbosity.startup = val;
+      art::gLogVerbosity.third_party_jni = val;
+      art::gLogVerbosity.threads = val;
+      art::gLogVerbosity.verifier = val;
+      art::gLogVerbosity.image = val;
+
+      // Note: can't switch systrace_lock_logging. That requires changing entrypoints.
+
+      art::gLogVerbosity.agents = val;
+    } else {
+      // Spec isn't clear whether "flag" is a mask or supposed to be single. We implement the mask
+      // semantics.
+      constexpr std::underlying_type<jvmtiVerboseFlag>::type kMask =
+          jvmtiVerboseFlag::JVMTI_VERBOSE_GC |
+          jvmtiVerboseFlag::JVMTI_VERBOSE_CLASS |
+          jvmtiVerboseFlag::JVMTI_VERBOSE_JNI;
+      if ((flag & ~kMask) != 0) {
+        return ERR(ILLEGAL_ARGUMENT);
+      }
+
+      bool val = (value == JNI_TRUE) ? true : false;
+
+      if ((flag & jvmtiVerboseFlag::JVMTI_VERBOSE_GC) != 0) {
+        art::gLogVerbosity.gc = val;
+      }
+
+      if ((flag & jvmtiVerboseFlag::JVMTI_VERBOSE_CLASS) != 0) {
+        art::gLogVerbosity.class_linker = val;
+      }
+
+      if ((flag & jvmtiVerboseFlag::JVMTI_VERBOSE_JNI) != 0) {
+        art::gLogVerbosity.jni = val;
+      }
+    }
+
+    return ERR(NONE);
+  }
+
+  static jvmtiError GetJLocationFormat(jvmtiEnv* env, jvmtiJlocationFormat* format_ptr) {
+    ENSURE_VALID_ENV(env);
+    // Report BCI as jlocation format. We report dex bytecode indices.
+    if (format_ptr == nullptr) {
+      return ERR(NULL_POINTER);
+    }
+    *format_ptr = jvmtiJlocationFormat::JVMTI_JLOCATION_JVMBCI;
+    return ERR(NONE);
+  }
+};
+
+static bool IsJvmtiVersion(jint version) {
+  return version ==  JVMTI_VERSION_1 ||
+         version == JVMTI_VERSION_1_0 ||
+         version == JVMTI_VERSION_1_1 ||
+         version == JVMTI_VERSION_1_2 ||
+         version == JVMTI_VERSION;
+}
+
+extern const jvmtiInterface_1 gJvmtiInterface;
+
+ArtJvmTiEnv::ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler)
+    : art_vm(runtime),
+      local_data(nullptr),
+      capabilities() {
+  object_tag_table = std::unique_ptr<ObjectTagTable>(new ObjectTagTable(event_handler, this));
+  functions = &gJvmtiInterface;
+}
+
+// Creates a jvmtiEnv and returns it with the art::ti::Env that is associated with it. new_art_ti
+// is a pointer to the uninitialized memory for an art::ti::Env.
+static void CreateArtJvmTiEnv(art::JavaVMExt* vm, /*out*/void** new_jvmtiEnv) {
+  struct ArtJvmTiEnv* env = new ArtJvmTiEnv(vm, &gEventHandler);
+  *new_jvmtiEnv = env;
+
+  gEventHandler.RegisterArtJvmTiEnv(env);
+
+  art::Runtime::Current()->AddSystemWeakHolder(
+      ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get());
+}
+
+// A hook that the runtime uses to allow plugins to handle GetEnv calls. It returns true and
+// places the return value in 'env' if this library can handle the GetEnv request. Otherwise
+// returns false and does not modify the 'env' pointer.
+static jint GetEnvHandler(art::JavaVMExt* vm, /*out*/void** env, jint version) {
+  if (IsJvmtiVersion(version)) {
+    CreateArtJvmTiEnv(vm, env);
+    return JNI_OK;
+  } else {
+    printf("version 0x%x is not valid!", version);
+    return JNI_EVERSION;
+  }
+}
+
+// The plugin initialization function. This adds the jvmti environment.
+extern "C" bool ArtPlugin_Initialize() {
+  art::Runtime* runtime = art::Runtime::Current();
+
+  if (runtime->IsStarted()) {
+    PhaseUtil::SetToLive();
+  } else {
+    PhaseUtil::SetToOnLoad();
+  }
+  PhaseUtil::Register(&gEventHandler);
+  ThreadUtil::Register(&gEventHandler);
+  ClassUtil::Register(&gEventHandler);
+  DumpUtil::Register(&gEventHandler);
+  MethodUtil::Register(&gEventHandler);
+  SearchUtil::Register();
+  HeapUtil::Register();
+
+  runtime->GetJavaVM()->AddEnvironmentHook(GetEnvHandler);
+
+  return true;
+}
+
+extern "C" bool ArtPlugin_Deinitialize() {
+  gEventHandler.Shutdown();
+  PhaseUtil::Unregister();
+  ThreadUtil::Unregister();
+  ClassUtil::Unregister();
+  DumpUtil::Unregister();
+  MethodUtil::Unregister();
+  SearchUtil::Unregister();
+  HeapUtil::Unregister();
+
+  return true;
+}
+
+// The actual struct holding all of the entrypoints into the jvmti interface.
+const jvmtiInterface_1 gJvmtiInterface = {
+  nullptr,  // reserved1
+  JvmtiFunctions::SetEventNotificationMode,
+  nullptr,  // reserved3
+  JvmtiFunctions::GetAllThreads,
+  JvmtiFunctions::SuspendThread,
+  JvmtiFunctions::ResumeThread,
+  JvmtiFunctions::StopThread,
+  JvmtiFunctions::InterruptThread,
+  JvmtiFunctions::GetThreadInfo,
+  JvmtiFunctions::GetOwnedMonitorInfo,  // 10
+  JvmtiFunctions::GetCurrentContendedMonitor,
+  JvmtiFunctions::RunAgentThread,
+  JvmtiFunctions::GetTopThreadGroups,
+  JvmtiFunctions::GetThreadGroupInfo,
+  JvmtiFunctions::GetThreadGroupChildren,
+  JvmtiFunctions::GetFrameCount,
+  JvmtiFunctions::GetThreadState,
+  JvmtiFunctions::GetCurrentThread,
+  JvmtiFunctions::GetFrameLocation,
+  JvmtiFunctions::NotifyFramePop,  // 20
+  JvmtiFunctions::GetLocalObject,
+  JvmtiFunctions::GetLocalInt,
+  JvmtiFunctions::GetLocalLong,
+  JvmtiFunctions::GetLocalFloat,
+  JvmtiFunctions::GetLocalDouble,
+  JvmtiFunctions::SetLocalObject,
+  JvmtiFunctions::SetLocalInt,
+  JvmtiFunctions::SetLocalLong,
+  JvmtiFunctions::SetLocalFloat,
+  JvmtiFunctions::SetLocalDouble,  // 30
+  JvmtiFunctions::CreateRawMonitor,
+  JvmtiFunctions::DestroyRawMonitor,
+  JvmtiFunctions::RawMonitorEnter,
+  JvmtiFunctions::RawMonitorExit,
+  JvmtiFunctions::RawMonitorWait,
+  JvmtiFunctions::RawMonitorNotify,
+  JvmtiFunctions::RawMonitorNotifyAll,
+  JvmtiFunctions::SetBreakpoint,
+  JvmtiFunctions::ClearBreakpoint,
+  nullptr,  // reserved40
+  JvmtiFunctions::SetFieldAccessWatch,
+  JvmtiFunctions::ClearFieldAccessWatch,
+  JvmtiFunctions::SetFieldModificationWatch,
+  JvmtiFunctions::ClearFieldModificationWatch,
+  JvmtiFunctions::IsModifiableClass,
+  JvmtiFunctions::Allocate,
+  JvmtiFunctions::Deallocate,
+  JvmtiFunctions::GetClassSignature,
+  JvmtiFunctions::GetClassStatus,
+  JvmtiFunctions::GetSourceFileName,  // 50
+  JvmtiFunctions::GetClassModifiers,
+  JvmtiFunctions::GetClassMethods,
+  JvmtiFunctions::GetClassFields,
+  JvmtiFunctions::GetImplementedInterfaces,
+  JvmtiFunctions::IsInterface,
+  JvmtiFunctions::IsArrayClass,
+  JvmtiFunctions::GetClassLoader,
+  JvmtiFunctions::GetObjectHashCode,
+  JvmtiFunctions::GetObjectMonitorUsage,
+  JvmtiFunctions::GetFieldName,  // 60
+  JvmtiFunctions::GetFieldDeclaringClass,
+  JvmtiFunctions::GetFieldModifiers,
+  JvmtiFunctions::IsFieldSynthetic,
+  JvmtiFunctions::GetMethodName,
+  JvmtiFunctions::GetMethodDeclaringClass,
+  JvmtiFunctions::GetMethodModifiers,
+  nullptr,  // reserved67
+  JvmtiFunctions::GetMaxLocals,
+  JvmtiFunctions::GetArgumentsSize,
+  JvmtiFunctions::GetLineNumberTable,  // 70
+  JvmtiFunctions::GetMethodLocation,
+  JvmtiFunctions::GetLocalVariableTable,
+  JvmtiFunctions::SetNativeMethodPrefix,
+  JvmtiFunctions::SetNativeMethodPrefixes,
+  JvmtiFunctions::GetBytecodes,
+  JvmtiFunctions::IsMethodNative,
+  JvmtiFunctions::IsMethodSynthetic,
+  JvmtiFunctions::GetLoadedClasses,
+  JvmtiFunctions::GetClassLoaderClasses,
+  JvmtiFunctions::PopFrame,  // 80
+  JvmtiFunctions::ForceEarlyReturnObject,
+  JvmtiFunctions::ForceEarlyReturnInt,
+  JvmtiFunctions::ForceEarlyReturnLong,
+  JvmtiFunctions::ForceEarlyReturnFloat,
+  JvmtiFunctions::ForceEarlyReturnDouble,
+  JvmtiFunctions::ForceEarlyReturnVoid,
+  JvmtiFunctions::RedefineClasses,
+  JvmtiFunctions::GetVersionNumber,
+  JvmtiFunctions::GetCapabilities,
+  JvmtiFunctions::GetSourceDebugExtension,  // 90
+  JvmtiFunctions::IsMethodObsolete,
+  JvmtiFunctions::SuspendThreadList,
+  JvmtiFunctions::ResumeThreadList,
+  nullptr,  // reserved94
+  nullptr,  // reserved95
+  nullptr,  // reserved96
+  nullptr,  // reserved97
+  nullptr,  // reserved98
+  nullptr,  // reserved99
+  JvmtiFunctions::GetAllStackTraces,  // 100
+  JvmtiFunctions::GetThreadListStackTraces,
+  JvmtiFunctions::GetThreadLocalStorage,
+  JvmtiFunctions::SetThreadLocalStorage,
+  JvmtiFunctions::GetStackTrace,
+  nullptr,  // reserved105
+  JvmtiFunctions::GetTag,
+  JvmtiFunctions::SetTag,
+  JvmtiFunctions::ForceGarbageCollection,
+  JvmtiFunctions::IterateOverObjectsReachableFromObject,
+  JvmtiFunctions::IterateOverReachableObjects,  // 110
+  JvmtiFunctions::IterateOverHeap,
+  JvmtiFunctions::IterateOverInstancesOfClass,
+  nullptr,  // reserved113
+  JvmtiFunctions::GetObjectsWithTags,
+  JvmtiFunctions::FollowReferences,
+  JvmtiFunctions::IterateThroughHeap,
+  nullptr,  // reserved117
+  nullptr,  // reserved118
+  nullptr,  // reserved119
+  JvmtiFunctions::SetJNIFunctionTable,  // 120
+  JvmtiFunctions::GetJNIFunctionTable,
+  JvmtiFunctions::SetEventCallbacks,
+  JvmtiFunctions::GenerateEvents,
+  JvmtiFunctions::GetExtensionFunctions,
+  JvmtiFunctions::GetExtensionEvents,
+  JvmtiFunctions::SetExtensionEventCallback,
+  JvmtiFunctions::DisposeEnvironment,
+  JvmtiFunctions::GetErrorName,
+  JvmtiFunctions::GetJLocationFormat,
+  JvmtiFunctions::GetSystemProperties,  // 130
+  JvmtiFunctions::GetSystemProperty,
+  JvmtiFunctions::SetSystemProperty,
+  JvmtiFunctions::GetPhase,
+  JvmtiFunctions::GetCurrentThreadCpuTimerInfo,
+  JvmtiFunctions::GetCurrentThreadCpuTime,
+  JvmtiFunctions::GetThreadCpuTimerInfo,
+  JvmtiFunctions::GetThreadCpuTime,
+  JvmtiFunctions::GetTimerInfo,
+  JvmtiFunctions::GetTime,
+  JvmtiFunctions::GetPotentialCapabilities,  // 140
+  nullptr,  // reserved141
+  JvmtiFunctions::AddCapabilities,
+  JvmtiFunctions::RelinquishCapabilities,
+  JvmtiFunctions::GetAvailableProcessors,
+  JvmtiFunctions::GetClassVersionNumbers,
+  JvmtiFunctions::GetConstantPool,
+  JvmtiFunctions::GetEnvironmentLocalStorage,
+  JvmtiFunctions::SetEnvironmentLocalStorage,
+  JvmtiFunctions::AddToBootstrapClassLoaderSearch,
+  JvmtiFunctions::SetVerboseFlag,  // 150
+  JvmtiFunctions::AddToSystemClassLoaderSearch,
+  JvmtiFunctions::RetransformClasses,
+  JvmtiFunctions::GetOwnedMonitorStackDepthInfo,
+  JvmtiFunctions::GetObjectSize,
+  JvmtiFunctions::GetLocalInstance,
+};
+
+};  // namespace openjdkjvmti
diff --git a/openjdkjvmti/README.md b/openjdkjvmti/README.md
new file mode 100644
index 0000000..b8bab57
--- /dev/null
+++ b/openjdkjvmti/README.md
@@ -0,0 +1,7 @@
+openjdkjvmti plugin
+====
+
+This is a partial implementation of the JVMTI v1.2 interface for the android
+runtime as a plugin. This allows the use of agents that can modify the running
+state of the program by modifying dex files in memory and performing other
+operations on the global runtime state.
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
new file mode 100644
index 0000000..12f4cab
--- /dev/null
+++ b/openjdkjvmti/art_jvmti.h
@@ -0,0 +1,265 @@
+/* Copyright (C) 2016 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_ART_JVMTI_H_
+#define ART_OPENJDKJVMTI_ART_JVMTI_H_
+
+#include <memory>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+
+#include <jni.h>
+
+#include "base/casts.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strlcpy.h"
+#include "events.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "jvmti.h"
+#include "ti_breakpoint.h"
+
+namespace art {
+class ArtField;
+class ArtMethod;
+}  // namespace art
+
+namespace openjdkjvmti {
+
+class ObjectTagTable;
+
+// A structure that is a jvmtiEnv with additional information for the runtime.
+struct ArtJvmTiEnv : public jvmtiEnv {
+  art::JavaVMExt* art_vm;
+  void* local_data;
+  jvmtiCapabilities capabilities;
+
+  EventMasks event_masks;
+  std::unique_ptr<jvmtiEventCallbacks> event_callbacks;
+
+  // Tagging is specific to the jvmtiEnv.
+  std::unique_ptr<ObjectTagTable> object_tag_table;
+
+  // Set of watched fields is unique to each jvmtiEnv.
+  // TODO It might be good to follow the RI and only let one jvmtiEnv ever have the watch caps so
+  // we can record this on the field directly. We could do this either using free access-flag bits
+  // or by putting a list in the ClassExt of a field's DeclaringClass.
+  // TODO Maybe just have an extension to let one put a watch on every field, that would probably be
+  // good enough maybe since you probably want either a few or all/almost all of them.
+  std::unordered_set<art::ArtField*> access_watched_fields;
+  std::unordered_set<art::ArtField*> modify_watched_fields;
+
+  // Set of breakpoints is unique to each jvmtiEnv.
+  std::unordered_set<Breakpoint> breakpoints;
+
+  ArtJvmTiEnv(art::JavaVMExt* runtime, EventHandler* event_handler);
+
+  static ArtJvmTiEnv* AsArtJvmTiEnv(jvmtiEnv* env) {
+    return art::down_cast<ArtJvmTiEnv*>(env);
+  }
+};
+
+// Macro and constexpr to make error values less annoying to write.
+#define ERR(e) JVMTI_ERROR_ ## e
+static constexpr jvmtiError OK = JVMTI_ERROR_NONE;
+
+// Special error code for unimplemented functions in JVMTI
+static constexpr jvmtiError ERR(NOT_IMPLEMENTED) = JVMTI_ERROR_NOT_AVAILABLE;
+
+static inline JNIEnv* GetJniEnv(jvmtiEnv* env) {
+  JNIEnv* ret_value = nullptr;
+  jint res = reinterpret_cast<ArtJvmTiEnv*>(env)->art_vm->GetEnv(
+      reinterpret_cast<void**>(&ret_value), JNI_VERSION_1_1);
+  if (res != JNI_OK) {
+    return nullptr;
+  }
+  return ret_value;
+}
+
+template <typename T>
+class JvmtiDeleter {
+ public:
+  JvmtiDeleter() : env_(nullptr) {}
+  explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+  JvmtiDeleter(JvmtiDeleter&) = default;
+  JvmtiDeleter(JvmtiDeleter&&) = default;
+  JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+  void operator()(T* ptr) const {
+    CHECK(env_ != nullptr);
+    jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+    CHECK(ret == ERR(NONE));
+  }
+
+ private:
+  mutable jvmtiEnv* env_;
+};
+
+template <typename T>
+class JvmtiDeleter<T[]> {
+  public:
+  JvmtiDeleter() : env_(nullptr) {}
+  explicit JvmtiDeleter(jvmtiEnv* env) : env_(env) {}
+
+  JvmtiDeleter(JvmtiDeleter&) = default;
+  JvmtiDeleter(JvmtiDeleter&&) = default;
+  JvmtiDeleter& operator=(const JvmtiDeleter&) = default;
+
+  template <typename U>
+  void operator()(U* ptr) const {
+    CHECK(env_ != nullptr);
+    jvmtiError ret = env_->Deallocate(reinterpret_cast<unsigned char*>(ptr));
+    CHECK(ret == ERR(NONE));
+  }
+
+ private:
+  mutable jvmtiEnv* env_;
+};
+
+template <typename T>
+using JvmtiUniquePtr = std::unique_ptr<T, JvmtiDeleter<T>>;
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, T* mem) {
+  return JvmtiUniquePtr<T>(mem, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> MakeJvmtiUniquePtr(jvmtiEnv* env, unsigned char* mem) {
+  return JvmtiUniquePtr<T>(reinterpret_cast<T*>(mem), JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env, jvmtiError* error) {
+  unsigned char* tmp;
+  *error = env->Allocate(sizeof(T), &tmp);
+  if (*error != ERR(NONE)) {
+    return JvmtiUniquePtr<T>();
+  }
+  return JvmtiUniquePtr<T>(tmp, JvmtiDeleter<T>(env));
+}
+
+template <typename T>
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<T> AllocJvmtiUniquePtr(jvmtiEnv* env,
+                                                    size_t count,
+                                                    jvmtiError* error) {
+  unsigned char* tmp;
+  *error = env->Allocate(sizeof(typename std::remove_extent<T>::type) * count, &tmp);
+  if (*error != ERR(NONE)) {
+    return JvmtiUniquePtr<T>();
+  }
+  return JvmtiUniquePtr<T>(reinterpret_cast<typename std::remove_extent<T>::type*>(tmp),
+                           JvmtiDeleter<T>(env));
+}
+
+ALWAYS_INLINE
+static inline jvmtiError CopyDataIntoJvmtiBuffer(ArtJvmTiEnv* env,
+                                                 const unsigned char* source,
+                                                 jint len,
+                                                 /*out*/unsigned char** dest) {
+  jvmtiError res = env->Allocate(len, dest);
+  if (res != OK) {
+    return res;
+  }
+  memcpy(reinterpret_cast<void*>(*dest),
+         reinterpret_cast<const void*>(source),
+         len);
+  return OK;
+}
+
+ALWAYS_INLINE
+static inline JvmtiUniquePtr<char[]> CopyString(jvmtiEnv* env, const char* src, jvmtiError* error) {
+  if (src == nullptr) {
+    JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, 0, error);
+    return ret;
+  }
+  size_t len = strlen(src) + 1;
+  JvmtiUniquePtr<char[]> ret = AllocJvmtiUniquePtr<char[]>(env, len, error);
+  if (ret != nullptr) {
+    strlcpy(ret.get(), src, len);
+  }
+  return ret;
+}
+
+const jvmtiCapabilities kPotentialCapabilities = {
+    .can_tag_objects                                 = 1,
+    .can_generate_field_modification_events          = 1,
+    .can_generate_field_access_events                = 1,
+    .can_get_bytecodes                               = 1,
+    .can_get_synthetic_attribute                     = 1,
+    .can_get_owned_monitor_info                      = 0,
+    .can_get_current_contended_monitor               = 0,
+    .can_get_monitor_info                            = 0,
+    .can_pop_frame                                   = 0,
+    .can_redefine_classes                            = 1,
+    .can_signal_thread                               = 0,
+    .can_get_source_file_name                        = 1,
+    .can_get_line_numbers                            = 1,
+    .can_get_source_debug_extension                  = 1,
+    .can_access_local_variables                      = 1,
+    .can_maintain_original_method_order              = 1,
+    .can_generate_single_step_events                 = 1,
+    .can_generate_exception_events                   = 0,
+    .can_generate_frame_pop_events                   = 0,
+    .can_generate_breakpoint_events                  = 1,
+    .can_suspend                                     = 1,
+    .can_redefine_any_class                          = 0,
+    .can_get_current_thread_cpu_time                 = 0,
+    .can_get_thread_cpu_time                         = 0,
+    .can_generate_method_entry_events                = 1,
+    .can_generate_method_exit_events                 = 1,
+    .can_generate_all_class_hook_events              = 0,
+    .can_generate_compiled_method_load_events        = 0,
+    .can_generate_monitor_events                     = 0,
+    .can_generate_vm_object_alloc_events             = 1,
+    .can_generate_native_method_bind_events          = 1,
+    .can_generate_garbage_collection_events          = 1,
+    .can_generate_object_free_events                 = 1,
+    .can_force_early_return                          = 0,
+    .can_get_owned_monitor_stack_depth_info          = 0,
+    .can_get_constant_pool                           = 0,
+    .can_set_native_method_prefix                    = 0,
+    .can_retransform_classes                         = 1,
+    .can_retransform_any_class                       = 0,
+    .can_generate_resource_exhaustion_heap_events    = 0,
+    .can_generate_resource_exhaustion_threads_events = 0,
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_ART_JVMTI_H_
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
new file mode 100644
index 0000000..32dba3e
--- /dev/null
+++ b/openjdkjvmti/events-inl.h
@@ -0,0 +1,440 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_OPENJDKJVMTI_EVENTS_INL_H_
+#define ART_OPENJDKJVMTI_EVENTS_INL_H_
+
+#include <array>
+
+#include "events.h"
+#include "jni_internal.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "ti_breakpoint.h"
+
+#include "art_jvmti.h"
+
+namespace openjdkjvmti {
+
+static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e) {
+  if (UNLIKELY(e == JVMTI_EVENT_CLASS_FILE_LOAD_HOOK)) {
+    if (env->capabilities.can_retransform_classes) {
+      return ArtJvmtiEvent::kClassFileLoadHookRetransformable;
+    } else {
+      return ArtJvmtiEvent::kClassFileLoadHookNonRetransformable;
+    }
+  } else {
+    return static_cast<ArtJvmtiEvent>(e);
+  }
+}
+
+namespace impl {
+
+// Infrastructure to achieve type safety for event dispatch.
+
+#define FORALL_EVENT_TYPES(fn)                                                       \
+  fn(VMInit,                  ArtJvmtiEvent::kVmInit)                                \
+  fn(VMDeath,                 ArtJvmtiEvent::kVmDeath)                               \
+  fn(ThreadStart,             ArtJvmtiEvent::kThreadStart)                           \
+  fn(ThreadEnd,               ArtJvmtiEvent::kThreadEnd)                             \
+  fn(ClassFileLoadHook,       ArtJvmtiEvent::kClassFileLoadHookRetransformable)      \
+  fn(ClassFileLoadHook,       ArtJvmtiEvent::kClassFileLoadHookNonRetransformable)   \
+  fn(ClassLoad,               ArtJvmtiEvent::kClassLoad)                             \
+  fn(ClassPrepare,            ArtJvmtiEvent::kClassPrepare)                          \
+  fn(VMStart,                 ArtJvmtiEvent::kVmStart)                               \
+  fn(Exception,               ArtJvmtiEvent::kException)                             \
+  fn(ExceptionCatch,          ArtJvmtiEvent::kExceptionCatch)                        \
+  fn(SingleStep,              ArtJvmtiEvent::kSingleStep)                            \
+  fn(FramePop,                ArtJvmtiEvent::kFramePop)                              \
+  fn(Breakpoint,              ArtJvmtiEvent::kBreakpoint)                            \
+  fn(FieldAccess,             ArtJvmtiEvent::kFieldAccess)                           \
+  fn(FieldModification,       ArtJvmtiEvent::kFieldModification)                     \
+  fn(MethodEntry,             ArtJvmtiEvent::kMethodEntry)                           \
+  fn(MethodExit,              ArtJvmtiEvent::kMethodExit)                            \
+  fn(NativeMethodBind,        ArtJvmtiEvent::kNativeMethodBind)                      \
+  fn(CompiledMethodLoad,      ArtJvmtiEvent::kCompiledMethodLoad)                    \
+  fn(CompiledMethodUnload,    ArtJvmtiEvent::kCompiledMethodUnload)                  \
+  fn(DynamicCodeGenerated,    ArtJvmtiEvent::kDynamicCodeGenerated)                  \
+  fn(DataDumpRequest,         ArtJvmtiEvent::kDataDumpRequest)                       \
+  fn(MonitorWait,             ArtJvmtiEvent::kMonitorWait)                           \
+  fn(MonitorWaited,           ArtJvmtiEvent::kMonitorWaited)                         \
+  fn(MonitorContendedEnter,   ArtJvmtiEvent::kMonitorContendedEnter)                 \
+  fn(MonitorContendedEntered, ArtJvmtiEvent::kMonitorContendedEntered)               \
+  fn(ResourceExhausted,       ArtJvmtiEvent::kResourceExhausted)                     \
+  fn(GarbageCollectionStart,  ArtJvmtiEvent::kGarbageCollectionStart)                \
+  fn(GarbageCollectionFinish, ArtJvmtiEvent::kGarbageCollectionFinish)               \
+  fn(ObjectFree,              ArtJvmtiEvent::kObjectFree)                            \
+  fn(VMObjectAlloc,           ArtJvmtiEvent::kVmObjectAlloc)
+
+template <ArtJvmtiEvent kEvent>
+struct EventFnType {
+};
+
+#define EVENT_FN_TYPE(name, enum_name)               \
+template <>                                          \
+struct EventFnType<enum_name> {                      \
+  using type = decltype(jvmtiEventCallbacks().name); \
+};
+
+FORALL_EVENT_TYPES(EVENT_FN_TYPE)
+
+#undef EVENT_FN_TYPE
+
+template <ArtJvmtiEvent kEvent>
+ALWAYS_INLINE inline typename EventFnType<kEvent>::type GetCallback(ArtJvmTiEnv* env);
+
+#define GET_CALLBACK(name, enum_name)                                     \
+template <>                                                               \
+ALWAYS_INLINE inline EventFnType<enum_name>::type GetCallback<enum_name>( \
+    ArtJvmTiEnv* env) {                                                   \
+  if (env->event_callbacks == nullptr) {                                  \
+    return nullptr;                                                       \
+  }                                                                       \
+  return env->event_callbacks->name;                                      \
+}
+
+FORALL_EVENT_TYPES(GET_CALLBACK)
+
+#undef GET_CALLBACK
+
+#undef FORALL_EVENT_TYPES
+
+}  // namespace impl
+
+// C++ does not allow partial template function specialization. The dispatch for our separated
+// ClassFileLoadHook event types is the same, so use this helper for code deduplication.
+// TODO Locking of some type!
+template <ArtJvmtiEvent kEvent>
+inline void EventHandler::DispatchClassFileLoadHookEvent(art::Thread* thread,
+                                                         JNIEnv* jnienv,
+                                                         jclass class_being_redefined,
+                                                         jobject loader,
+                                                         const char* name,
+                                                         jobject protection_domain,
+                                                         jint class_data_len,
+                                                         const unsigned char* class_data,
+                                                         jint* new_class_data_len,
+                                                         unsigned char** new_class_data) const {
+  static_assert(kEvent == ArtJvmtiEvent::kClassFileLoadHookRetransformable ||
+                kEvent == ArtJvmtiEvent::kClassFileLoadHookNonRetransformable, "Unsupported event");
+  DCHECK(*new_class_data == nullptr);
+  jint current_len = class_data_len;
+  unsigned char* current_class_data = const_cast<unsigned char*>(class_data);
+  ArtJvmTiEnv* last_env = nullptr;
+  for (ArtJvmTiEnv* env : envs) {
+    if (env == nullptr) {
+      continue;
+    }
+    if (ShouldDispatch<kEvent>(env, thread)) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      jint new_len = 0;
+      unsigned char* new_data = nullptr;
+      auto callback = impl::GetCallback<kEvent>(env);
+      callback(env,
+               jnienv,
+               class_being_redefined,
+               loader,
+               name,
+               protection_domain,
+               current_len,
+               current_class_data,
+               &new_len,
+               &new_data);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+      if (new_data != nullptr && new_data != current_class_data) {
+        // Destroy the data the last transformer made. We skip this if the previous state was the
+        // initial one since we don't know here which jvmtiEnv allocated it.
+        // NB Currently this doesn't matter since all allocations just go to malloc but in the
+        // future we might have jvmtiEnv's keep track of their allocations for leak-checking.
+        if (last_env != nullptr) {
+          last_env->Deallocate(current_class_data);
+        }
+        last_env = env;
+        current_class_data = new_data;
+        current_len = new_len;
+      }
+    }
+  }
+  if (last_env != nullptr) {
+    *new_class_data_len = current_len;
+    *new_class_data = current_class_data;
+  }
+}
+
+// Our goal for DispatchEvent: Do not allow implicit type conversion. Types of ...args must match
+// exactly the argument types of the corresponding Jvmti kEvent function pointer.
+
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::DispatchEvent(art::Thread* thread, Args... args) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr) {
+      DispatchEvent<kEvent, Args...>(env, thread, args...);
+    }
+  }
+}
+
+// Events with JNIEnvs need to stash pending exceptions since they can cause new ones to be thrown.
+// In accordance with the JVMTI specification we allow exceptions originating from events to
+// overwrite the current exception, including exceptions originating from earlier events.
+// TODO It would be nice to add the overwritten exceptions to the suppressed exceptions list of the
+// newest exception.
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      DispatchEvent<kEvent, JNIEnv*, Args...>(env, thread, jnienv, args...);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
+template <ArtJvmtiEvent kEvent, typename ...Args>
+inline void EventHandler::DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const {
+  using FnType = void(jvmtiEnv*, Args...);
+  if (ShouldDispatch<kEvent>(env, thread)) {
+    FnType* callback = impl::GetCallback<kEvent>(env);
+    if (callback != nullptr) {
+      (*callback)(env, args...);
+    }
+  }
+}
+
+// Need to give custom specializations for Breakpoint since it needs to filter out which particular
+// methods/dex_pcs agents get notified on.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kBreakpoint>(art::Thread* thread,
+                                                                    JNIEnv* jnienv,
+                                                                    jthread jni_thread,
+                                                                    jmethodID jmethod,
+                                                                    jlocation location) const {
+  art::ArtMethod* method = art::jni::DecodeArtMethod(jmethod);
+  for (ArtJvmTiEnv* env : envs) {
+    // Search for a breakpoint on this particular method and location.
+    if (env != nullptr &&
+        ShouldDispatch<ArtJvmtiEvent::kBreakpoint>(env, thread) &&
+        env->breakpoints.find({method, location}) != env->breakpoints.end()) {
+      // We temporarily clear any pending exceptions so the event can call back into java code.
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kBreakpoint>(env);
+      (*callback)(env, jnienv, jni_thread, jmethod, location);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
+// Need to give custom specializations for FieldAccess and FieldModification since they need to
+// filter out which particular fields agents want to get notified on.
+// TODO The spec allows us to do shortcuts like only allow one agent to ever set these watches. This
+// could make the system more performant.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldModification>(art::Thread* thread,
+                                                                           JNIEnv* jnienv,
+                                                                           jthread jni_thread,
+                                                                           jmethodID method,
+                                                                           jlocation location,
+                                                                           jclass field_klass,
+                                                                           jobject object,
+                                                                           jfieldID field,
+                                                                           char type_char,
+                                                                           jvalue val) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr &&
+        ShouldDispatch<ArtJvmtiEvent::kFieldModification>(env, thread) &&
+        env->modify_watched_fields.find(
+            art::jni::DecodeArtField(field)) != env->modify_watched_fields.end()) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldModification>(env);
+      (*callback)(env,
+                  jnienv,
+                  jni_thread,
+                  method,
+                  location,
+                  field_klass,
+                  object,
+                  field,
+                  type_char,
+                  val);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kFieldAccess>(art::Thread* thread,
+                                                                     JNIEnv* jnienv,
+                                                                     jthread jni_thread,
+                                                                     jmethodID method,
+                                                                     jlocation location,
+                                                                     jclass field_klass,
+                                                                     jobject object,
+                                                                     jfieldID field) const {
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr &&
+        ShouldDispatch<ArtJvmtiEvent::kFieldAccess>(env, thread) &&
+        env->access_watched_fields.find(
+            art::jni::DecodeArtField(field)) != env->access_watched_fields.end()) {
+      ScopedLocalRef<jthrowable> thr(jnienv, jnienv->ExceptionOccurred());
+      jnienv->ExceptionClear();
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kFieldAccess>(env);
+      (*callback)(env, jnienv, jni_thread, method, location, field_klass, object, field);
+      if (thr.get() != nullptr && !jnienv->ExceptionCheck()) {
+        jnienv->Throw(thr.get());
+      }
+    }
+  }
+}
+
+// Need to give a custom specialization for NativeMethodBind since it has to deal with an out
+// variable.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(art::Thread* thread,
+                                                                          JNIEnv* jnienv,
+                                                                          jthread jni_thread,
+                                                                          jmethodID method,
+                                                                          void* cur_method,
+                                                                          void** new_method) const {
+  *new_method = cur_method;
+  for (ArtJvmTiEnv* env : envs) {
+    if (env != nullptr && ShouldDispatch<ArtJvmtiEvent::kNativeMethodBind>(env, thread)) {
+      auto callback = impl::GetCallback<ArtJvmtiEvent::kNativeMethodBind>(env);
+      (*callback)(env, jnienv, jni_thread, method, cur_method, new_method);
+      if (*new_method != nullptr) {
+        cur_method = *new_method;
+      }
+    }
+  }
+}
+
+// C++ does not allow partial template function specialization. The dispatch for our separated
+// ClassFileLoadHook event types is the same, and in the DispatchClassFileLoadHookEvent helper.
+// The following two DispatchEvent specializations dispatch to it.
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+    art::Thread* thread,
+    JNIEnv* jnienv,
+    jclass class_being_redefined,
+    jobject loader,
+    const char* name,
+    jobject protection_domain,
+    jint class_data_len,
+    const unsigned char* class_data,
+    jint* new_class_data_len,
+    unsigned char** new_class_data) const {
+  return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+      thread,
+      jnienv,
+      class_being_redefined,
+      loader,
+      name,
+      protection_domain,
+      class_data_len,
+      class_data,
+      new_class_data_len,
+      new_class_data);
+}
+template <>
+inline void EventHandler::DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+    art::Thread* thread,
+    JNIEnv* jnienv,
+    jclass class_being_redefined,
+    jobject loader,
+    const char* name,
+    jobject protection_domain,
+    jint class_data_len,
+    const unsigned char* class_data,
+    jint* new_class_data_len,
+    unsigned char** new_class_data) const {
+  return DispatchClassFileLoadHookEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+      thread,
+      jnienv,
+      class_being_redefined,
+      loader,
+      name,
+      protection_domain,
+      class_data_len,
+      class_data,
+      new_class_data_len,
+      new_class_data);
+}
+
+template <ArtJvmtiEvent kEvent>
+inline bool EventHandler::ShouldDispatch(ArtJvmTiEnv* env,
+                                         art::Thread* thread) {
+  bool dispatch = env->event_masks.global_event_mask.Test(kEvent);
+
+  if (!dispatch && thread != nullptr && env->event_masks.unioned_thread_event_mask.Test(kEvent)) {
+    EventMask* mask = env->event_masks.GetEventMaskOrNull(thread);
+    dispatch = mask != nullptr && mask->Test(kEvent);
+  }
+  return dispatch;
+}
+
+inline void EventHandler::RecalculateGlobalEventMask(ArtJvmtiEvent event) {
+  bool union_value = false;
+  for (const ArtJvmTiEnv* stored_env : envs) {
+    if (stored_env == nullptr) {
+      continue;
+    }
+    union_value |= stored_env->event_masks.global_event_mask.Test(event);
+    union_value |= stored_env->event_masks.unioned_thread_event_mask.Test(event);
+    if (union_value) {
+      break;
+    }
+  }
+  global_mask.Set(event, union_value);
+}
+
+inline bool EventHandler::NeedsEventUpdate(ArtJvmTiEnv* env,
+                                           const jvmtiCapabilities& caps,
+                                           bool added) {
+  ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
+                              : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
+  return (added && caps.can_access_local_variables == 1) ||
+      (caps.can_retransform_classes == 1 &&
+       IsEventEnabledAnywhere(event) &&
+       env->event_masks.IsEnabledAnywhere(event));
+}
+
+inline void EventHandler::HandleChangedCapabilities(ArtJvmTiEnv* env,
+                                                    const jvmtiCapabilities& caps,
+                                                    bool added) {
+  if (UNLIKELY(NeedsEventUpdate(env, caps, added))) {
+    env->event_masks.HandleChangedCapabilities(caps, added);
+    if (caps.can_retransform_classes == 1) {
+      RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookRetransformable);
+      RecalculateGlobalEventMask(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
+    }
+    if (added && caps.can_access_local_variables == 1) {
+      HandleLocalAccessCapabilityAdded();
+    }
+  }
+}
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_EVENTS_INL_H_
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
new file mode 100644
index 0000000..2944a45
--- /dev/null
+++ b/openjdkjvmti/events.cc
@@ -0,0 +1,791 @@
+/* Copyright (C) 2016 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 "events-inl.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/logging.h"
+#include "gc/allocation_listener.h"
+#include "gc/gc_pause_listener.h"
+#include "gc/heap.h"
+#include "gc/scoped_gc_critical_section.h"
+#include "handle_scope-inl.h"
+#include "instrumentation.h"
+#include "jni_env_ext-inl.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+bool EventMasks::IsEnabledAnywhere(ArtJvmtiEvent event) {
+  return global_event_mask.Test(event) || unioned_thread_event_mask.Test(event);
+}
+
+EventMask& EventMasks::GetEventMask(art::Thread* thread) {
+  if (thread == nullptr) {
+    return global_event_mask;
+  }
+
+  for (auto& pair : thread_event_masks) {
+    const UniqueThread& unique_thread = pair.first;
+    if (unique_thread.first == thread &&
+        unique_thread.second == static_cast<uint32_t>(thread->GetTid())) {
+      return pair.second;
+    }
+  }
+
+  // TODO: Remove old UniqueThread with the same pointer, if exists.
+
+  thread_event_masks.emplace_back(UniqueThread(thread, thread->GetTid()), EventMask());
+  return thread_event_masks.back().second;
+}
+
+EventMask* EventMasks::GetEventMaskOrNull(art::Thread* thread) {
+  if (thread == nullptr) {
+    return &global_event_mask;
+  }
+
+  for (auto& pair : thread_event_masks) {
+    const UniqueThread& unique_thread = pair.first;
+    if (unique_thread.first == thread &&
+        unique_thread.second == static_cast<uint32_t>(thread->GetTid())) {
+      return &pair.second;
+    }
+  }
+
+  return nullptr;
+}
+
+
+void EventMasks::EnableEvent(art::Thread* thread, ArtJvmtiEvent event) {
+  DCHECK(EventMask::EventIsInRange(event));
+  GetEventMask(thread).Set(event);
+  if (thread != nullptr) {
+    unioned_thread_event_mask.Set(event, true);
+  }
+}
+
+void EventMasks::DisableEvent(art::Thread* thread, ArtJvmtiEvent event) {
+  DCHECK(EventMask::EventIsInRange(event));
+  GetEventMask(thread).Set(event, false);
+  if (thread != nullptr) {
+    // Regenerate union for the event.
+    bool union_value = false;
+    for (auto& pair : thread_event_masks) {
+      union_value |= pair.second.Test(event);
+      if (union_value) {
+        break;
+      }
+    }
+    unioned_thread_event_mask.Set(event, union_value);
+  }
+}
+
+void EventMasks::HandleChangedCapabilities(const jvmtiCapabilities& caps, bool caps_added) {
+  if (UNLIKELY(caps.can_retransform_classes == 1)) {
+    // If we are giving this env the retransform classes cap we need to switch all events of
+    // NonTransformable to Transformable and vice versa.
+    ArtJvmtiEvent to_remove = caps_added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
+                                         : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
+    ArtJvmtiEvent to_add = caps_added ? ArtJvmtiEvent::kClassFileLoadHookRetransformable
+                                      : ArtJvmtiEvent::kClassFileLoadHookNonRetransformable;
+    if (global_event_mask.Test(to_remove)) {
+      CHECK(!global_event_mask.Test(to_add));
+      global_event_mask.Set(to_remove, false);
+      global_event_mask.Set(to_add, true);
+    }
+
+    if (unioned_thread_event_mask.Test(to_remove)) {
+      CHECK(!unioned_thread_event_mask.Test(to_add));
+      unioned_thread_event_mask.Set(to_remove, false);
+      unioned_thread_event_mask.Set(to_add, true);
+    }
+    for (auto thread_mask : thread_event_masks) {
+      if (thread_mask.second.Test(to_remove)) {
+        CHECK(!thread_mask.second.Test(to_add));
+        thread_mask.second.Set(to_remove, false);
+        thread_mask.second.Set(to_add, true);
+      }
+    }
+  }
+}
+
+void EventHandler::RegisterArtJvmTiEnv(ArtJvmTiEnv* env) {
+  // Since we never shrink this array we might as well try to fill gaps.
+  auto it = std::find(envs.begin(), envs.end(), nullptr);
+  if (it != envs.end()) {
+    *it = env;
+  } else {
+    envs.push_back(env);
+  }
+}
+
+void EventHandler::RemoveArtJvmTiEnv(ArtJvmTiEnv* env) {
+  // Since we might be currently iterating over the envs list we cannot actually erase elements.
+  // Instead we will simply replace them with 'nullptr' and skip them manually.
+  auto it = std::find(envs.begin(), envs.end(), env);
+  if (it != envs.end()) {
+    *it = nullptr;
+    for (size_t i = static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal);
+         i <= static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal);
+         ++i) {
+      RecalculateGlobalEventMask(static_cast<ArtJvmtiEvent>(i));
+    }
+  }
+}
+
+static bool IsThreadControllable(ArtJvmtiEvent event) {
+  switch (event) {
+    case ArtJvmtiEvent::kVmInit:
+    case ArtJvmtiEvent::kVmStart:
+    case ArtJvmtiEvent::kVmDeath:
+    case ArtJvmtiEvent::kThreadStart:
+    case ArtJvmtiEvent::kCompiledMethodLoad:
+    case ArtJvmtiEvent::kCompiledMethodUnload:
+    case ArtJvmtiEvent::kDynamicCodeGenerated:
+    case ArtJvmtiEvent::kDataDumpRequest:
+      return false;
+
+    default:
+      return true;
+  }
+}
+
+class JvmtiAllocationListener : public art::gc::AllocationListener {
+ public:
+  explicit JvmtiAllocationListener(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_) {
+    DCHECK_EQ(self, art::Thread::Current());
+
+    if (handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kVmObjectAlloc)) {
+      art::StackHandleScope<1> hs(self);
+      auto h = hs.NewHandleWrapper(obj);
+      // jvmtiEventVMObjectAlloc parameters:
+      //      jvmtiEnv *jvmti_env,
+      //      JNIEnv* jni_env,
+      //      jthread thread,
+      //      jobject object,
+      //      jclass object_klass,
+      //      jlong size
+      art::JNIEnvExt* jni_env = self->GetJniEnv();
+
+      jthread thread_peer;
+      if (self->IsStillStarting()) {
+        thread_peer = nullptr;
+      } else {
+        thread_peer = jni_env->AddLocalReference<jthread>(self->GetPeer());
+      }
+
+      ScopedLocalRef<jthread> thread(jni_env, thread_peer);
+      ScopedLocalRef<jobject> object(
+          jni_env, jni_env->AddLocalReference<jobject>(*obj));
+      ScopedLocalRef<jclass> klass(
+          jni_env, jni_env->AddLocalReference<jclass>(obj->Ptr()->GetClass()));
+
+      handler_->DispatchEvent<ArtJvmtiEvent::kVmObjectAlloc>(self,
+                                                             reinterpret_cast<JNIEnv*>(jni_env),
+                                                             thread.get(),
+                                                             object.get(),
+                                                             klass.get(),
+                                                             static_cast<jlong>(byte_count));
+    }
+  }
+
+ private:
+  EventHandler* handler_;
+};
+
+static void SetupObjectAllocationTracking(art::gc::AllocationListener* listener, 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);
+  } else {
+    art::Runtime::Current()->GetHeap()->RemoveAllocationListener();
+  }
+}
+
+// Report GC pauses (see spec) as GARBAGE_COLLECTION_START and GARBAGE_COLLECTION_END.
+class JvmtiGcPauseListener : public art::gc::GcPauseListener {
+ public:
+  explicit JvmtiGcPauseListener(EventHandler* handler)
+      : handler_(handler),
+        start_enabled_(false),
+        finish_enabled_(false) {}
+
+  void StartPause() OVERRIDE {
+    handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionStart>(nullptr);
+  }
+
+  void EndPause() OVERRIDE {
+    handler_->DispatchEvent<ArtJvmtiEvent::kGarbageCollectionFinish>(nullptr);
+  }
+
+  bool IsEnabled() {
+    return start_enabled_ || finish_enabled_;
+  }
+
+  void SetStartEnabled(bool e) {
+    start_enabled_ = e;
+  }
+
+  void SetFinishEnabled(bool e) {
+    finish_enabled_ = e;
+  }
+
+ private:
+  EventHandler* handler_;
+  bool start_enabled_;
+  bool finish_enabled_;
+};
+
+static void SetupGcPauseTracking(JvmtiGcPauseListener* listener, ArtJvmtiEvent event, bool enable) {
+  bool old_state = listener->IsEnabled();
+
+  if (event == ArtJvmtiEvent::kGarbageCollectionStart) {
+    listener->SetStartEnabled(enable);
+  } else {
+    listener->SetFinishEnabled(enable);
+  }
+
+  bool new_state = listener->IsEnabled();
+
+  if (old_state != new_state) {
+    if (new_state) {
+      art::Runtime::Current()->GetHeap()->SetGcPauseListener(listener);
+    } else {
+      art::Runtime::Current()->GetHeap()->RemoveGcPauseListener();
+    }
+  }
+}
+
+template<typename Type>
+static Type AddLocalRef(art::JNIEnvExt* e, art::mirror::Object* obj)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  return (obj == nullptr) ? nullptr : e->AddLocalReference<Type>(obj);
+}
+
+class JvmtiMethodTraceListener FINAL : public art::instrumentation::InstrumentationListener {
+ public:
+  explicit JvmtiMethodTraceListener(EventHandler* handler) : event_handler_(handler) {}
+
+  template<ArtJvmtiEvent kEvent, typename ...Args>
+  void RunEventCallback(art::Thread* self, art::JNIEnvExt* jnienv, Args... args)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    ScopedLocalRef<jthread> thread_jni(jnienv, AddLocalRef<jthread>(jnienv, self->GetPeer()));
+    // Just give the event a good sized JNI frame. 100 should be fine.
+    jnienv->PushFrame(100);
+    {
+      // Need to do trampoline! :(
+      art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+      event_handler_->DispatchEvent<kEvent>(self,
+                                            static_cast<JNIEnv*>(jnienv),
+                                            thread_jni.get(),
+                                            args...);
+    }
+    jnienv->PopFrame();
+  }
+
+  // Call-back for when a method is entered.
+  void MethodEntered(art::Thread* self,
+                     art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                     art::ArtMethod* method,
+                     uint32_t dex_pc ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodEntry)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      RunEventCallback<ArtJvmtiEvent::kMethodEntry>(self,
+                                                    jnienv,
+                                                    art::jni::EncodeArtMethod(method));
+    }
+  }
+
+  // Callback for when a method is exited with a reference return value.
+  void MethodExited(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED,
+                    art::Handle<art::mirror::Object> return_value)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) {
+      DCHECK_EQ(method->GetReturnTypePrimitive(), art::Primitive::kPrimNot)
+          << method->PrettyMethod();
+      DCHECK(!self->IsExceptionPending());
+      jvalue val;
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      ScopedLocalRef<jobject> return_jobj(jnienv, AddLocalRef<jobject>(jnienv, return_value.Get()));
+      val.l = return_jobj.get();
+      RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          /*was_popped_by_exception*/ static_cast<jboolean>(JNI_FALSE),
+          val);
+    }
+  }
+
+  // Call-back for when a method is exited.
+  void MethodExited(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED,
+                    const art::JValue& return_value)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) {
+      DCHECK_NE(method->GetReturnTypePrimitive(), art::Primitive::kPrimNot)
+          << method->PrettyMethod();
+      DCHECK(!self->IsExceptionPending());
+      jvalue val;
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      // 64bit integer is the largest value in the union so we should be fine simply copying it into
+      // the union.
+      val.j = return_value.GetJ();
+      RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          /*was_popped_by_exception*/ static_cast<jboolean>(JNI_FALSE),
+          val);
+    }
+  }
+
+  // Call-back for when a method is popped due to an exception throw. A method will either cause a
+  // MethodExited call-back or a MethodUnwind call-back when its activation is removed.
+  void MethodUnwind(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (!method->IsRuntimeMethod() &&
+        event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kMethodExit)) {
+      jvalue val;
+      // Just set this to 0xffffffffffffffff so it's not uninitialized.
+      val.j = static_cast<jlong>(-1);
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      art::StackHandleScope<1> hs(self);
+      art::Handle<art::mirror::Throwable> old_exception(hs.NewHandle(self->GetException()));
+      CHECK(!old_exception.IsNull());
+      self->ClearException();
+      RunEventCallback<ArtJvmtiEvent::kMethodExit>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          /*was_popped_by_exception*/ static_cast<jboolean>(JNI_TRUE),
+          val);
+      // Match RI behavior of just throwing away original exception if a new one is thrown.
+      if (LIKELY(!self->IsExceptionPending())) {
+        self->SetException(old_exception.Get());
+      }
+    }
+  }
+
+  // Call-back for when the dex pc moves in a method.
+  void DexPcMoved(art::Thread* self,
+                  art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                  art::ArtMethod* method,
+                  uint32_t new_dex_pc)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    DCHECK(!method->IsRuntimeMethod());
+    // Default methods might be copied to multiple classes. We need to get the canonical version of
+    // this method so that we can check for breakpoints correctly.
+    // TODO We should maybe do this on other events to ensure that we are consistent WRT default
+    // methods. This could interact with obsolete methods if we ever let interface redefinition
+    // happen though.
+    method = method->GetCanonicalMethod();
+    art::JNIEnvExt* jnienv = self->GetJniEnv();
+    jmethodID jmethod = art::jni::EncodeArtMethod(method);
+    jlocation location = static_cast<jlocation>(new_dex_pc);
+    // Step event is reported first according to the spec.
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kSingleStep)) {
+      RunEventCallback<ArtJvmtiEvent::kSingleStep>(self, jnienv, jmethod, location);
+    }
+    // Next we do the Breakpoint events. The Dispatch code will filter the individual
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kBreakpoint)) {
+      RunEventCallback<ArtJvmtiEvent::kBreakpoint>(self, jnienv, jmethod, location);
+    }
+  }
+
+  // Call-back for when we read from a field.
+  void FieldRead(art::Thread* self,
+                 art::Handle<art::mirror::Object> this_object,
+                 art::ArtMethod* method,
+                 uint32_t dex_pc,
+                 art::ArtField* field)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldAccess)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      // DCHECK(!self->IsExceptionPending());
+      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+      ScopedLocalRef<jobject> fklass(jnienv,
+                                     AddLocalRef<jobject>(jnienv,
+                                                          field->GetDeclaringClass().Ptr()));
+      RunEventCallback<ArtJvmtiEvent::kFieldAccess>(self,
+                                                    jnienv,
+                                                    art::jni::EncodeArtMethod(method),
+                                                    static_cast<jlocation>(dex_pc),
+                                                    static_cast<jclass>(fklass.get()),
+                                                    this_ref.get(),
+                                                    art::jni::EncodeArtField(field));
+    }
+  }
+
+  void FieldWritten(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc,
+                    art::ArtField* field,
+                    art::Handle<art::mirror::Object> new_val)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      // DCHECK(!self->IsExceptionPending());
+      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+      ScopedLocalRef<jobject> fklass(jnienv,
+                                     AddLocalRef<jobject>(jnienv,
+                                                          field->GetDeclaringClass().Ptr()));
+      ScopedLocalRef<jobject> fval(jnienv, AddLocalRef<jobject>(jnienv, new_val.Get()));
+      jvalue val;
+      val.l = fval.get();
+      RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          static_cast<jlocation>(dex_pc),
+          static_cast<jclass>(fklass.get()),
+          field->IsStatic() ? nullptr :  this_ref.get(),
+          art::jni::EncodeArtField(field),
+          'L',  // type_char
+          val);
+    }
+  }
+
+  // Call-back for when we write into a field.
+  void FieldWritten(art::Thread* self,
+                    art::Handle<art::mirror::Object> this_object,
+                    art::ArtMethod* method,
+                    uint32_t dex_pc,
+                    art::ArtField* field,
+                    const art::JValue& field_value)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    if (event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kFieldModification)) {
+      art::JNIEnvExt* jnienv = self->GetJniEnv();
+      DCHECK(!self->IsExceptionPending());
+      ScopedLocalRef<jobject> this_ref(jnienv, AddLocalRef<jobject>(jnienv, this_object.Get()));
+      ScopedLocalRef<jobject> fklass(jnienv,
+                                     AddLocalRef<jobject>(jnienv,
+                                                          field->GetDeclaringClass().Ptr()));
+      char type_char = art::Primitive::Descriptor(field->GetTypeAsPrimitiveType())[0];
+      jvalue val;
+      // 64bit integer is the largest value in the union so we should be fine simply copying it into
+      // the union.
+      val.j = field_value.GetJ();
+      RunEventCallback<ArtJvmtiEvent::kFieldModification>(
+          self,
+          jnienv,
+          art::jni::EncodeArtMethod(method),
+          static_cast<jlocation>(dex_pc),
+          static_cast<jclass>(fklass.get()),
+          field->IsStatic() ? nullptr :  this_ref.get(),  // nb static field modification get given
+                                                          // the class as this_object for some
+                                                          // reason.
+          art::jni::EncodeArtField(field),
+          type_char,
+          val);
+    }
+  }
+
+  // Call-back when an exception is caught.
+  void ExceptionCaught(art::Thread* self ATTRIBUTE_UNUSED,
+                       art::Handle<art::mirror::Throwable> exception_object ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back for when we execute a branch.
+  void Branch(art::Thread* self ATTRIBUTE_UNUSED,
+              art::ArtMethod* method ATTRIBUTE_UNUSED,
+              uint32_t dex_pc ATTRIBUTE_UNUSED,
+              int32_t dex_pc_offset ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+  // Call-back for when we get an invokevirtual or an invokeinterface.
+  void InvokeVirtualOrInterface(art::Thread* self ATTRIBUTE_UNUSED,
+                                art::Handle<art::mirror::Object> this_object ATTRIBUTE_UNUSED,
+                                art::ArtMethod* caller ATTRIBUTE_UNUSED,
+                                uint32_t dex_pc ATTRIBUTE_UNUSED,
+                                art::ArtMethod* callee ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    return;
+  }
+
+ private:
+  EventHandler* const event_handler_;
+};
+
+static uint32_t GetInstrumentationEventsFor(ArtJvmtiEvent event) {
+  switch (event) {
+    case ArtJvmtiEvent::kMethodEntry:
+      return art::instrumentation::Instrumentation::kMethodEntered;
+    case ArtJvmtiEvent::kMethodExit:
+      return art::instrumentation::Instrumentation::kMethodExited |
+             art::instrumentation::Instrumentation::kMethodUnwind;
+    case ArtJvmtiEvent::kFieldModification:
+      return art::instrumentation::Instrumentation::kFieldWritten;
+    case ArtJvmtiEvent::kFieldAccess:
+      return art::instrumentation::Instrumentation::kFieldRead;
+    case ArtJvmtiEvent::kBreakpoint:
+    case ArtJvmtiEvent::kSingleStep:
+      return art::instrumentation::Instrumentation::kDexPcMoved;
+    default:
+      LOG(FATAL) << "Unknown event ";
+      return 0;
+  }
+}
+
+static void SetupTraceListener(JvmtiMethodTraceListener* listener,
+                               ArtJvmtiEvent event,
+                               bool enable) {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
+  uint32_t new_events = GetInstrumentationEventsFor(event);
+  art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+  art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("jvmti method tracing installation");
+  if (enable) {
+    // TODO Depending on the features being used we should be able to avoid deoptimizing everything
+    // like we do here.
+    if (!instr->AreAllMethodsDeoptimized()) {
+      instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true);
+    }
+    instr->AddListener(listener, new_events);
+  } else {
+    instr->RemoveListener(listener, new_events);
+  }
+}
+
+void EventHandler::HandleLocalAccessCapabilityAdded() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
+  art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
+  art::gc::ScopedGCCriticalSection gcs(art::Thread::Current(),
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("Deoptimize everything for local variable access", true);
+  // TODO This should be disabled when there are no environments using it.
+  if (!instr->CanDeoptimize()) {
+    instr->EnableDeoptimization();
+  }
+  // TODO We should be able to support can_access_local_variables without this.
+  instr->DeoptimizeEverything("jvmti-local-variable-access");
+}
+
+// Handle special work for the given event type, if necessary.
+void EventHandler::HandleEventType(ArtJvmtiEvent event, bool enable) {
+  switch (event) {
+    case ArtJvmtiEvent::kVmObjectAlloc:
+      SetupObjectAllocationTracking(alloc_listener_.get(), enable);
+      return;
+
+    case ArtJvmtiEvent::kGarbageCollectionStart:
+    case ArtJvmtiEvent::kGarbageCollectionFinish:
+      SetupGcPauseTracking(gc_pause_listener_.get(), event, enable);
+      return;
+
+    case ArtJvmtiEvent::kBreakpoint:
+    case ArtJvmtiEvent::kSingleStep: {
+      ArtJvmtiEvent other = (event == ArtJvmtiEvent::kBreakpoint) ? ArtJvmtiEvent::kSingleStep
+                                                                  : ArtJvmtiEvent::kBreakpoint;
+      // We only need to do anything if there isn't already a listener installed/held-on by the
+      // other jvmti event that uses DexPcMoved.
+      if (!IsEventEnabledAnywhere(other)) {
+        SetupTraceListener(method_trace_listener_.get(), event, enable);
+      }
+      return;
+    }
+    case ArtJvmtiEvent::kMethodEntry:
+    case ArtJvmtiEvent::kMethodExit:
+    case ArtJvmtiEvent::kFieldAccess:
+    case ArtJvmtiEvent::kFieldModification:
+      SetupTraceListener(method_trace_listener_.get(), event, enable);
+      return;
+
+    default:
+      break;
+  }
+}
+
+// Checks to see if the env has the capabilities associated with the given event.
+static bool HasAssociatedCapability(ArtJvmTiEnv* env,
+                                    ArtJvmtiEvent event) {
+  jvmtiCapabilities caps = env->capabilities;
+  switch (event) {
+    case ArtJvmtiEvent::kBreakpoint:
+      return caps.can_generate_breakpoint_events == 1;
+
+    case ArtJvmtiEvent::kCompiledMethodLoad:
+    case ArtJvmtiEvent::kCompiledMethodUnload:
+      return caps.can_generate_compiled_method_load_events == 1;
+
+    case ArtJvmtiEvent::kException:
+    case ArtJvmtiEvent::kExceptionCatch:
+      return caps.can_generate_exception_events == 1;
+
+    case ArtJvmtiEvent::kFieldAccess:
+      return caps.can_generate_field_access_events == 1;
+
+    case ArtJvmtiEvent::kFieldModification:
+      return caps.can_generate_field_modification_events == 1;
+
+    case ArtJvmtiEvent::kFramePop:
+      return caps.can_generate_frame_pop_events == 1;
+
+    case ArtJvmtiEvent::kGarbageCollectionStart:
+    case ArtJvmtiEvent::kGarbageCollectionFinish:
+      return caps.can_generate_garbage_collection_events == 1;
+
+    case ArtJvmtiEvent::kMethodEntry:
+      return caps.can_generate_method_entry_events == 1;
+
+    case ArtJvmtiEvent::kMethodExit:
+      return caps.can_generate_method_exit_events == 1;
+
+    case ArtJvmtiEvent::kMonitorContendedEnter:
+    case ArtJvmtiEvent::kMonitorContendedEntered:
+    case ArtJvmtiEvent::kMonitorWait:
+    case ArtJvmtiEvent::kMonitorWaited:
+      return caps.can_generate_monitor_events == 1;
+
+    case ArtJvmtiEvent::kNativeMethodBind:
+      return caps.can_generate_native_method_bind_events == 1;
+
+    case ArtJvmtiEvent::kObjectFree:
+      return caps.can_generate_object_free_events == 1;
+
+    case ArtJvmtiEvent::kSingleStep:
+      return caps.can_generate_single_step_events == 1;
+
+    case ArtJvmtiEvent::kVmObjectAlloc:
+      return caps.can_generate_vm_object_alloc_events == 1;
+
+    default:
+      return true;
+  }
+}
+
+jvmtiError EventHandler::SetEvent(ArtJvmTiEnv* env,
+                                  art::Thread* thread,
+                                  ArtJvmtiEvent event,
+                                  jvmtiEventMode mode) {
+  if (thread != nullptr) {
+    art::ThreadState state = thread->GetState();
+    if (state == art::ThreadState::kStarting ||
+        state == art::ThreadState::kTerminated ||
+        thread->IsStillStarting()) {
+      return ERR(THREAD_NOT_ALIVE);
+    }
+    if (!IsThreadControllable(event)) {
+      return ERR(ILLEGAL_ARGUMENT);
+    }
+  }
+
+  if (mode != JVMTI_ENABLE && mode != JVMTI_DISABLE) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  if (!EventMask::EventIsInRange(event)) {
+    return ERR(INVALID_EVENT_TYPE);
+  }
+
+  if (!HasAssociatedCapability(env, event)) {
+    return ERR(MUST_POSSESS_CAPABILITY);
+  }
+
+  bool old_state = global_mask.Test(event);
+
+  if (mode == JVMTI_ENABLE) {
+    env->event_masks.EnableEvent(thread, event);
+    global_mask.Set(event);
+  } else {
+    DCHECK_EQ(mode, JVMTI_DISABLE);
+
+    env->event_masks.DisableEvent(thread, event);
+    RecalculateGlobalEventMask(event);
+  }
+
+  bool new_state = global_mask.Test(event);
+
+  // Handle any special work required for the event type.
+  if (new_state != old_state) {
+    HandleEventType(event, mode == JVMTI_ENABLE);
+  }
+
+  return ERR(NONE);
+}
+
+void EventHandler::Shutdown() {
+  // Need to remove the method_trace_listener_ if it's there.
+  art::Thread* self = art::Thread::Current();
+  art::gc::ScopedGCCriticalSection gcs(self,
+                                       art::gc::kGcCauseInstrumentation,
+                                       art::gc::kCollectorTypeInstrumentation);
+  art::ScopedSuspendAll ssa("jvmti method tracing uninstallation");
+  // Just remove every possible event.
+  art::Runtime::Current()->GetInstrumentation()->RemoveListener(method_trace_listener_.get(), ~0);
+}
+
+EventHandler::EventHandler() {
+  alloc_listener_.reset(new JvmtiAllocationListener(this));
+  gc_pause_listener_.reset(new JvmtiGcPauseListener(this));
+  method_trace_listener_.reset(new JvmtiMethodTraceListener(this));
+}
+
+EventHandler::~EventHandler() {
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
new file mode 100644
index 0000000..3d05fa1
--- /dev/null
+++ b/openjdkjvmti/events.h
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_OPENJDKJVMTI_EVENTS_H_
+#define ART_OPENJDKJVMTI_EVENTS_H_
+
+#include <bitset>
+#include <vector>
+
+#include "base/logging.h"
+#include "jvmti.h"
+#include "thread.h"
+
+namespace openjdkjvmti {
+
+struct ArtJvmTiEnv;
+class JvmtiAllocationListener;
+class JvmtiGcPauseListener;
+class JvmtiMethodTraceListener;
+
+// an enum for ArtEvents. This differs from the JVMTI events only in that we distinguish between
+// retransformation capable and incapable loading
+enum class ArtJvmtiEvent {
+    kMinEventTypeVal = JVMTI_MIN_EVENT_TYPE_VAL,
+    kVmInit = JVMTI_EVENT_VM_INIT,
+    kVmDeath = JVMTI_EVENT_VM_DEATH,
+    kThreadStart = JVMTI_EVENT_THREAD_START,
+    kThreadEnd = JVMTI_EVENT_THREAD_END,
+    kClassFileLoadHookNonRetransformable = JVMTI_EVENT_CLASS_FILE_LOAD_HOOK,
+    kClassLoad = JVMTI_EVENT_CLASS_LOAD,
+    kClassPrepare = JVMTI_EVENT_CLASS_PREPARE,
+    kVmStart = JVMTI_EVENT_VM_START,
+    kException = JVMTI_EVENT_EXCEPTION,
+    kExceptionCatch = JVMTI_EVENT_EXCEPTION_CATCH,
+    kSingleStep = JVMTI_EVENT_SINGLE_STEP,
+    kFramePop = JVMTI_EVENT_FRAME_POP,
+    kBreakpoint = JVMTI_EVENT_BREAKPOINT,
+    kFieldAccess = JVMTI_EVENT_FIELD_ACCESS,
+    kFieldModification = JVMTI_EVENT_FIELD_MODIFICATION,
+    kMethodEntry = JVMTI_EVENT_METHOD_ENTRY,
+    kMethodExit = JVMTI_EVENT_METHOD_EXIT,
+    kNativeMethodBind = JVMTI_EVENT_NATIVE_METHOD_BIND,
+    kCompiledMethodLoad = JVMTI_EVENT_COMPILED_METHOD_LOAD,
+    kCompiledMethodUnload = JVMTI_EVENT_COMPILED_METHOD_UNLOAD,
+    kDynamicCodeGenerated = JVMTI_EVENT_DYNAMIC_CODE_GENERATED,
+    kDataDumpRequest = JVMTI_EVENT_DATA_DUMP_REQUEST,
+    kMonitorWait = JVMTI_EVENT_MONITOR_WAIT,
+    kMonitorWaited = JVMTI_EVENT_MONITOR_WAITED,
+    kMonitorContendedEnter = JVMTI_EVENT_MONITOR_CONTENDED_ENTER,
+    kMonitorContendedEntered = JVMTI_EVENT_MONITOR_CONTENDED_ENTERED,
+    kResourceExhausted = JVMTI_EVENT_RESOURCE_EXHAUSTED,
+    kGarbageCollectionStart = JVMTI_EVENT_GARBAGE_COLLECTION_START,
+    kGarbageCollectionFinish = JVMTI_EVENT_GARBAGE_COLLECTION_FINISH,
+    kObjectFree = JVMTI_EVENT_OBJECT_FREE,
+    kVmObjectAlloc = JVMTI_EVENT_VM_OBJECT_ALLOC,
+    kClassFileLoadHookRetransformable = JVMTI_MAX_EVENT_TYPE_VAL + 1,
+    kMaxEventTypeVal = kClassFileLoadHookRetransformable,
+};
+
+// Convert a jvmtiEvent into a ArtJvmtiEvent
+ALWAYS_INLINE static inline ArtJvmtiEvent GetArtJvmtiEvent(ArtJvmTiEnv* env, jvmtiEvent e);
+
+static inline jvmtiEvent GetJvmtiEvent(ArtJvmtiEvent e) {
+  if (UNLIKELY(e == ArtJvmtiEvent::kClassFileLoadHookRetransformable)) {
+    return JVMTI_EVENT_CLASS_FILE_LOAD_HOOK;
+  } else {
+    return static_cast<jvmtiEvent>(e);
+  }
+}
+
+struct EventMask {
+  static constexpr size_t kEventsSize =
+      static_cast<size_t>(ArtJvmtiEvent::kMaxEventTypeVal) -
+      static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal) + 1;
+  std::bitset<kEventsSize> bit_set;
+
+  static bool EventIsInRange(ArtJvmtiEvent event) {
+    return event >= ArtJvmtiEvent::kMinEventTypeVal && event <= ArtJvmtiEvent::kMaxEventTypeVal;
+  }
+
+  void Set(ArtJvmtiEvent event, bool value = true) {
+    DCHECK(EventIsInRange(event));
+    bit_set.set(static_cast<size_t>(event) - static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal),
+                value);
+  }
+
+  bool Test(ArtJvmtiEvent event) const {
+    DCHECK(EventIsInRange(event));
+    return bit_set.test(
+        static_cast<size_t>(event) - static_cast<size_t>(ArtJvmtiEvent::kMinEventTypeVal));
+  }
+};
+
+struct EventMasks {
+  // The globally enabled events.
+  EventMask global_event_mask;
+
+  // The per-thread enabled events.
+
+  // It is not enough to store a Thread pointer, as these may be reused. Use the pointer and the
+  // thread id.
+  // Note: We could just use the tid like tracing does.
+  using UniqueThread = std::pair<art::Thread*, uint32_t>;
+  // TODO: Native thread objects are immovable, so we can use them as keys in an (unordered) map,
+  //       if necessary.
+  std::vector<std::pair<UniqueThread, EventMask>> thread_event_masks;
+
+  // A union of the per-thread events, for fast-pathing.
+  EventMask unioned_thread_event_mask;
+
+  EventMask& GetEventMask(art::Thread* thread);
+  EventMask* GetEventMaskOrNull(art::Thread* thread);
+  void EnableEvent(art::Thread* thread, ArtJvmtiEvent event);
+  void DisableEvent(art::Thread* thread, ArtJvmtiEvent event);
+  bool IsEnabledAnywhere(ArtJvmtiEvent event);
+  // Make any changes to event masks needed for the given capability changes. If caps_added is true
+  // then caps is all the newly set capabilities of the jvmtiEnv. If it is false then caps is the
+  // set of all capabilities that were removed from the jvmtiEnv.
+  void HandleChangedCapabilities(const jvmtiCapabilities& caps, bool caps_added);
+};
+
+// Helper class for event handling.
+class EventHandler {
+ public:
+  EventHandler();
+  ~EventHandler();
+
+  // do cleanup for the event handler.
+  void Shutdown();
+
+  // Register an env. It is assumed that this happens on env creation, that is, no events are
+  // enabled, yet.
+  void RegisterArtJvmTiEnv(ArtJvmTiEnv* env);
+
+  // Remove an env.
+  void RemoveArtJvmTiEnv(ArtJvmTiEnv* env);
+
+  bool IsEventEnabledAnywhere(ArtJvmtiEvent event) const {
+    if (!EventMask::EventIsInRange(event)) {
+      return false;
+    }
+    return global_mask.Test(event);
+  }
+
+  jvmtiError SetEvent(ArtJvmTiEnv* env,
+                      art::Thread* thread,
+                      ArtJvmtiEvent event,
+                      jvmtiEventMode mode);
+
+  // Dispatch event to all registered environments.
+  template <ArtJvmtiEvent kEvent, typename ...Args>
+  ALWAYS_INLINE
+  inline void DispatchEvent(art::Thread* thread, Args... args) const;
+  // Dispatch event to all registered environments stashing exceptions as needed. This works since
+  // JNIEnv* is always the second argument if it is passed to an event. Needed since C++ does not
+  // allow partial template function specialization.
+  template <ArtJvmtiEvent kEvent, typename ...Args>
+  ALWAYS_INLINE
+  void DispatchEvent(art::Thread* thread, JNIEnv* jnienv, Args... args) const;
+  // Dispatch event to the given environment, only.
+  template <ArtJvmtiEvent kEvent, typename ...Args>
+  ALWAYS_INLINE
+  inline void DispatchEvent(ArtJvmTiEnv* env, art::Thread* thread, Args... args) const;
+
+  // Tell the event handler capabilities were added/lost so it can adjust the sent events.If
+  // caps_added is true then caps is all the newly set capabilities of the jvmtiEnv. If it is false
+  // then caps is the set of all capabilities that were removed from the jvmtiEnv.
+  ALWAYS_INLINE
+  inline void HandleChangedCapabilities(ArtJvmTiEnv* env,
+                                        const jvmtiCapabilities& caps,
+                                        bool added);
+
+ private:
+  template <ArtJvmtiEvent kEvent>
+  ALWAYS_INLINE
+  static inline bool ShouldDispatch(ArtJvmTiEnv* env, art::Thread* thread);
+
+  ALWAYS_INLINE
+  inline bool NeedsEventUpdate(ArtJvmTiEnv* env,
+                               const jvmtiCapabilities& caps,
+                               bool added);
+
+  // Recalculates the event mask for the given event.
+  ALWAYS_INLINE
+  inline void RecalculateGlobalEventMask(ArtJvmtiEvent event);
+
+  template <ArtJvmtiEvent kEvent>
+  ALWAYS_INLINE inline void DispatchClassFileLoadHookEvent(art::Thread* thread,
+                                                           JNIEnv* jnienv,
+                                                           jclass class_being_redefined,
+                                                           jobject loader,
+                                                           const char* name,
+                                                           jobject protection_domain,
+                                                           jint class_data_len,
+                                                           const unsigned char* class_data,
+                                                           jint* new_class_data_len,
+                                                           unsigned char** new_class_data) const;
+
+  void HandleEventType(ArtJvmtiEvent event, bool enable);
+  void HandleLocalAccessCapabilityAdded();
+
+  // List of all JvmTiEnv objects that have been created, in their creation order.
+  // NB Some elements might be null representing envs that have been deleted. They should be skipped
+  // anytime this list is used.
+  std::vector<ArtJvmTiEnv*> envs;
+
+  // A union of all enabled events, anywhere.
+  EventMask global_mask;
+
+  std::unique_ptr<JvmtiAllocationListener> alloc_listener_;
+  std::unique_ptr<JvmtiGcPauseListener> gc_pause_listener_;
+  std::unique_ptr<JvmtiMethodTraceListener> method_trace_listener_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_EVENTS_H_
diff --git a/openjdkjvmti/fixed_up_dex_file.cc b/openjdkjvmti/fixed_up_dex_file.cc
new file mode 100644
index 0000000..5bfa5ca
--- /dev/null
+++ b/openjdkjvmti/fixed_up_dex_file.cc
@@ -0,0 +1,91 @@
+/* Copyright (C) 2017 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 "fixed_up_dex_file.h"
+#include "dex_file-inl.h"
+
+// Runtime includes.
+#include "dex_to_dex_decompiler.h"
+#include "oat_file.h"
+#include "vdex_file.h"
+
+namespace openjdkjvmti {
+
+static void RecomputeDexChecksum(art::DexFile* dex_file)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  reinterpret_cast<art::DexFile::Header*>(const_cast<uint8_t*>(dex_file->Begin()))->checksum_ =
+      dex_file->CalculateChecksum();
+}
+
+static void DoDexUnquicken(const art::DexFile& new_dex_file, const art::DexFile& original_dex_file)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  const art::OatDexFile* oat_dex = original_dex_file.GetOatDexFile();
+  if (oat_dex == nullptr) {
+    return;
+  }
+  const art::OatFile* oat_file = oat_dex->GetOatFile();
+  if (oat_file == nullptr) {
+    return;
+  }
+  const art::VdexFile* vdex = oat_file->GetVdexFile();
+  if (vdex == nullptr) {
+    return;
+  }
+  vdex->FullyUnquickenDexFile(new_dex_file, original_dex_file);
+}
+
+std::unique_ptr<FixedUpDexFile> FixedUpDexFile::Create(const art::DexFile& original) {
+  // Copy the data into mutable memory.
+  std::vector<unsigned char> data;
+  data.resize(original.Size());
+  memcpy(data.data(), original.Begin(), original.Size());
+  std::string error;
+  std::unique_ptr<const art::DexFile> new_dex_file(art::DexFile::Open(
+      data.data(),
+      data.size(),
+      /*location*/"Unquickening_dexfile.dex",
+      /*location_checksum*/0,
+      /*oat_dex_file*/nullptr,
+      /*verify*/false,
+      /*verify_checksum*/false,
+      &error));
+  if (new_dex_file.get() == nullptr) {
+    LOG(ERROR) << "Unable to open dex file from memory for unquickening! error: " << error;
+    return nullptr;
+  }
+
+  DoDexUnquicken(*new_dex_file, original);
+  RecomputeDexChecksum(const_cast<art::DexFile*>(new_dex_file.get()));
+  std::unique_ptr<FixedUpDexFile> ret(new FixedUpDexFile(std::move(new_dex_file), std::move(data)));
+  return ret;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/fixed_up_dex_file.h b/openjdkjvmti/fixed_up_dex_file.h
new file mode 100644
index 0000000..4cb39cf
--- /dev/null
+++ b/openjdkjvmti/fixed_up_dex_file.h
@@ -0,0 +1,83 @@
+/* Copyright (C) 2017 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_FIXED_UP_DEX_FILE_H_
+#define ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
+
+#include <memory>
+#include <vector>
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+#include "dex_file.h"
+
+namespace openjdkjvmti {
+
+// A holder for a DexFile that has been 'fixed up' to ensure it is fully compliant with the
+// published standard (no internal/quick opcodes, all fields are the defined values, etc). This is
+// used to ensure that agents get a consistent dex file regardless of what version of android they
+// are running on.
+class FixedUpDexFile {
+ public:
+  static std::unique_ptr<FixedUpDexFile> Create(const art::DexFile& original)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  const art::DexFile& GetDexFile() {
+    return *dex_file_;
+  }
+
+  const unsigned char* Begin() {
+    return data_.data();
+  }
+
+  size_t Size() {
+    return data_.size();
+  }
+
+ private:
+  explicit FixedUpDexFile(std::unique_ptr<const art::DexFile> fixed_up_dex_file,
+                          std::vector<unsigned char> data)
+      : dex_file_(std::move(fixed_up_dex_file)),
+        data_(std::move(data)) {}
+
+  // the fixed up DexFile
+  std::unique_ptr<const art::DexFile> dex_file_;
+  // The backing data for dex_file_.
+  const std::vector<unsigned char> data_;
+
+  DISALLOW_COPY_AND_ASSIGN(FixedUpDexFile);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_FIXED_UP_DEX_FILE_H_
diff --git a/openjdkjvmti/include/jvmti.h b/openjdkjvmti/include/jvmti.h
new file mode 100644
index 0000000..de07c16
--- /dev/null
+++ b/openjdkjvmti/include/jvmti.h
@@ -0,0 +1,2534 @@
+/*
+ * 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.
+ */
+
+    /* AUTOMATICALLY GENERATED FILE - DO NOT EDIT */
+
+
+    /* Include file for the Java(tm) Virtual Machine Tool Interface */
+
+#ifndef _JAVA_JVMTI_H_
+#define _JAVA_JVMTI_H_
+
+#include "jni.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+enum {
+    JVMTI_VERSION_1   = 0x30010000,
+    JVMTI_VERSION_1_0 = 0x30010000,
+    JVMTI_VERSION_1_1 = 0x30010100,
+    JVMTI_VERSION_1_2 = 0x30010200,
+
+    JVMTI_VERSION = 0x30000000 + (1 * 0x10000) + (2 * 0x100) + 1  /* version: 1.2.1 */
+};
+
+JNIEXPORT jint JNICALL
+Agent_OnLoad(JavaVM *vm, char *options, void *reserved);
+
+JNIEXPORT jint JNICALL
+Agent_OnAttach(JavaVM* vm, char* options, void* reserved);
+
+JNIEXPORT void JNICALL
+Agent_OnUnload(JavaVM *vm);
+
+    /* Forward declaration of the environment */
+
+struct _jvmtiEnv;
+
+struct jvmtiInterface_1_;
+
+#ifdef __cplusplus
+typedef _jvmtiEnv jvmtiEnv;
+#else
+typedef const struct jvmtiInterface_1_ *jvmtiEnv;
+#endif /* __cplusplus */
+
+/* Derived Base Types */
+
+typedef jobject jthread;
+typedef jobject jthreadGroup;
+typedef jlong jlocation;
+struct _jrawMonitorID;
+typedef struct _jrawMonitorID *jrawMonitorID;
+typedef struct JNINativeInterface jniNativeInterface;
+
+    /* Constants */
+
+
+    /* Thread State Flags */
+
+enum {
+    JVMTI_THREAD_STATE_ALIVE = 0x0001,
+    JVMTI_THREAD_STATE_TERMINATED = 0x0002,
+    JVMTI_THREAD_STATE_RUNNABLE = 0x0004,
+    JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER = 0x0400,
+    JVMTI_THREAD_STATE_WAITING = 0x0080,
+    JVMTI_THREAD_STATE_WAITING_INDEFINITELY = 0x0010,
+    JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT = 0x0020,
+    JVMTI_THREAD_STATE_SLEEPING = 0x0040,
+    JVMTI_THREAD_STATE_IN_OBJECT_WAIT = 0x0100,
+    JVMTI_THREAD_STATE_PARKED = 0x0200,
+    JVMTI_THREAD_STATE_SUSPENDED = 0x100000,
+    JVMTI_THREAD_STATE_INTERRUPTED = 0x200000,
+    JVMTI_THREAD_STATE_IN_NATIVE = 0x400000,
+    JVMTI_THREAD_STATE_VENDOR_1 = 0x10000000,
+    JVMTI_THREAD_STATE_VENDOR_2 = 0x20000000,
+    JVMTI_THREAD_STATE_VENDOR_3 = 0x40000000
+};
+
+    /* java.lang.Thread.State Conversion Masks */
+
+enum {
+    JVMTI_JAVA_LANG_THREAD_STATE_MASK = JVMTI_THREAD_STATE_TERMINATED | JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT,
+    JVMTI_JAVA_LANG_THREAD_STATE_NEW = 0,
+    JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED = JVMTI_THREAD_STATE_TERMINATED,
+    JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_RUNNABLE,
+    JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER,
+    JVMTI_JAVA_LANG_THREAD_STATE_WAITING = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_INDEFINITELY,
+    JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING = JVMTI_THREAD_STATE_ALIVE | JVMTI_THREAD_STATE_WAITING | JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT
+};
+
+    /* Thread Priority Constants */
+
+enum {
+    JVMTI_THREAD_MIN_PRIORITY = 1,
+    JVMTI_THREAD_NORM_PRIORITY = 5,
+    JVMTI_THREAD_MAX_PRIORITY = 10
+};
+
+    /* Heap Filter Flags */
+
+enum {
+    JVMTI_HEAP_FILTER_TAGGED = 0x4,
+    JVMTI_HEAP_FILTER_UNTAGGED = 0x8,
+    JVMTI_HEAP_FILTER_CLASS_TAGGED = 0x10,
+    JVMTI_HEAP_FILTER_CLASS_UNTAGGED = 0x20
+};
+
+    /* Heap Visit Control Flags */
+
+enum {
+    JVMTI_VISIT_OBJECTS = 0x100,
+    JVMTI_VISIT_ABORT = 0x8000
+};
+
+    /* Heap Reference Enumeration */
+
+typedef enum {
+    JVMTI_HEAP_REFERENCE_CLASS = 1,
+    JVMTI_HEAP_REFERENCE_FIELD = 2,
+    JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT = 3,
+    JVMTI_HEAP_REFERENCE_CLASS_LOADER = 4,
+    JVMTI_HEAP_REFERENCE_SIGNERS = 5,
+    JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN = 6,
+    JVMTI_HEAP_REFERENCE_INTERFACE = 7,
+    JVMTI_HEAP_REFERENCE_STATIC_FIELD = 8,
+    JVMTI_HEAP_REFERENCE_CONSTANT_POOL = 9,
+    JVMTI_HEAP_REFERENCE_SUPERCLASS = 10,
+    JVMTI_HEAP_REFERENCE_JNI_GLOBAL = 21,
+    JVMTI_HEAP_REFERENCE_SYSTEM_CLASS = 22,
+    JVMTI_HEAP_REFERENCE_MONITOR = 23,
+    JVMTI_HEAP_REFERENCE_STACK_LOCAL = 24,
+    JVMTI_HEAP_REFERENCE_JNI_LOCAL = 25,
+    JVMTI_HEAP_REFERENCE_THREAD = 26,
+    JVMTI_HEAP_REFERENCE_OTHER = 27
+} jvmtiHeapReferenceKind;
+
+    /* Primitive Type Enumeration */
+
+typedef enum {
+    JVMTI_PRIMITIVE_TYPE_BOOLEAN = 90,
+    JVMTI_PRIMITIVE_TYPE_BYTE = 66,
+    JVMTI_PRIMITIVE_TYPE_CHAR = 67,
+    JVMTI_PRIMITIVE_TYPE_SHORT = 83,
+    JVMTI_PRIMITIVE_TYPE_INT = 73,
+    JVMTI_PRIMITIVE_TYPE_LONG = 74,
+    JVMTI_PRIMITIVE_TYPE_FLOAT = 70,
+    JVMTI_PRIMITIVE_TYPE_DOUBLE = 68
+} jvmtiPrimitiveType;
+
+    /* Heap Object Filter Enumeration */
+
+typedef enum {
+    JVMTI_HEAP_OBJECT_TAGGED = 1,
+    JVMTI_HEAP_OBJECT_UNTAGGED = 2,
+    JVMTI_HEAP_OBJECT_EITHER = 3
+} jvmtiHeapObjectFilter;
+
+    /* Heap Root Kind Enumeration */
+
+typedef enum {
+    JVMTI_HEAP_ROOT_JNI_GLOBAL = 1,
+    JVMTI_HEAP_ROOT_SYSTEM_CLASS = 2,
+    JVMTI_HEAP_ROOT_MONITOR = 3,
+    JVMTI_HEAP_ROOT_STACK_LOCAL = 4,
+    JVMTI_HEAP_ROOT_JNI_LOCAL = 5,
+    JVMTI_HEAP_ROOT_THREAD = 6,
+    JVMTI_HEAP_ROOT_OTHER = 7
+} jvmtiHeapRootKind;
+
+    /* Object Reference Enumeration */
+
+typedef enum {
+    JVMTI_REFERENCE_CLASS = 1,
+    JVMTI_REFERENCE_FIELD = 2,
+    JVMTI_REFERENCE_ARRAY_ELEMENT = 3,
+    JVMTI_REFERENCE_CLASS_LOADER = 4,
+    JVMTI_REFERENCE_SIGNERS = 5,
+    JVMTI_REFERENCE_PROTECTION_DOMAIN = 6,
+    JVMTI_REFERENCE_INTERFACE = 7,
+    JVMTI_REFERENCE_STATIC_FIELD = 8,
+    JVMTI_REFERENCE_CONSTANT_POOL = 9
+} jvmtiObjectReferenceKind;
+
+    /* Iteration Control Enumeration */
+
+typedef enum {
+    JVMTI_ITERATION_CONTINUE = 1,
+    JVMTI_ITERATION_IGNORE = 2,
+    JVMTI_ITERATION_ABORT = 0
+} jvmtiIterationControl;
+
+    /* Class Status Flags */
+
+enum {
+    JVMTI_CLASS_STATUS_VERIFIED = 1,
+    JVMTI_CLASS_STATUS_PREPARED = 2,
+    JVMTI_CLASS_STATUS_INITIALIZED = 4,
+    JVMTI_CLASS_STATUS_ERROR = 8,
+    JVMTI_CLASS_STATUS_ARRAY = 16,
+    JVMTI_CLASS_STATUS_PRIMITIVE = 32
+};
+
+    /* Event Enable/Disable */
+
+typedef enum {
+    JVMTI_ENABLE = 1,
+    JVMTI_DISABLE = 0
+} jvmtiEventMode;
+
+    /* Extension Function/Event Parameter Types */
+
+typedef enum {
+    JVMTI_TYPE_JBYTE = 101,
+    JVMTI_TYPE_JCHAR = 102,
+    JVMTI_TYPE_JSHORT = 103,
+    JVMTI_TYPE_JINT = 104,
+    JVMTI_TYPE_JLONG = 105,
+    JVMTI_TYPE_JFLOAT = 106,
+    JVMTI_TYPE_JDOUBLE = 107,
+    JVMTI_TYPE_JBOOLEAN = 108,
+    JVMTI_TYPE_JOBJECT = 109,
+    JVMTI_TYPE_JTHREAD = 110,
+    JVMTI_TYPE_JCLASS = 111,
+    JVMTI_TYPE_JVALUE = 112,
+    JVMTI_TYPE_JFIELDID = 113,
+    JVMTI_TYPE_JMETHODID = 114,
+    JVMTI_TYPE_CCHAR = 115,
+    JVMTI_TYPE_CVOID = 116,
+    JVMTI_TYPE_JNIENV = 117
+} jvmtiParamTypes;
+
+    /* Extension Function/Event Parameter Kinds */
+
+typedef enum {
+    JVMTI_KIND_IN = 91,
+    JVMTI_KIND_IN_PTR = 92,
+    JVMTI_KIND_IN_BUF = 93,
+    JVMTI_KIND_ALLOC_BUF = 94,
+    JVMTI_KIND_ALLOC_ALLOC_BUF = 95,
+    JVMTI_KIND_OUT = 96,
+    JVMTI_KIND_OUT_BUF = 97
+} jvmtiParamKind;
+
+    /* Timer Kinds */
+
+typedef enum {
+    JVMTI_TIMER_USER_CPU = 30,
+    JVMTI_TIMER_TOTAL_CPU = 31,
+    JVMTI_TIMER_ELAPSED = 32
+} jvmtiTimerKind;
+
+    /* Phases of execution */
+
+typedef enum {
+    JVMTI_PHASE_ONLOAD = 1,
+    JVMTI_PHASE_PRIMORDIAL = 2,
+    JVMTI_PHASE_START = 6,
+    JVMTI_PHASE_LIVE = 4,
+    JVMTI_PHASE_DEAD = 8
+} jvmtiPhase;
+
+    /* Version Interface Types */
+
+enum {
+    JVMTI_VERSION_INTERFACE_JNI = 0x00000000,
+    JVMTI_VERSION_INTERFACE_JVMTI = 0x30000000
+};
+
+    /* Version Masks */
+
+enum {
+    JVMTI_VERSION_MASK_INTERFACE_TYPE = 0x70000000,
+    JVMTI_VERSION_MASK_MAJOR = 0x0FFF0000,
+    JVMTI_VERSION_MASK_MINOR = 0x0000FF00,
+    JVMTI_VERSION_MASK_MICRO = 0x000000FF
+};
+
+    /* Version Shifts */
+
+enum {
+    JVMTI_VERSION_SHIFT_MAJOR = 16,
+    JVMTI_VERSION_SHIFT_MINOR = 8,
+    JVMTI_VERSION_SHIFT_MICRO = 0
+};
+
+    /* Verbose Flag Enumeration */
+
+typedef enum {
+    JVMTI_VERBOSE_OTHER = 0,
+    JVMTI_VERBOSE_GC = 1,
+    JVMTI_VERBOSE_CLASS = 2,
+    JVMTI_VERBOSE_JNI = 4
+} jvmtiVerboseFlag;
+
+    /* JLocation Format Enumeration */
+
+typedef enum {
+    JVMTI_JLOCATION_JVMBCI = 1,
+    JVMTI_JLOCATION_MACHINEPC = 2,
+    JVMTI_JLOCATION_OTHER = 0
+} jvmtiJlocationFormat;
+
+    /* Resource Exhaustion Flags */
+
+enum {
+    JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR = 0x0001,
+    JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP = 0x0002,
+    JVMTI_RESOURCE_EXHAUSTED_THREADS = 0x0004
+};
+
+    /* Errors */
+
+typedef enum {
+    JVMTI_ERROR_NONE = 0,
+    JVMTI_ERROR_INVALID_THREAD = 10,
+    JVMTI_ERROR_INVALID_THREAD_GROUP = 11,
+    JVMTI_ERROR_INVALID_PRIORITY = 12,
+    JVMTI_ERROR_THREAD_NOT_SUSPENDED = 13,
+    JVMTI_ERROR_THREAD_SUSPENDED = 14,
+    JVMTI_ERROR_THREAD_NOT_ALIVE = 15,
+    JVMTI_ERROR_INVALID_OBJECT = 20,
+    JVMTI_ERROR_INVALID_CLASS = 21,
+    JVMTI_ERROR_CLASS_NOT_PREPARED = 22,
+    JVMTI_ERROR_INVALID_METHODID = 23,
+    JVMTI_ERROR_INVALID_LOCATION = 24,
+    JVMTI_ERROR_INVALID_FIELDID = 25,
+    JVMTI_ERROR_NO_MORE_FRAMES = 31,
+    JVMTI_ERROR_OPAQUE_FRAME = 32,
+    JVMTI_ERROR_TYPE_MISMATCH = 34,
+    JVMTI_ERROR_INVALID_SLOT = 35,
+    JVMTI_ERROR_DUPLICATE = 40,
+    JVMTI_ERROR_NOT_FOUND = 41,
+    JVMTI_ERROR_INVALID_MONITOR = 50,
+    JVMTI_ERROR_NOT_MONITOR_OWNER = 51,
+    JVMTI_ERROR_INTERRUPT = 52,
+    JVMTI_ERROR_INVALID_CLASS_FORMAT = 60,
+    JVMTI_ERROR_CIRCULAR_CLASS_DEFINITION = 61,
+    JVMTI_ERROR_FAILS_VERIFICATION = 62,
+    JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_ADDED = 63,
+    JVMTI_ERROR_UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED = 64,
+    JVMTI_ERROR_INVALID_TYPESTATE = 65,
+    JVMTI_ERROR_UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED = 66,
+    JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_DELETED = 67,
+    JVMTI_ERROR_UNSUPPORTED_VERSION = 68,
+    JVMTI_ERROR_NAMES_DONT_MATCH = 69,
+    JVMTI_ERROR_UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED = 70,
+    JVMTI_ERROR_UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED = 71,
+    JVMTI_ERROR_UNMODIFIABLE_CLASS = 79,
+    JVMTI_ERROR_NOT_AVAILABLE = 98,
+    JVMTI_ERROR_MUST_POSSESS_CAPABILITY = 99,
+    JVMTI_ERROR_NULL_POINTER = 100,
+    JVMTI_ERROR_ABSENT_INFORMATION = 101,
+    JVMTI_ERROR_INVALID_EVENT_TYPE = 102,
+    JVMTI_ERROR_ILLEGAL_ARGUMENT = 103,
+    JVMTI_ERROR_NATIVE_METHOD = 104,
+    JVMTI_ERROR_CLASS_LOADER_UNSUPPORTED = 106,
+    JVMTI_ERROR_OUT_OF_MEMORY = 110,
+    JVMTI_ERROR_ACCESS_DENIED = 111,
+    JVMTI_ERROR_WRONG_PHASE = 112,
+    JVMTI_ERROR_INTERNAL = 113,
+    JVMTI_ERROR_UNATTACHED_THREAD = 115,
+    JVMTI_ERROR_INVALID_ENVIRONMENT = 116,
+    JVMTI_ERROR_MAX = 116
+} jvmtiError;
+
+    /* Event IDs */
+
+typedef enum {
+    JVMTI_MIN_EVENT_TYPE_VAL = 50,
+    JVMTI_EVENT_VM_INIT = 50,
+    JVMTI_EVENT_VM_DEATH = 51,
+    JVMTI_EVENT_THREAD_START = 52,
+    JVMTI_EVENT_THREAD_END = 53,
+    JVMTI_EVENT_CLASS_FILE_LOAD_HOOK = 54,
+    JVMTI_EVENT_CLASS_LOAD = 55,
+    JVMTI_EVENT_CLASS_PREPARE = 56,
+    JVMTI_EVENT_VM_START = 57,
+    JVMTI_EVENT_EXCEPTION = 58,
+    JVMTI_EVENT_EXCEPTION_CATCH = 59,
+    JVMTI_EVENT_SINGLE_STEP = 60,
+    JVMTI_EVENT_FRAME_POP = 61,
+    JVMTI_EVENT_BREAKPOINT = 62,
+    JVMTI_EVENT_FIELD_ACCESS = 63,
+    JVMTI_EVENT_FIELD_MODIFICATION = 64,
+    JVMTI_EVENT_METHOD_ENTRY = 65,
+    JVMTI_EVENT_METHOD_EXIT = 66,
+    JVMTI_EVENT_NATIVE_METHOD_BIND = 67,
+    JVMTI_EVENT_COMPILED_METHOD_LOAD = 68,
+    JVMTI_EVENT_COMPILED_METHOD_UNLOAD = 69,
+    JVMTI_EVENT_DYNAMIC_CODE_GENERATED = 70,
+    JVMTI_EVENT_DATA_DUMP_REQUEST = 71,
+    JVMTI_EVENT_MONITOR_WAIT = 73,
+    JVMTI_EVENT_MONITOR_WAITED = 74,
+    JVMTI_EVENT_MONITOR_CONTENDED_ENTER = 75,
+    JVMTI_EVENT_MONITOR_CONTENDED_ENTERED = 76,
+    JVMTI_EVENT_RESOURCE_EXHAUSTED = 80,
+    JVMTI_EVENT_GARBAGE_COLLECTION_START = 81,
+    JVMTI_EVENT_GARBAGE_COLLECTION_FINISH = 82,
+    JVMTI_EVENT_OBJECT_FREE = 83,
+    JVMTI_EVENT_VM_OBJECT_ALLOC = 84,
+    JVMTI_MAX_EVENT_TYPE_VAL = 84
+} jvmtiEvent;
+
+
+    /* Pre-Declarations */
+struct _jvmtiThreadInfo;
+typedef struct _jvmtiThreadInfo jvmtiThreadInfo;
+struct _jvmtiMonitorStackDepthInfo;
+typedef struct _jvmtiMonitorStackDepthInfo jvmtiMonitorStackDepthInfo;
+struct _jvmtiThreadGroupInfo;
+typedef struct _jvmtiThreadGroupInfo jvmtiThreadGroupInfo;
+struct _jvmtiFrameInfo;
+typedef struct _jvmtiFrameInfo jvmtiFrameInfo;
+struct _jvmtiStackInfo;
+typedef struct _jvmtiStackInfo jvmtiStackInfo;
+struct _jvmtiHeapReferenceInfoField;
+typedef struct _jvmtiHeapReferenceInfoField jvmtiHeapReferenceInfoField;
+struct _jvmtiHeapReferenceInfoArray;
+typedef struct _jvmtiHeapReferenceInfoArray jvmtiHeapReferenceInfoArray;
+struct _jvmtiHeapReferenceInfoConstantPool;
+typedef struct _jvmtiHeapReferenceInfoConstantPool jvmtiHeapReferenceInfoConstantPool;
+struct _jvmtiHeapReferenceInfoStackLocal;
+typedef struct _jvmtiHeapReferenceInfoStackLocal jvmtiHeapReferenceInfoStackLocal;
+struct _jvmtiHeapReferenceInfoJniLocal;
+typedef struct _jvmtiHeapReferenceInfoJniLocal jvmtiHeapReferenceInfoJniLocal;
+struct _jvmtiHeapReferenceInfoReserved;
+typedef struct _jvmtiHeapReferenceInfoReserved jvmtiHeapReferenceInfoReserved;
+union _jvmtiHeapReferenceInfo;
+typedef union _jvmtiHeapReferenceInfo jvmtiHeapReferenceInfo;
+struct _jvmtiHeapCallbacks;
+typedef struct _jvmtiHeapCallbacks jvmtiHeapCallbacks;
+struct _jvmtiClassDefinition;
+typedef struct _jvmtiClassDefinition jvmtiClassDefinition;
+struct _jvmtiMonitorUsage;
+typedef struct _jvmtiMonitorUsage jvmtiMonitorUsage;
+struct _jvmtiLineNumberEntry;
+typedef struct _jvmtiLineNumberEntry jvmtiLineNumberEntry;
+struct _jvmtiLocalVariableEntry;
+typedef struct _jvmtiLocalVariableEntry jvmtiLocalVariableEntry;
+struct _jvmtiParamInfo;
+typedef struct _jvmtiParamInfo jvmtiParamInfo;
+struct _jvmtiExtensionFunctionInfo;
+typedef struct _jvmtiExtensionFunctionInfo jvmtiExtensionFunctionInfo;
+struct _jvmtiExtensionEventInfo;
+typedef struct _jvmtiExtensionEventInfo jvmtiExtensionEventInfo;
+struct _jvmtiTimerInfo;
+typedef struct _jvmtiTimerInfo jvmtiTimerInfo;
+struct _jvmtiAddrLocationMap;
+typedef struct _jvmtiAddrLocationMap jvmtiAddrLocationMap;
+
+    /* Function Types */
+
+typedef void (JNICALL *jvmtiStartFunction)
+    (jvmtiEnv* jvmti_env, JNIEnv* jni_env, void* arg);
+
+typedef jint (JNICALL *jvmtiHeapIterationCallback)
+    (jlong class_tag, jlong size, jlong* tag_ptr, jint length, void* user_data);
+
+typedef jint (JNICALL *jvmtiHeapReferenceCallback)
+    (jvmtiHeapReferenceKind reference_kind, const jvmtiHeapReferenceInfo* reference_info, jlong class_tag, jlong referrer_class_tag, jlong size, jlong* tag_ptr, jlong* referrer_tag_ptr, jint length, void* user_data);
+
+typedef jint (JNICALL *jvmtiPrimitiveFieldCallback)
+    (jvmtiHeapReferenceKind kind, const jvmtiHeapReferenceInfo* info, jlong object_class_tag, jlong* object_tag_ptr, jvalue value, jvmtiPrimitiveType value_type, void* user_data);
+
+typedef jint (JNICALL *jvmtiArrayPrimitiveValueCallback)
+    (jlong class_tag, jlong size, jlong* tag_ptr, jint element_count, jvmtiPrimitiveType element_type, const void* elements, void* user_data);
+
+typedef jint (JNICALL *jvmtiStringPrimitiveValueCallback)
+    (jlong class_tag, jlong size, jlong* tag_ptr, const jchar* value, jint value_length, void* user_data);
+
+typedef jint (JNICALL *jvmtiReservedCallback)
+    ();
+
+typedef jvmtiIterationControl (JNICALL *jvmtiHeapObjectCallback)
+    (jlong class_tag, jlong size, jlong* tag_ptr, void* user_data);
+
+typedef jvmtiIterationControl (JNICALL *jvmtiHeapRootCallback)
+    (jvmtiHeapRootKind root_kind, jlong class_tag, jlong size, jlong* tag_ptr, void* user_data);
+
+typedef jvmtiIterationControl (JNICALL *jvmtiStackReferenceCallback)
+    (jvmtiHeapRootKind root_kind, jlong class_tag, jlong size, jlong* tag_ptr, jlong thread_tag, jint depth, jmethodID method, jint slot, void* user_data);
+
+typedef jvmtiIterationControl (JNICALL *jvmtiObjectReferenceCallback)
+    (jvmtiObjectReferenceKind reference_kind, jlong class_tag, jlong size, jlong* tag_ptr, jlong referrer_tag, jint referrer_index, void* user_data);
+
+typedef jvmtiError (JNICALL *jvmtiExtensionFunction)
+    (jvmtiEnv* jvmti_env,  ...);
+
+typedef void (JNICALL *jvmtiExtensionEvent)
+    (jvmtiEnv* jvmti_env,  ...);
+
+
+    /* Structure Types */
+struct _jvmtiThreadInfo {
+    char* name;
+    jint priority;
+    jboolean is_daemon;
+    jthreadGroup thread_group;
+    jobject context_class_loader;
+};
+struct _jvmtiMonitorStackDepthInfo {
+    jobject monitor;
+    jint stack_depth;
+};
+struct _jvmtiThreadGroupInfo {
+    jthreadGroup parent;
+    char* name;
+    jint max_priority;
+    jboolean is_daemon;
+};
+struct _jvmtiFrameInfo {
+    jmethodID method;
+    jlocation location;
+};
+struct _jvmtiStackInfo {
+    jthread thread;
+    jint state;
+    jvmtiFrameInfo* frame_buffer;
+    jint frame_count;
+};
+struct _jvmtiHeapReferenceInfoField {
+    jint index;
+};
+struct _jvmtiHeapReferenceInfoArray {
+    jint index;
+};
+struct _jvmtiHeapReferenceInfoConstantPool {
+    jint index;
+};
+struct _jvmtiHeapReferenceInfoStackLocal {
+    jlong thread_tag;
+    jlong thread_id;
+    jint depth;
+    jmethodID method;
+    jlocation location;
+    jint slot;
+};
+struct _jvmtiHeapReferenceInfoJniLocal {
+    jlong thread_tag;
+    jlong thread_id;
+    jint depth;
+    jmethodID method;
+};
+struct _jvmtiHeapReferenceInfoReserved {
+    jlong reserved1;
+    jlong reserved2;
+    jlong reserved3;
+    jlong reserved4;
+    jlong reserved5;
+    jlong reserved6;
+    jlong reserved7;
+    jlong reserved8;
+};
+union _jvmtiHeapReferenceInfo {
+    jvmtiHeapReferenceInfoField field;
+    jvmtiHeapReferenceInfoArray array;
+    jvmtiHeapReferenceInfoConstantPool constant_pool;
+    jvmtiHeapReferenceInfoStackLocal stack_local;
+    jvmtiHeapReferenceInfoJniLocal jni_local;
+    jvmtiHeapReferenceInfoReserved other;
+};
+struct _jvmtiHeapCallbacks {
+    jvmtiHeapIterationCallback heap_iteration_callback;
+    jvmtiHeapReferenceCallback heap_reference_callback;
+    jvmtiPrimitiveFieldCallback primitive_field_callback;
+    jvmtiArrayPrimitiveValueCallback array_primitive_value_callback;
+    jvmtiStringPrimitiveValueCallback string_primitive_value_callback;
+    jvmtiReservedCallback reserved5;
+    jvmtiReservedCallback reserved6;
+    jvmtiReservedCallback reserved7;
+    jvmtiReservedCallback reserved8;
+    jvmtiReservedCallback reserved9;
+    jvmtiReservedCallback reserved10;
+    jvmtiReservedCallback reserved11;
+    jvmtiReservedCallback reserved12;
+    jvmtiReservedCallback reserved13;
+    jvmtiReservedCallback reserved14;
+    jvmtiReservedCallback reserved15;
+};
+struct _jvmtiClassDefinition {
+    jclass klass;
+    jint class_byte_count;
+    const unsigned char* class_bytes;
+};
+struct _jvmtiMonitorUsage {
+    jthread owner;
+    jint entry_count;
+    jint waiter_count;
+    jthread* waiters;
+    jint notify_waiter_count;
+    jthread* notify_waiters;
+};
+struct _jvmtiLineNumberEntry {
+    jlocation start_location;
+    jint line_number;
+};
+struct _jvmtiLocalVariableEntry {
+    jlocation start_location;
+    jint length;
+    char* name;
+    char* signature;
+    char* generic_signature;
+    jint slot;
+};
+struct _jvmtiParamInfo {
+    char* name;
+    jvmtiParamKind kind;
+    jvmtiParamTypes base_type;
+    jboolean null_ok;
+};
+struct _jvmtiExtensionFunctionInfo {
+    jvmtiExtensionFunction func;
+    char* id;
+    char* short_description;
+    jint param_count;
+    jvmtiParamInfo* params;
+    jint error_count;
+    jvmtiError* errors;
+};
+struct _jvmtiExtensionEventInfo {
+    jint extension_event_index;
+    char* id;
+    char* short_description;
+    jint param_count;
+    jvmtiParamInfo* params;
+};
+struct _jvmtiTimerInfo {
+    jlong max_value;
+    jboolean may_skip_forward;
+    jboolean may_skip_backward;
+    jvmtiTimerKind kind;
+    jlong reserved1;
+    jlong reserved2;
+};
+struct _jvmtiAddrLocationMap {
+    const void* start_address;
+    jlocation location;
+};
+
+typedef struct {
+    unsigned int can_tag_objects : 1;
+    unsigned int can_generate_field_modification_events : 1;
+    unsigned int can_generate_field_access_events : 1;
+    unsigned int can_get_bytecodes : 1;
+    unsigned int can_get_synthetic_attribute : 1;
+    unsigned int can_get_owned_monitor_info : 1;
+    unsigned int can_get_current_contended_monitor : 1;
+    unsigned int can_get_monitor_info : 1;
+    unsigned int can_pop_frame : 1;
+    unsigned int can_redefine_classes : 1;
+    unsigned int can_signal_thread : 1;
+    unsigned int can_get_source_file_name : 1;
+    unsigned int can_get_line_numbers : 1;
+    unsigned int can_get_source_debug_extension : 1;
+    unsigned int can_access_local_variables : 1;
+    unsigned int can_maintain_original_method_order : 1;
+    unsigned int can_generate_single_step_events : 1;
+    unsigned int can_generate_exception_events : 1;
+    unsigned int can_generate_frame_pop_events : 1;
+    unsigned int can_generate_breakpoint_events : 1;
+    unsigned int can_suspend : 1;
+    unsigned int can_redefine_any_class : 1;
+    unsigned int can_get_current_thread_cpu_time : 1;
+    unsigned int can_get_thread_cpu_time : 1;
+    unsigned int can_generate_method_entry_events : 1;
+    unsigned int can_generate_method_exit_events : 1;
+    unsigned int can_generate_all_class_hook_events : 1;
+    unsigned int can_generate_compiled_method_load_events : 1;
+    unsigned int can_generate_monitor_events : 1;
+    unsigned int can_generate_vm_object_alloc_events : 1;
+    unsigned int can_generate_native_method_bind_events : 1;
+    unsigned int can_generate_garbage_collection_events : 1;
+    unsigned int can_generate_object_free_events : 1;
+    unsigned int can_force_early_return : 1;
+    unsigned int can_get_owned_monitor_stack_depth_info : 1;
+    unsigned int can_get_constant_pool : 1;
+    unsigned int can_set_native_method_prefix : 1;
+    unsigned int can_retransform_classes : 1;
+    unsigned int can_retransform_any_class : 1;
+    unsigned int can_generate_resource_exhaustion_heap_events : 1;
+    unsigned int can_generate_resource_exhaustion_threads_events : 1;
+    unsigned int : 7;
+    unsigned int : 16;
+    unsigned int : 16;
+    unsigned int : 16;
+    unsigned int : 16;
+    unsigned int : 16;
+} jvmtiCapabilities;
+
+
+    /* Event Definitions */
+
+typedef void (JNICALL *jvmtiEventReserved)(void);
+
+
+typedef void (JNICALL *jvmtiEventBreakpoint)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jlocation location);
+
+typedef void (JNICALL *jvmtiEventClassFileLoadHook)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jclass class_being_redefined,
+     jobject loader,
+     const char* name,
+     jobject protection_domain,
+     jint class_data_len,
+     const unsigned char* class_data,
+     jint* new_class_data_len,
+     unsigned char** new_class_data);
+
+typedef void (JNICALL *jvmtiEventClassLoad)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jclass klass);
+
+typedef void (JNICALL *jvmtiEventClassPrepare)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jclass klass);
+
+typedef void (JNICALL *jvmtiEventCompiledMethodLoad)
+    (jvmtiEnv *jvmti_env,
+     jmethodID method,
+     jint code_size,
+     const void* code_addr,
+     jint map_length,
+     const jvmtiAddrLocationMap* map,
+     const void* compile_info);
+
+typedef void (JNICALL *jvmtiEventCompiledMethodUnload)
+    (jvmtiEnv *jvmti_env,
+     jmethodID method,
+     const void* code_addr);
+
+typedef void (JNICALL *jvmtiEventDataDumpRequest)
+    (jvmtiEnv *jvmti_env);
+
+typedef void (JNICALL *jvmtiEventDynamicCodeGenerated)
+    (jvmtiEnv *jvmti_env,
+     const char* name,
+     const void* address,
+     jint length);
+
+typedef void (JNICALL *jvmtiEventException)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jlocation location,
+     jobject exception,
+     jmethodID catch_method,
+     jlocation catch_location);
+
+typedef void (JNICALL *jvmtiEventExceptionCatch)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jlocation location,
+     jobject exception);
+
+typedef void (JNICALL *jvmtiEventFieldAccess)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jlocation location,
+     jclass field_klass,
+     jobject object,
+     jfieldID field);
+
+typedef void (JNICALL *jvmtiEventFieldModification)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jlocation location,
+     jclass field_klass,
+     jobject object,
+     jfieldID field,
+     char signature_type,
+     jvalue new_value);
+
+typedef void (JNICALL *jvmtiEventFramePop)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jboolean was_popped_by_exception);
+
+typedef void (JNICALL *jvmtiEventGarbageCollectionFinish)
+    (jvmtiEnv *jvmti_env);
+
+typedef void (JNICALL *jvmtiEventGarbageCollectionStart)
+    (jvmtiEnv *jvmti_env);
+
+typedef void (JNICALL *jvmtiEventMethodEntry)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method);
+
+typedef void (JNICALL *jvmtiEventMethodExit)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jboolean was_popped_by_exception,
+     jvalue return_value);
+
+typedef void (JNICALL *jvmtiEventMonitorContendedEnter)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jobject object);
+
+typedef void (JNICALL *jvmtiEventMonitorContendedEntered)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jobject object);
+
+typedef void (JNICALL *jvmtiEventMonitorWait)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jobject object,
+     jlong timeout);
+
+typedef void (JNICALL *jvmtiEventMonitorWaited)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jobject object,
+     jboolean timed_out);
+
+typedef void (JNICALL *jvmtiEventNativeMethodBind)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     void* address,
+     void** new_address_ptr);
+
+typedef void (JNICALL *jvmtiEventObjectFree)
+    (jvmtiEnv *jvmti_env,
+     jlong tag);
+
+typedef void (JNICALL *jvmtiEventResourceExhausted)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jint flags,
+     const void* reserved,
+     const char* description);
+
+typedef void (JNICALL *jvmtiEventSingleStep)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jmethodID method,
+     jlocation location);
+
+typedef void (JNICALL *jvmtiEventThreadEnd)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread);
+
+typedef void (JNICALL *jvmtiEventThreadStart)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread);
+
+typedef void (JNICALL *jvmtiEventVMDeath)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env);
+
+typedef void (JNICALL *jvmtiEventVMInit)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread);
+
+typedef void (JNICALL *jvmtiEventVMObjectAlloc)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env,
+     jthread thread,
+     jobject object,
+     jclass object_klass,
+     jlong size);
+
+typedef void (JNICALL *jvmtiEventVMStart)
+    (jvmtiEnv *jvmti_env,
+     JNIEnv* jni_env);
+
+    /* Event Callback Structure */
+
+typedef struct {
+                              /*   50 : VM Initialization Event */
+    jvmtiEventVMInit VMInit;
+                              /*   51 : VM Death Event */
+    jvmtiEventVMDeath VMDeath;
+                              /*   52 : Thread Start */
+    jvmtiEventThreadStart ThreadStart;
+                              /*   53 : Thread End */
+    jvmtiEventThreadEnd ThreadEnd;
+                              /*   54 : Class File Load Hook */
+    jvmtiEventClassFileLoadHook ClassFileLoadHook;
+                              /*   55 : Class Load */
+    jvmtiEventClassLoad ClassLoad;
+                              /*   56 : Class Prepare */
+    jvmtiEventClassPrepare ClassPrepare;
+                              /*   57 : VM Start Event */
+    jvmtiEventVMStart VMStart;
+                              /*   58 : Exception */
+    jvmtiEventException Exception;
+                              /*   59 : Exception Catch */
+    jvmtiEventExceptionCatch ExceptionCatch;
+                              /*   60 : Single Step */
+    jvmtiEventSingleStep SingleStep;
+                              /*   61 : Frame Pop */
+    jvmtiEventFramePop FramePop;
+                              /*   62 : Breakpoint */
+    jvmtiEventBreakpoint Breakpoint;
+                              /*   63 : Field Access */
+    jvmtiEventFieldAccess FieldAccess;
+                              /*   64 : Field Modification */
+    jvmtiEventFieldModification FieldModification;
+                              /*   65 : Method Entry */
+    jvmtiEventMethodEntry MethodEntry;
+                              /*   66 : Method Exit */
+    jvmtiEventMethodExit MethodExit;
+                              /*   67 : Native Method Bind */
+    jvmtiEventNativeMethodBind NativeMethodBind;
+                              /*   68 : Compiled Method Load */
+    jvmtiEventCompiledMethodLoad CompiledMethodLoad;
+                              /*   69 : Compiled Method Unload */
+    jvmtiEventCompiledMethodUnload CompiledMethodUnload;
+                              /*   70 : Dynamic Code Generated */
+    jvmtiEventDynamicCodeGenerated DynamicCodeGenerated;
+                              /*   71 : Data Dump Request */
+    jvmtiEventDataDumpRequest DataDumpRequest;
+                              /*   72 */
+    jvmtiEventReserved reserved72;
+                              /*   73 : Monitor Wait */
+    jvmtiEventMonitorWait MonitorWait;
+                              /*   74 : Monitor Waited */
+    jvmtiEventMonitorWaited MonitorWaited;
+                              /*   75 : Monitor Contended Enter */
+    jvmtiEventMonitorContendedEnter MonitorContendedEnter;
+                              /*   76 : Monitor Contended Entered */
+    jvmtiEventMonitorContendedEntered MonitorContendedEntered;
+                              /*   77 */
+    jvmtiEventReserved reserved77;
+                              /*   78 */
+    jvmtiEventReserved reserved78;
+                              /*   79 */
+    jvmtiEventReserved reserved79;
+                              /*   80 : Resource Exhausted */
+    jvmtiEventResourceExhausted ResourceExhausted;
+                              /*   81 : Garbage Collection Start */
+    jvmtiEventGarbageCollectionStart GarbageCollectionStart;
+                              /*   82 : Garbage Collection Finish */
+    jvmtiEventGarbageCollectionFinish GarbageCollectionFinish;
+                              /*   83 : Object Free */
+    jvmtiEventObjectFree ObjectFree;
+                              /*   84 : VM Object Allocation */
+    jvmtiEventVMObjectAlloc VMObjectAlloc;
+} jvmtiEventCallbacks;
+
+
+    /* Function Interface */
+
+typedef struct jvmtiInterface_1_ {
+
+  /*   1 :  RESERVED */
+  void *reserved1;
+
+  /*   2 : Set Event Notification Mode */
+  jvmtiError (JNICALL *SetEventNotificationMode) (jvmtiEnv* env,
+    jvmtiEventMode mode,
+    jvmtiEvent event_type,
+    jthread event_thread,
+     ...);
+
+  /*   3 :  RESERVED */
+  void *reserved3;
+
+  /*   4 : Get All Threads */
+  jvmtiError (JNICALL *GetAllThreads) (jvmtiEnv* env,
+    jint* threads_count_ptr,
+    jthread** threads_ptr);
+
+  /*   5 : Suspend Thread */
+  jvmtiError (JNICALL *SuspendThread) (jvmtiEnv* env,
+    jthread thread);
+
+  /*   6 : Resume Thread */
+  jvmtiError (JNICALL *ResumeThread) (jvmtiEnv* env,
+    jthread thread);
+
+  /*   7 : Stop Thread */
+  jvmtiError (JNICALL *StopThread) (jvmtiEnv* env,
+    jthread thread,
+    jobject exception);
+
+  /*   8 : Interrupt Thread */
+  jvmtiError (JNICALL *InterruptThread) (jvmtiEnv* env,
+    jthread thread);
+
+  /*   9 : Get Thread Info */
+  jvmtiError (JNICALL *GetThreadInfo) (jvmtiEnv* env,
+    jthread thread,
+    jvmtiThreadInfo* info_ptr);
+
+  /*   10 : Get Owned Monitor Info */
+  jvmtiError (JNICALL *GetOwnedMonitorInfo) (jvmtiEnv* env,
+    jthread thread,
+    jint* owned_monitor_count_ptr,
+    jobject** owned_monitors_ptr);
+
+  /*   11 : Get Current Contended Monitor */
+  jvmtiError (JNICALL *GetCurrentContendedMonitor) (jvmtiEnv* env,
+    jthread thread,
+    jobject* monitor_ptr);
+
+  /*   12 : Run Agent Thread */
+  jvmtiError (JNICALL *RunAgentThread) (jvmtiEnv* env,
+    jthread thread,
+    jvmtiStartFunction proc,
+    const void* arg,
+    jint priority);
+
+  /*   13 : Get Top Thread Groups */
+  jvmtiError (JNICALL *GetTopThreadGroups) (jvmtiEnv* env,
+    jint* group_count_ptr,
+    jthreadGroup** groups_ptr);
+
+  /*   14 : Get Thread Group Info */
+  jvmtiError (JNICALL *GetThreadGroupInfo) (jvmtiEnv* env,
+    jthreadGroup group,
+    jvmtiThreadGroupInfo* info_ptr);
+
+  /*   15 : Get Thread Group Children */
+  jvmtiError (JNICALL *GetThreadGroupChildren) (jvmtiEnv* env,
+    jthreadGroup group,
+    jint* thread_count_ptr,
+    jthread** threads_ptr,
+    jint* group_count_ptr,
+    jthreadGroup** groups_ptr);
+
+  /*   16 : Get Frame Count */
+  jvmtiError (JNICALL *GetFrameCount) (jvmtiEnv* env,
+    jthread thread,
+    jint* count_ptr);
+
+  /*   17 : Get Thread State */
+  jvmtiError (JNICALL *GetThreadState) (jvmtiEnv* env,
+    jthread thread,
+    jint* thread_state_ptr);
+
+  /*   18 : Get Current Thread */
+  jvmtiError (JNICALL *GetCurrentThread) (jvmtiEnv* env,
+    jthread* thread_ptr);
+
+  /*   19 : Get Frame Location */
+  jvmtiError (JNICALL *GetFrameLocation) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jmethodID* method_ptr,
+    jlocation* location_ptr);
+
+  /*   20 : Notify Frame Pop */
+  jvmtiError (JNICALL *NotifyFramePop) (jvmtiEnv* env,
+    jthread thread,
+    jint depth);
+
+  /*   21 : Get Local Variable - Object */
+  jvmtiError (JNICALL *GetLocalObject) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jobject* value_ptr);
+
+  /*   22 : Get Local Variable - Int */
+  jvmtiError (JNICALL *GetLocalInt) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jint* value_ptr);
+
+  /*   23 : Get Local Variable - Long */
+  jvmtiError (JNICALL *GetLocalLong) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jlong* value_ptr);
+
+  /*   24 : Get Local Variable - Float */
+  jvmtiError (JNICALL *GetLocalFloat) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jfloat* value_ptr);
+
+  /*   25 : Get Local Variable - Double */
+  jvmtiError (JNICALL *GetLocalDouble) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jdouble* value_ptr);
+
+  /*   26 : Set Local Variable - Object */
+  jvmtiError (JNICALL *SetLocalObject) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jobject value);
+
+  /*   27 : Set Local Variable - Int */
+  jvmtiError (JNICALL *SetLocalInt) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jint value);
+
+  /*   28 : Set Local Variable - Long */
+  jvmtiError (JNICALL *SetLocalLong) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jlong value);
+
+  /*   29 : Set Local Variable - Float */
+  jvmtiError (JNICALL *SetLocalFloat) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jfloat value);
+
+  /*   30 : Set Local Variable - Double */
+  jvmtiError (JNICALL *SetLocalDouble) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jint slot,
+    jdouble value);
+
+  /*   31 : Create Raw Monitor */
+  jvmtiError (JNICALL *CreateRawMonitor) (jvmtiEnv* env,
+    const char* name,
+    jrawMonitorID* monitor_ptr);
+
+  /*   32 : Destroy Raw Monitor */
+  jvmtiError (JNICALL *DestroyRawMonitor) (jvmtiEnv* env,
+    jrawMonitorID monitor);
+
+  /*   33 : Raw Monitor Enter */
+  jvmtiError (JNICALL *RawMonitorEnter) (jvmtiEnv* env,
+    jrawMonitorID monitor);
+
+  /*   34 : Raw Monitor Exit */
+  jvmtiError (JNICALL *RawMonitorExit) (jvmtiEnv* env,
+    jrawMonitorID monitor);
+
+  /*   35 : Raw Monitor Wait */
+  jvmtiError (JNICALL *RawMonitorWait) (jvmtiEnv* env,
+    jrawMonitorID monitor,
+    jlong millis);
+
+  /*   36 : Raw Monitor Notify */
+  jvmtiError (JNICALL *RawMonitorNotify) (jvmtiEnv* env,
+    jrawMonitorID monitor);
+
+  /*   37 : Raw Monitor Notify All */
+  jvmtiError (JNICALL *RawMonitorNotifyAll) (jvmtiEnv* env,
+    jrawMonitorID monitor);
+
+  /*   38 : Set Breakpoint */
+  jvmtiError (JNICALL *SetBreakpoint) (jvmtiEnv* env,
+    jmethodID method,
+    jlocation location);
+
+  /*   39 : Clear Breakpoint */
+  jvmtiError (JNICALL *ClearBreakpoint) (jvmtiEnv* env,
+    jmethodID method,
+    jlocation location);
+
+  /*   40 :  RESERVED */
+  void *reserved40;
+
+  /*   41 : Set Field Access Watch */
+  jvmtiError (JNICALL *SetFieldAccessWatch) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field);
+
+  /*   42 : Clear Field Access Watch */
+  jvmtiError (JNICALL *ClearFieldAccessWatch) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field);
+
+  /*   43 : Set Field Modification Watch */
+  jvmtiError (JNICALL *SetFieldModificationWatch) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field);
+
+  /*   44 : Clear Field Modification Watch */
+  jvmtiError (JNICALL *ClearFieldModificationWatch) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field);
+
+  /*   45 : Is Modifiable Class */
+  jvmtiError (JNICALL *IsModifiableClass) (jvmtiEnv* env,
+    jclass klass,
+    jboolean* is_modifiable_class_ptr);
+
+  /*   46 : Allocate */
+  jvmtiError (JNICALL *Allocate) (jvmtiEnv* env,
+    jlong size,
+    unsigned char** mem_ptr);
+
+  /*   47 : Deallocate */
+  jvmtiError (JNICALL *Deallocate) (jvmtiEnv* env,
+    unsigned char* mem);
+
+  /*   48 : Get Class Signature */
+  jvmtiError (JNICALL *GetClassSignature) (jvmtiEnv* env,
+    jclass klass,
+    char** signature_ptr,
+    char** generic_ptr);
+
+  /*   49 : Get Class Status */
+  jvmtiError (JNICALL *GetClassStatus) (jvmtiEnv* env,
+    jclass klass,
+    jint* status_ptr);
+
+  /*   50 : Get Source File Name */
+  jvmtiError (JNICALL *GetSourceFileName) (jvmtiEnv* env,
+    jclass klass,
+    char** source_name_ptr);
+
+  /*   51 : Get Class Modifiers */
+  jvmtiError (JNICALL *GetClassModifiers) (jvmtiEnv* env,
+    jclass klass,
+    jint* modifiers_ptr);
+
+  /*   52 : Get Class Methods */
+  jvmtiError (JNICALL *GetClassMethods) (jvmtiEnv* env,
+    jclass klass,
+    jint* method_count_ptr,
+    jmethodID** methods_ptr);
+
+  /*   53 : Get Class Fields */
+  jvmtiError (JNICALL *GetClassFields) (jvmtiEnv* env,
+    jclass klass,
+    jint* field_count_ptr,
+    jfieldID** fields_ptr);
+
+  /*   54 : Get Implemented Interfaces */
+  jvmtiError (JNICALL *GetImplementedInterfaces) (jvmtiEnv* env,
+    jclass klass,
+    jint* interface_count_ptr,
+    jclass** interfaces_ptr);
+
+  /*   55 : Is Interface */
+  jvmtiError (JNICALL *IsInterface) (jvmtiEnv* env,
+    jclass klass,
+    jboolean* is_interface_ptr);
+
+  /*   56 : Is Array Class */
+  jvmtiError (JNICALL *IsArrayClass) (jvmtiEnv* env,
+    jclass klass,
+    jboolean* is_array_class_ptr);
+
+  /*   57 : Get Class Loader */
+  jvmtiError (JNICALL *GetClassLoader) (jvmtiEnv* env,
+    jclass klass,
+    jobject* classloader_ptr);
+
+  /*   58 : Get Object Hash Code */
+  jvmtiError (JNICALL *GetObjectHashCode) (jvmtiEnv* env,
+    jobject object,
+    jint* hash_code_ptr);
+
+  /*   59 : Get Object Monitor Usage */
+  jvmtiError (JNICALL *GetObjectMonitorUsage) (jvmtiEnv* env,
+    jobject object,
+    jvmtiMonitorUsage* info_ptr);
+
+  /*   60 : Get Field Name (and Signature) */
+  jvmtiError (JNICALL *GetFieldName) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field,
+    char** name_ptr,
+    char** signature_ptr,
+    char** generic_ptr);
+
+  /*   61 : Get Field Declaring Class */
+  jvmtiError (JNICALL *GetFieldDeclaringClass) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field,
+    jclass* declaring_class_ptr);
+
+  /*   62 : Get Field Modifiers */
+  jvmtiError (JNICALL *GetFieldModifiers) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field,
+    jint* modifiers_ptr);
+
+  /*   63 : Is Field Synthetic */
+  jvmtiError (JNICALL *IsFieldSynthetic) (jvmtiEnv* env,
+    jclass klass,
+    jfieldID field,
+    jboolean* is_synthetic_ptr);
+
+  /*   64 : Get Method Name (and Signature) */
+  jvmtiError (JNICALL *GetMethodName) (jvmtiEnv* env,
+    jmethodID method,
+    char** name_ptr,
+    char** signature_ptr,
+    char** generic_ptr);
+
+  /*   65 : Get Method Declaring Class */
+  jvmtiError (JNICALL *GetMethodDeclaringClass) (jvmtiEnv* env,
+    jmethodID method,
+    jclass* declaring_class_ptr);
+
+  /*   66 : Get Method Modifiers */
+  jvmtiError (JNICALL *GetMethodModifiers) (jvmtiEnv* env,
+    jmethodID method,
+    jint* modifiers_ptr);
+
+  /*   67 :  RESERVED */
+  void *reserved67;
+
+  /*   68 : Get Max Locals */
+  jvmtiError (JNICALL *GetMaxLocals) (jvmtiEnv* env,
+    jmethodID method,
+    jint* max_ptr);
+
+  /*   69 : Get Arguments Size */
+  jvmtiError (JNICALL *GetArgumentsSize) (jvmtiEnv* env,
+    jmethodID method,
+    jint* size_ptr);
+
+  /*   70 : Get Line Number Table */
+  jvmtiError (JNICALL *GetLineNumberTable) (jvmtiEnv* env,
+    jmethodID method,
+    jint* entry_count_ptr,
+    jvmtiLineNumberEntry** table_ptr);
+
+  /*   71 : Get Method Location */
+  jvmtiError (JNICALL *GetMethodLocation) (jvmtiEnv* env,
+    jmethodID method,
+    jlocation* start_location_ptr,
+    jlocation* end_location_ptr);
+
+  /*   72 : Get Local Variable Table */
+  jvmtiError (JNICALL *GetLocalVariableTable) (jvmtiEnv* env,
+    jmethodID method,
+    jint* entry_count_ptr,
+    jvmtiLocalVariableEntry** table_ptr);
+
+  /*   73 : Set Native Method Prefix */
+  jvmtiError (JNICALL *SetNativeMethodPrefix) (jvmtiEnv* env,
+    const char* prefix);
+
+  /*   74 : Set Native Method Prefixes */
+  jvmtiError (JNICALL *SetNativeMethodPrefixes) (jvmtiEnv* env,
+    jint prefix_count,
+    char** prefixes);
+
+  /*   75 : Get Bytecodes */
+  jvmtiError (JNICALL *GetBytecodes) (jvmtiEnv* env,
+    jmethodID method,
+    jint* bytecode_count_ptr,
+    unsigned char** bytecodes_ptr);
+
+  /*   76 : Is Method Native */
+  jvmtiError (JNICALL *IsMethodNative) (jvmtiEnv* env,
+    jmethodID method,
+    jboolean* is_native_ptr);
+
+  /*   77 : Is Method Synthetic */
+  jvmtiError (JNICALL *IsMethodSynthetic) (jvmtiEnv* env,
+    jmethodID method,
+    jboolean* is_synthetic_ptr);
+
+  /*   78 : Get Loaded Classes */
+  jvmtiError (JNICALL *GetLoadedClasses) (jvmtiEnv* env,
+    jint* class_count_ptr,
+    jclass** classes_ptr);
+
+  /*   79 : Get Classloader Classes */
+  jvmtiError (JNICALL *GetClassLoaderClasses) (jvmtiEnv* env,
+    jobject initiating_loader,
+    jint* class_count_ptr,
+    jclass** classes_ptr);
+
+  /*   80 : Pop Frame */
+  jvmtiError (JNICALL *PopFrame) (jvmtiEnv* env,
+    jthread thread);
+
+  /*   81 : Force Early Return - Object */
+  jvmtiError (JNICALL *ForceEarlyReturnObject) (jvmtiEnv* env,
+    jthread thread,
+    jobject value);
+
+  /*   82 : Force Early Return - Int */
+  jvmtiError (JNICALL *ForceEarlyReturnInt) (jvmtiEnv* env,
+    jthread thread,
+    jint value);
+
+  /*   83 : Force Early Return - Long */
+  jvmtiError (JNICALL *ForceEarlyReturnLong) (jvmtiEnv* env,
+    jthread thread,
+    jlong value);
+
+  /*   84 : Force Early Return - Float */
+  jvmtiError (JNICALL *ForceEarlyReturnFloat) (jvmtiEnv* env,
+    jthread thread,
+    jfloat value);
+
+  /*   85 : Force Early Return - Double */
+  jvmtiError (JNICALL *ForceEarlyReturnDouble) (jvmtiEnv* env,
+    jthread thread,
+    jdouble value);
+
+  /*   86 : Force Early Return - Void */
+  jvmtiError (JNICALL *ForceEarlyReturnVoid) (jvmtiEnv* env,
+    jthread thread);
+
+  /*   87 : Redefine Classes */
+  jvmtiError (JNICALL *RedefineClasses) (jvmtiEnv* env,
+    jint class_count,
+    const jvmtiClassDefinition* class_definitions);
+
+  /*   88 : Get Version Number */
+  jvmtiError (JNICALL *GetVersionNumber) (jvmtiEnv* env,
+    jint* version_ptr);
+
+  /*   89 : Get Capabilities */
+  jvmtiError (JNICALL *GetCapabilities) (jvmtiEnv* env,
+    jvmtiCapabilities* capabilities_ptr);
+
+  /*   90 : Get Source Debug Extension */
+  jvmtiError (JNICALL *GetSourceDebugExtension) (jvmtiEnv* env,
+    jclass klass,
+    char** source_debug_extension_ptr);
+
+  /*   91 : Is Method Obsolete */
+  jvmtiError (JNICALL *IsMethodObsolete) (jvmtiEnv* env,
+    jmethodID method,
+    jboolean* is_obsolete_ptr);
+
+  /*   92 : Suspend Thread List */
+  jvmtiError (JNICALL *SuspendThreadList) (jvmtiEnv* env,
+    jint request_count,
+    const jthread* request_list,
+    jvmtiError* results);
+
+  /*   93 : Resume Thread List */
+  jvmtiError (JNICALL *ResumeThreadList) (jvmtiEnv* env,
+    jint request_count,
+    const jthread* request_list,
+    jvmtiError* results);
+
+  /*   94 :  RESERVED */
+  void *reserved94;
+
+  /*   95 :  RESERVED */
+  void *reserved95;
+
+  /*   96 :  RESERVED */
+  void *reserved96;
+
+  /*   97 :  RESERVED */
+  void *reserved97;
+
+  /*   98 :  RESERVED */
+  void *reserved98;
+
+  /*   99 :  RESERVED */
+  void *reserved99;
+
+  /*   100 : Get All Stack Traces */
+  jvmtiError (JNICALL *GetAllStackTraces) (jvmtiEnv* env,
+    jint max_frame_count,
+    jvmtiStackInfo** stack_info_ptr,
+    jint* thread_count_ptr);
+
+  /*   101 : Get Thread List Stack Traces */
+  jvmtiError (JNICALL *GetThreadListStackTraces) (jvmtiEnv* env,
+    jint thread_count,
+    const jthread* thread_list,
+    jint max_frame_count,
+    jvmtiStackInfo** stack_info_ptr);
+
+  /*   102 : Get Thread Local Storage */
+  jvmtiError (JNICALL *GetThreadLocalStorage) (jvmtiEnv* env,
+    jthread thread,
+    void** data_ptr);
+
+  /*   103 : Set Thread Local Storage */
+  jvmtiError (JNICALL *SetThreadLocalStorage) (jvmtiEnv* env,
+    jthread thread,
+    const void* data);
+
+  /*   104 : Get Stack Trace */
+  jvmtiError (JNICALL *GetStackTrace) (jvmtiEnv* env,
+    jthread thread,
+    jint start_depth,
+    jint max_frame_count,
+    jvmtiFrameInfo* frame_buffer,
+    jint* count_ptr);
+
+  /*   105 :  RESERVED */
+  void *reserved105;
+
+  /*   106 : Get Tag */
+  jvmtiError (JNICALL *GetTag) (jvmtiEnv* env,
+    jobject object,
+    jlong* tag_ptr);
+
+  /*   107 : Set Tag */
+  jvmtiError (JNICALL *SetTag) (jvmtiEnv* env,
+    jobject object,
+    jlong tag);
+
+  /*   108 : Force Garbage Collection */
+  jvmtiError (JNICALL *ForceGarbageCollection) (jvmtiEnv* env);
+
+  /*   109 : Iterate Over Objects Reachable From Object */
+  jvmtiError (JNICALL *IterateOverObjectsReachableFromObject) (jvmtiEnv* env,
+    jobject object,
+    jvmtiObjectReferenceCallback object_reference_callback,
+    const void* user_data);
+
+  /*   110 : Iterate Over Reachable Objects */
+  jvmtiError (JNICALL *IterateOverReachableObjects) (jvmtiEnv* env,
+    jvmtiHeapRootCallback heap_root_callback,
+    jvmtiStackReferenceCallback stack_ref_callback,
+    jvmtiObjectReferenceCallback object_ref_callback,
+    const void* user_data);
+
+  /*   111 : Iterate Over Heap */
+  jvmtiError (JNICALL *IterateOverHeap) (jvmtiEnv* env,
+    jvmtiHeapObjectFilter object_filter,
+    jvmtiHeapObjectCallback heap_object_callback,
+    const void* user_data);
+
+  /*   112 : Iterate Over Instances Of Class */
+  jvmtiError (JNICALL *IterateOverInstancesOfClass) (jvmtiEnv* env,
+    jclass klass,
+    jvmtiHeapObjectFilter object_filter,
+    jvmtiHeapObjectCallback heap_object_callback,
+    const void* user_data);
+
+  /*   113 :  RESERVED */
+  void *reserved113;
+
+  /*   114 : Get Objects With Tags */
+  jvmtiError (JNICALL *GetObjectsWithTags) (jvmtiEnv* env,
+    jint tag_count,
+    const jlong* tags,
+    jint* count_ptr,
+    jobject** object_result_ptr,
+    jlong** tag_result_ptr);
+
+  /*   115 : Follow References */
+  jvmtiError (JNICALL *FollowReferences) (jvmtiEnv* env,
+    jint heap_filter,
+    jclass klass,
+    jobject initial_object,
+    const jvmtiHeapCallbacks* callbacks,
+    const void* user_data);
+
+  /*   116 : Iterate Through Heap */
+  jvmtiError (JNICALL *IterateThroughHeap) (jvmtiEnv* env,
+    jint heap_filter,
+    jclass klass,
+    const jvmtiHeapCallbacks* callbacks,
+    const void* user_data);
+
+  /*   117 :  RESERVED */
+  void *reserved117;
+
+  /*   118 :  RESERVED */
+  void *reserved118;
+
+  /*   119 :  RESERVED */
+  void *reserved119;
+
+  /*   120 : Set JNI Function Table */
+  jvmtiError (JNICALL *SetJNIFunctionTable) (jvmtiEnv* env,
+    const jniNativeInterface* function_table);
+
+  /*   121 : Get JNI Function Table */
+  jvmtiError (JNICALL *GetJNIFunctionTable) (jvmtiEnv* env,
+    jniNativeInterface** function_table);
+
+  /*   122 : Set Event Callbacks */
+  jvmtiError (JNICALL *SetEventCallbacks) (jvmtiEnv* env,
+    const jvmtiEventCallbacks* callbacks,
+    jint size_of_callbacks);
+
+  /*   123 : Generate Events */
+  jvmtiError (JNICALL *GenerateEvents) (jvmtiEnv* env,
+    jvmtiEvent event_type);
+
+  /*   124 : Get Extension Functions */
+  jvmtiError (JNICALL *GetExtensionFunctions) (jvmtiEnv* env,
+    jint* extension_count_ptr,
+    jvmtiExtensionFunctionInfo** extensions);
+
+  /*   125 : Get Extension Events */
+  jvmtiError (JNICALL *GetExtensionEvents) (jvmtiEnv* env,
+    jint* extension_count_ptr,
+    jvmtiExtensionEventInfo** extensions);
+
+  /*   126 : Set Extension Event Callback */
+  jvmtiError (JNICALL *SetExtensionEventCallback) (jvmtiEnv* env,
+    jint extension_event_index,
+    jvmtiExtensionEvent callback);
+
+  /*   127 : Dispose Environment */
+  jvmtiError (JNICALL *DisposeEnvironment) (jvmtiEnv* env);
+
+  /*   128 : Get Error Name */
+  jvmtiError (JNICALL *GetErrorName) (jvmtiEnv* env,
+    jvmtiError error,
+    char** name_ptr);
+
+  /*   129 : Get JLocation Format */
+  jvmtiError (JNICALL *GetJLocationFormat) (jvmtiEnv* env,
+    jvmtiJlocationFormat* format_ptr);
+
+  /*   130 : Get System Properties */
+  jvmtiError (JNICALL *GetSystemProperties) (jvmtiEnv* env,
+    jint* count_ptr,
+    char*** property_ptr);
+
+  /*   131 : Get System Property */
+  jvmtiError (JNICALL *GetSystemProperty) (jvmtiEnv* env,
+    const char* property,
+    char** value_ptr);
+
+  /*   132 : Set System Property */
+  jvmtiError (JNICALL *SetSystemProperty) (jvmtiEnv* env,
+    const char* property,
+    const char* value);
+
+  /*   133 : Get Phase */
+  jvmtiError (JNICALL *GetPhase) (jvmtiEnv* env,
+    jvmtiPhase* phase_ptr);
+
+  /*   134 : Get Current Thread CPU Timer Information */
+  jvmtiError (JNICALL *GetCurrentThreadCpuTimerInfo) (jvmtiEnv* env,
+    jvmtiTimerInfo* info_ptr);
+
+  /*   135 : Get Current Thread CPU Time */
+  jvmtiError (JNICALL *GetCurrentThreadCpuTime) (jvmtiEnv* env,
+    jlong* nanos_ptr);
+
+  /*   136 : Get Thread CPU Timer Information */
+  jvmtiError (JNICALL *GetThreadCpuTimerInfo) (jvmtiEnv* env,
+    jvmtiTimerInfo* info_ptr);
+
+  /*   137 : Get Thread CPU Time */
+  jvmtiError (JNICALL *GetThreadCpuTime) (jvmtiEnv* env,
+    jthread thread,
+    jlong* nanos_ptr);
+
+  /*   138 : Get Timer Information */
+  jvmtiError (JNICALL *GetTimerInfo) (jvmtiEnv* env,
+    jvmtiTimerInfo* info_ptr);
+
+  /*   139 : Get Time */
+  jvmtiError (JNICALL *GetTime) (jvmtiEnv* env,
+    jlong* nanos_ptr);
+
+  /*   140 : Get Potential Capabilities */
+  jvmtiError (JNICALL *GetPotentialCapabilities) (jvmtiEnv* env,
+    jvmtiCapabilities* capabilities_ptr);
+
+  /*   141 :  RESERVED */
+  void *reserved141;
+
+  /*   142 : Add Capabilities */
+  jvmtiError (JNICALL *AddCapabilities) (jvmtiEnv* env,
+    const jvmtiCapabilities* capabilities_ptr);
+
+  /*   143 : Relinquish Capabilities */
+  jvmtiError (JNICALL *RelinquishCapabilities) (jvmtiEnv* env,
+    const jvmtiCapabilities* capabilities_ptr);
+
+  /*   144 : Get Available Processors */
+  jvmtiError (JNICALL *GetAvailableProcessors) (jvmtiEnv* env,
+    jint* processor_count_ptr);
+
+  /*   145 : Get Class Version Numbers */
+  jvmtiError (JNICALL *GetClassVersionNumbers) (jvmtiEnv* env,
+    jclass klass,
+    jint* minor_version_ptr,
+    jint* major_version_ptr);
+
+  /*   146 : Get Constant Pool */
+  jvmtiError (JNICALL *GetConstantPool) (jvmtiEnv* env,
+    jclass klass,
+    jint* constant_pool_count_ptr,
+    jint* constant_pool_byte_count_ptr,
+    unsigned char** constant_pool_bytes_ptr);
+
+  /*   147 : Get Environment Local Storage */
+  jvmtiError (JNICALL *GetEnvironmentLocalStorage) (jvmtiEnv* env,
+    void** data_ptr);
+
+  /*   148 : Set Environment Local Storage */
+  jvmtiError (JNICALL *SetEnvironmentLocalStorage) (jvmtiEnv* env,
+    const void* data);
+
+  /*   149 : Add To Bootstrap Class Loader Search */
+  jvmtiError (JNICALL *AddToBootstrapClassLoaderSearch) (jvmtiEnv* env,
+    const char* segment);
+
+  /*   150 : Set Verbose Flag */
+  jvmtiError (JNICALL *SetVerboseFlag) (jvmtiEnv* env,
+    jvmtiVerboseFlag flag,
+    jboolean value);
+
+  /*   151 : Add To System Class Loader Search */
+  jvmtiError (JNICALL *AddToSystemClassLoaderSearch) (jvmtiEnv* env,
+    const char* segment);
+
+  /*   152 : Retransform Classes */
+  jvmtiError (JNICALL *RetransformClasses) (jvmtiEnv* env,
+    jint class_count,
+    const jclass* classes);
+
+  /*   153 : Get Owned Monitor Stack Depth Info */
+  jvmtiError (JNICALL *GetOwnedMonitorStackDepthInfo) (jvmtiEnv* env,
+    jthread thread,
+    jint* monitor_info_count_ptr,
+    jvmtiMonitorStackDepthInfo** monitor_info_ptr);
+
+  /*   154 : Get Object Size */
+  jvmtiError (JNICALL *GetObjectSize) (jvmtiEnv* env,
+    jobject object,
+    jlong* size_ptr);
+
+  /*   155 : Get Local Instance */
+  jvmtiError (JNICALL *GetLocalInstance) (jvmtiEnv* env,
+    jthread thread,
+    jint depth,
+    jobject* value_ptr);
+
+} jvmtiInterface_1;
+
+struct _jvmtiEnv {
+    const struct jvmtiInterface_1_ *functions;
+#ifdef __cplusplus
+
+
+  jvmtiError Allocate(jlong size,
+            unsigned char** mem_ptr) {
+    return functions->Allocate(this, size, mem_ptr);
+  }
+
+  jvmtiError Deallocate(unsigned char* mem) {
+    return functions->Deallocate(this, mem);
+  }
+
+  jvmtiError GetThreadState(jthread thread,
+            jint* thread_state_ptr) {
+    return functions->GetThreadState(this, thread, thread_state_ptr);
+  }
+
+  jvmtiError GetCurrentThread(jthread* thread_ptr) {
+    return functions->GetCurrentThread(this, thread_ptr);
+  }
+
+  jvmtiError GetAllThreads(jint* threads_count_ptr,
+            jthread** threads_ptr) {
+    return functions->GetAllThreads(this, threads_count_ptr, threads_ptr);
+  }
+
+  jvmtiError SuspendThread(jthread thread) {
+    return functions->SuspendThread(this, thread);
+  }
+
+  jvmtiError SuspendThreadList(jint request_count,
+            const jthread* request_list,
+            jvmtiError* results) {
+    return functions->SuspendThreadList(this, request_count, request_list, results);
+  }
+
+  jvmtiError ResumeThread(jthread thread) {
+    return functions->ResumeThread(this, thread);
+  }
+
+  jvmtiError ResumeThreadList(jint request_count,
+            const jthread* request_list,
+            jvmtiError* results) {
+    return functions->ResumeThreadList(this, request_count, request_list, results);
+  }
+
+  jvmtiError StopThread(jthread thread,
+            jobject exception) {
+    return functions->StopThread(this, thread, exception);
+  }
+
+  jvmtiError InterruptThread(jthread thread) {
+    return functions->InterruptThread(this, thread);
+  }
+
+  jvmtiError GetThreadInfo(jthread thread,
+            jvmtiThreadInfo* info_ptr) {
+    return functions->GetThreadInfo(this, thread, info_ptr);
+  }
+
+  jvmtiError GetOwnedMonitorInfo(jthread thread,
+            jint* owned_monitor_count_ptr,
+            jobject** owned_monitors_ptr) {
+    return functions->GetOwnedMonitorInfo(this, thread, owned_monitor_count_ptr, owned_monitors_ptr);
+  }
+
+  jvmtiError GetOwnedMonitorStackDepthInfo(jthread thread,
+            jint* monitor_info_count_ptr,
+            jvmtiMonitorStackDepthInfo** monitor_info_ptr) {
+    return functions->GetOwnedMonitorStackDepthInfo(this, thread, monitor_info_count_ptr, monitor_info_ptr);
+  }
+
+  jvmtiError GetCurrentContendedMonitor(jthread thread,
+            jobject* monitor_ptr) {
+    return functions->GetCurrentContendedMonitor(this, thread, monitor_ptr);
+  }
+
+  jvmtiError RunAgentThread(jthread thread,
+            jvmtiStartFunction proc,
+            const void* arg,
+            jint priority) {
+    return functions->RunAgentThread(this, thread, proc, arg, priority);
+  }
+
+  jvmtiError SetThreadLocalStorage(jthread thread,
+            const void* data) {
+    return functions->SetThreadLocalStorage(this, thread, data);
+  }
+
+  jvmtiError GetThreadLocalStorage(jthread thread,
+            void** data_ptr) {
+    return functions->GetThreadLocalStorage(this, thread, data_ptr);
+  }
+
+  jvmtiError GetTopThreadGroups(jint* group_count_ptr,
+            jthreadGroup** groups_ptr) {
+    return functions->GetTopThreadGroups(this, group_count_ptr, groups_ptr);
+  }
+
+  jvmtiError GetThreadGroupInfo(jthreadGroup group,
+            jvmtiThreadGroupInfo* info_ptr) {
+    return functions->GetThreadGroupInfo(this, group, info_ptr);
+  }
+
+  jvmtiError GetThreadGroupChildren(jthreadGroup group,
+            jint* thread_count_ptr,
+            jthread** threads_ptr,
+            jint* group_count_ptr,
+            jthreadGroup** groups_ptr) {
+    return functions->GetThreadGroupChildren(this, group, thread_count_ptr, threads_ptr, group_count_ptr, groups_ptr);
+  }
+
+  jvmtiError GetStackTrace(jthread thread,
+            jint start_depth,
+            jint max_frame_count,
+            jvmtiFrameInfo* frame_buffer,
+            jint* count_ptr) {
+    return functions->GetStackTrace(this, thread, start_depth, max_frame_count, frame_buffer, count_ptr);
+  }
+
+  jvmtiError GetAllStackTraces(jint max_frame_count,
+            jvmtiStackInfo** stack_info_ptr,
+            jint* thread_count_ptr) {
+    return functions->GetAllStackTraces(this, max_frame_count, stack_info_ptr, thread_count_ptr);
+  }
+
+  jvmtiError GetThreadListStackTraces(jint thread_count,
+            const jthread* thread_list,
+            jint max_frame_count,
+            jvmtiStackInfo** stack_info_ptr) {
+    return functions->GetThreadListStackTraces(this, thread_count, thread_list, max_frame_count, stack_info_ptr);
+  }
+
+  jvmtiError GetFrameCount(jthread thread,
+            jint* count_ptr) {
+    return functions->GetFrameCount(this, thread, count_ptr);
+  }
+
+  jvmtiError PopFrame(jthread thread) {
+    return functions->PopFrame(this, thread);
+  }
+
+  jvmtiError GetFrameLocation(jthread thread,
+            jint depth,
+            jmethodID* method_ptr,
+            jlocation* location_ptr) {
+    return functions->GetFrameLocation(this, thread, depth, method_ptr, location_ptr);
+  }
+
+  jvmtiError NotifyFramePop(jthread thread,
+            jint depth) {
+    return functions->NotifyFramePop(this, thread, depth);
+  }
+
+  jvmtiError ForceEarlyReturnObject(jthread thread,
+            jobject value) {
+    return functions->ForceEarlyReturnObject(this, thread, value);
+  }
+
+  jvmtiError ForceEarlyReturnInt(jthread thread,
+            jint value) {
+    return functions->ForceEarlyReturnInt(this, thread, value);
+  }
+
+  jvmtiError ForceEarlyReturnLong(jthread thread,
+            jlong value) {
+    return functions->ForceEarlyReturnLong(this, thread, value);
+  }
+
+  jvmtiError ForceEarlyReturnFloat(jthread thread,
+            jfloat value) {
+    return functions->ForceEarlyReturnFloat(this, thread, value);
+  }
+
+  jvmtiError ForceEarlyReturnDouble(jthread thread,
+            jdouble value) {
+    return functions->ForceEarlyReturnDouble(this, thread, value);
+  }
+
+  jvmtiError ForceEarlyReturnVoid(jthread thread) {
+    return functions->ForceEarlyReturnVoid(this, thread);
+  }
+
+  jvmtiError FollowReferences(jint heap_filter,
+            jclass klass,
+            jobject initial_object,
+            const jvmtiHeapCallbacks* callbacks,
+            const void* user_data) {
+    return functions->FollowReferences(this, heap_filter, klass, initial_object, callbacks, user_data);
+  }
+
+  jvmtiError IterateThroughHeap(jint heap_filter,
+            jclass klass,
+            const jvmtiHeapCallbacks* callbacks,
+            const void* user_data) {
+    return functions->IterateThroughHeap(this, heap_filter, klass, callbacks, user_data);
+  }
+
+  jvmtiError GetTag(jobject object,
+            jlong* tag_ptr) {
+    return functions->GetTag(this, object, tag_ptr);
+  }
+
+  jvmtiError SetTag(jobject object,
+            jlong tag) {
+    return functions->SetTag(this, object, tag);
+  }
+
+  jvmtiError GetObjectsWithTags(jint tag_count,
+            const jlong* tags,
+            jint* count_ptr,
+            jobject** object_result_ptr,
+            jlong** tag_result_ptr) {
+    return functions->GetObjectsWithTags(this, tag_count, tags, count_ptr, object_result_ptr, tag_result_ptr);
+  }
+
+  jvmtiError ForceGarbageCollection() {
+    return functions->ForceGarbageCollection(this);
+  }
+
+  jvmtiError IterateOverObjectsReachableFromObject(jobject object,
+            jvmtiObjectReferenceCallback object_reference_callback,
+            const void* user_data) {
+    return functions->IterateOverObjectsReachableFromObject(this, object, object_reference_callback, user_data);
+  }
+
+  jvmtiError IterateOverReachableObjects(jvmtiHeapRootCallback heap_root_callback,
+            jvmtiStackReferenceCallback stack_ref_callback,
+            jvmtiObjectReferenceCallback object_ref_callback,
+            const void* user_data) {
+    return functions->IterateOverReachableObjects(this, heap_root_callback, stack_ref_callback, object_ref_callback, user_data);
+  }
+
+  jvmtiError IterateOverHeap(jvmtiHeapObjectFilter object_filter,
+            jvmtiHeapObjectCallback heap_object_callback,
+            const void* user_data) {
+    return functions->IterateOverHeap(this, object_filter, heap_object_callback, user_data);
+  }
+
+  jvmtiError IterateOverInstancesOfClass(jclass klass,
+            jvmtiHeapObjectFilter object_filter,
+            jvmtiHeapObjectCallback heap_object_callback,
+            const void* user_data) {
+    return functions->IterateOverInstancesOfClass(this, klass, object_filter, heap_object_callback, user_data);
+  }
+
+  jvmtiError GetLocalObject(jthread thread,
+            jint depth,
+            jint slot,
+            jobject* value_ptr) {
+    return functions->GetLocalObject(this, thread, depth, slot, value_ptr);
+  }
+
+  jvmtiError GetLocalInstance(jthread thread,
+            jint depth,
+            jobject* value_ptr) {
+    return functions->GetLocalInstance(this, thread, depth, value_ptr);
+  }
+
+  jvmtiError GetLocalInt(jthread thread,
+            jint depth,
+            jint slot,
+            jint* value_ptr) {
+    return functions->GetLocalInt(this, thread, depth, slot, value_ptr);
+  }
+
+  jvmtiError GetLocalLong(jthread thread,
+            jint depth,
+            jint slot,
+            jlong* value_ptr) {
+    return functions->GetLocalLong(this, thread, depth, slot, value_ptr);
+  }
+
+  jvmtiError GetLocalFloat(jthread thread,
+            jint depth,
+            jint slot,
+            jfloat* value_ptr) {
+    return functions->GetLocalFloat(this, thread, depth, slot, value_ptr);
+  }
+
+  jvmtiError GetLocalDouble(jthread thread,
+            jint depth,
+            jint slot,
+            jdouble* value_ptr) {
+    return functions->GetLocalDouble(this, thread, depth, slot, value_ptr);
+  }
+
+  jvmtiError SetLocalObject(jthread thread,
+            jint depth,
+            jint slot,
+            jobject value) {
+    return functions->SetLocalObject(this, thread, depth, slot, value);
+  }
+
+  jvmtiError SetLocalInt(jthread thread,
+            jint depth,
+            jint slot,
+            jint value) {
+    return functions->SetLocalInt(this, thread, depth, slot, value);
+  }
+
+  jvmtiError SetLocalLong(jthread thread,
+            jint depth,
+            jint slot,
+            jlong value) {
+    return functions->SetLocalLong(this, thread, depth, slot, value);
+  }
+
+  jvmtiError SetLocalFloat(jthread thread,
+            jint depth,
+            jint slot,
+            jfloat value) {
+    return functions->SetLocalFloat(this, thread, depth, slot, value);
+  }
+
+  jvmtiError SetLocalDouble(jthread thread,
+            jint depth,
+            jint slot,
+            jdouble value) {
+    return functions->SetLocalDouble(this, thread, depth, slot, value);
+  }
+
+  jvmtiError SetBreakpoint(jmethodID method,
+            jlocation location) {
+    return functions->SetBreakpoint(this, method, location);
+  }
+
+  jvmtiError ClearBreakpoint(jmethodID method,
+            jlocation location) {
+    return functions->ClearBreakpoint(this, method, location);
+  }
+
+  jvmtiError SetFieldAccessWatch(jclass klass,
+            jfieldID field) {
+    return functions->SetFieldAccessWatch(this, klass, field);
+  }
+
+  jvmtiError ClearFieldAccessWatch(jclass klass,
+            jfieldID field) {
+    return functions->ClearFieldAccessWatch(this, klass, field);
+  }
+
+  jvmtiError SetFieldModificationWatch(jclass klass,
+            jfieldID field) {
+    return functions->SetFieldModificationWatch(this, klass, field);
+  }
+
+  jvmtiError ClearFieldModificationWatch(jclass klass,
+            jfieldID field) {
+    return functions->ClearFieldModificationWatch(this, klass, field);
+  }
+
+  jvmtiError GetLoadedClasses(jint* class_count_ptr,
+            jclass** classes_ptr) {
+    return functions->GetLoadedClasses(this, class_count_ptr, classes_ptr);
+  }
+
+  jvmtiError GetClassLoaderClasses(jobject initiating_loader,
+            jint* class_count_ptr,
+            jclass** classes_ptr) {
+    return functions->GetClassLoaderClasses(this, initiating_loader, class_count_ptr, classes_ptr);
+  }
+
+  jvmtiError GetClassSignature(jclass klass,
+            char** signature_ptr,
+            char** generic_ptr) {
+    return functions->GetClassSignature(this, klass, signature_ptr, generic_ptr);
+  }
+
+  jvmtiError GetClassStatus(jclass klass,
+            jint* status_ptr) {
+    return functions->GetClassStatus(this, klass, status_ptr);
+  }
+
+  jvmtiError GetSourceFileName(jclass klass,
+            char** source_name_ptr) {
+    return functions->GetSourceFileName(this, klass, source_name_ptr);
+  }
+
+  jvmtiError GetClassModifiers(jclass klass,
+            jint* modifiers_ptr) {
+    return functions->GetClassModifiers(this, klass, modifiers_ptr);
+  }
+
+  jvmtiError GetClassMethods(jclass klass,
+            jint* method_count_ptr,
+            jmethodID** methods_ptr) {
+    return functions->GetClassMethods(this, klass, method_count_ptr, methods_ptr);
+  }
+
+  jvmtiError GetClassFields(jclass klass,
+            jint* field_count_ptr,
+            jfieldID** fields_ptr) {
+    return functions->GetClassFields(this, klass, field_count_ptr, fields_ptr);
+  }
+
+  jvmtiError GetImplementedInterfaces(jclass klass,
+            jint* interface_count_ptr,
+            jclass** interfaces_ptr) {
+    return functions->GetImplementedInterfaces(this, klass, interface_count_ptr, interfaces_ptr);
+  }
+
+  jvmtiError GetClassVersionNumbers(jclass klass,
+            jint* minor_version_ptr,
+            jint* major_version_ptr) {
+    return functions->GetClassVersionNumbers(this, klass, minor_version_ptr, major_version_ptr);
+  }
+
+  jvmtiError GetConstantPool(jclass klass,
+            jint* constant_pool_count_ptr,
+            jint* constant_pool_byte_count_ptr,
+            unsigned char** constant_pool_bytes_ptr) {
+    return functions->GetConstantPool(this, klass, constant_pool_count_ptr, constant_pool_byte_count_ptr, constant_pool_bytes_ptr);
+  }
+
+  jvmtiError IsInterface(jclass klass,
+            jboolean* is_interface_ptr) {
+    return functions->IsInterface(this, klass, is_interface_ptr);
+  }
+
+  jvmtiError IsArrayClass(jclass klass,
+            jboolean* is_array_class_ptr) {
+    return functions->IsArrayClass(this, klass, is_array_class_ptr);
+  }
+
+  jvmtiError IsModifiableClass(jclass klass,
+            jboolean* is_modifiable_class_ptr) {
+    return functions->IsModifiableClass(this, klass, is_modifiable_class_ptr);
+  }
+
+  jvmtiError GetClassLoader(jclass klass,
+            jobject* classloader_ptr) {
+    return functions->GetClassLoader(this, klass, classloader_ptr);
+  }
+
+  jvmtiError GetSourceDebugExtension(jclass klass,
+            char** source_debug_extension_ptr) {
+    return functions->GetSourceDebugExtension(this, klass, source_debug_extension_ptr);
+  }
+
+  jvmtiError RetransformClasses(jint class_count,
+            const jclass* classes) {
+    return functions->RetransformClasses(this, class_count, classes);
+  }
+
+  jvmtiError RedefineClasses(jint class_count,
+            const jvmtiClassDefinition* class_definitions) {
+    return functions->RedefineClasses(this, class_count, class_definitions);
+  }
+
+  jvmtiError GetObjectSize(jobject object,
+            jlong* size_ptr) {
+    return functions->GetObjectSize(this, object, size_ptr);
+  }
+
+  jvmtiError GetObjectHashCode(jobject object,
+            jint* hash_code_ptr) {
+    return functions->GetObjectHashCode(this, object, hash_code_ptr);
+  }
+
+  jvmtiError GetObjectMonitorUsage(jobject object,
+            jvmtiMonitorUsage* info_ptr) {
+    return functions->GetObjectMonitorUsage(this, object, info_ptr);
+  }
+
+  jvmtiError GetFieldName(jclass klass,
+            jfieldID field,
+            char** name_ptr,
+            char** signature_ptr,
+            char** generic_ptr) {
+    return functions->GetFieldName(this, klass, field, name_ptr, signature_ptr, generic_ptr);
+  }
+
+  jvmtiError GetFieldDeclaringClass(jclass klass,
+            jfieldID field,
+            jclass* declaring_class_ptr) {
+    return functions->GetFieldDeclaringClass(this, klass, field, declaring_class_ptr);
+  }
+
+  jvmtiError GetFieldModifiers(jclass klass,
+            jfieldID field,
+            jint* modifiers_ptr) {
+    return functions->GetFieldModifiers(this, klass, field, modifiers_ptr);
+  }
+
+  jvmtiError IsFieldSynthetic(jclass klass,
+            jfieldID field,
+            jboolean* is_synthetic_ptr) {
+    return functions->IsFieldSynthetic(this, klass, field, is_synthetic_ptr);
+  }
+
+  jvmtiError GetMethodName(jmethodID method,
+            char** name_ptr,
+            char** signature_ptr,
+            char** generic_ptr) {
+    return functions->GetMethodName(this, method, name_ptr, signature_ptr, generic_ptr);
+  }
+
+  jvmtiError GetMethodDeclaringClass(jmethodID method,
+            jclass* declaring_class_ptr) {
+    return functions->GetMethodDeclaringClass(this, method, declaring_class_ptr);
+  }
+
+  jvmtiError GetMethodModifiers(jmethodID method,
+            jint* modifiers_ptr) {
+    return functions->GetMethodModifiers(this, method, modifiers_ptr);
+  }
+
+  jvmtiError GetMaxLocals(jmethodID method,
+            jint* max_ptr) {
+    return functions->GetMaxLocals(this, method, max_ptr);
+  }
+
+  jvmtiError GetArgumentsSize(jmethodID method,
+            jint* size_ptr) {
+    return functions->GetArgumentsSize(this, method, size_ptr);
+  }
+
+  jvmtiError GetLineNumberTable(jmethodID method,
+            jint* entry_count_ptr,
+            jvmtiLineNumberEntry** table_ptr) {
+    return functions->GetLineNumberTable(this, method, entry_count_ptr, table_ptr);
+  }
+
+  jvmtiError GetMethodLocation(jmethodID method,
+            jlocation* start_location_ptr,
+            jlocation* end_location_ptr) {
+    return functions->GetMethodLocation(this, method, start_location_ptr, end_location_ptr);
+  }
+
+  jvmtiError GetLocalVariableTable(jmethodID method,
+            jint* entry_count_ptr,
+            jvmtiLocalVariableEntry** table_ptr) {
+    return functions->GetLocalVariableTable(this, method, entry_count_ptr, table_ptr);
+  }
+
+  jvmtiError GetBytecodes(jmethodID method,
+            jint* bytecode_count_ptr,
+            unsigned char** bytecodes_ptr) {
+    return functions->GetBytecodes(this, method, bytecode_count_ptr, bytecodes_ptr);
+  }
+
+  jvmtiError IsMethodNative(jmethodID method,
+            jboolean* is_native_ptr) {
+    return functions->IsMethodNative(this, method, is_native_ptr);
+  }
+
+  jvmtiError IsMethodSynthetic(jmethodID method,
+            jboolean* is_synthetic_ptr) {
+    return functions->IsMethodSynthetic(this, method, is_synthetic_ptr);
+  }
+
+  jvmtiError IsMethodObsolete(jmethodID method,
+            jboolean* is_obsolete_ptr) {
+    return functions->IsMethodObsolete(this, method, is_obsolete_ptr);
+  }
+
+  jvmtiError SetNativeMethodPrefix(const char* prefix) {
+    return functions->SetNativeMethodPrefix(this, prefix);
+  }
+
+  jvmtiError SetNativeMethodPrefixes(jint prefix_count,
+            char** prefixes) {
+    return functions->SetNativeMethodPrefixes(this, prefix_count, prefixes);
+  }
+
+  jvmtiError CreateRawMonitor(const char* name,
+            jrawMonitorID* monitor_ptr) {
+    return functions->CreateRawMonitor(this, name, monitor_ptr);
+  }
+
+  jvmtiError DestroyRawMonitor(jrawMonitorID monitor) {
+    return functions->DestroyRawMonitor(this, monitor);
+  }
+
+  jvmtiError RawMonitorEnter(jrawMonitorID monitor) {
+    return functions->RawMonitorEnter(this, monitor);
+  }
+
+  jvmtiError RawMonitorExit(jrawMonitorID monitor) {
+    return functions->RawMonitorExit(this, monitor);
+  }
+
+  jvmtiError RawMonitorWait(jrawMonitorID monitor,
+            jlong millis) {
+    return functions->RawMonitorWait(this, monitor, millis);
+  }
+
+  jvmtiError RawMonitorNotify(jrawMonitorID monitor) {
+    return functions->RawMonitorNotify(this, monitor);
+  }
+
+  jvmtiError RawMonitorNotifyAll(jrawMonitorID monitor) {
+    return functions->RawMonitorNotifyAll(this, monitor);
+  }
+
+  jvmtiError SetJNIFunctionTable(const jniNativeInterface* function_table) {
+    return functions->SetJNIFunctionTable(this, function_table);
+  }
+
+  jvmtiError GetJNIFunctionTable(jniNativeInterface** function_table) {
+    return functions->GetJNIFunctionTable(this, function_table);
+  }
+
+  jvmtiError SetEventCallbacks(const jvmtiEventCallbacks* callbacks,
+            jint size_of_callbacks) {
+    return functions->SetEventCallbacks(this, callbacks, size_of_callbacks);
+  }
+
+  jvmtiError SetEventNotificationMode(jvmtiEventMode mode,
+            jvmtiEvent event_type,
+            jthread event_thread,
+             ...) {
+    return functions->SetEventNotificationMode(this, mode, event_type, event_thread);
+  }
+
+  jvmtiError GenerateEvents(jvmtiEvent event_type) {
+    return functions->GenerateEvents(this, event_type);
+  }
+
+  jvmtiError GetExtensionFunctions(jint* extension_count_ptr,
+            jvmtiExtensionFunctionInfo** extensions) {
+    return functions->GetExtensionFunctions(this, extension_count_ptr, extensions);
+  }
+
+  jvmtiError GetExtensionEvents(jint* extension_count_ptr,
+            jvmtiExtensionEventInfo** extensions) {
+    return functions->GetExtensionEvents(this, extension_count_ptr, extensions);
+  }
+
+  jvmtiError SetExtensionEventCallback(jint extension_event_index,
+            jvmtiExtensionEvent callback) {
+    return functions->SetExtensionEventCallback(this, extension_event_index, callback);
+  }
+
+  jvmtiError GetPotentialCapabilities(jvmtiCapabilities* capabilities_ptr) {
+    return functions->GetPotentialCapabilities(this, capabilities_ptr);
+  }
+
+  jvmtiError AddCapabilities(const jvmtiCapabilities* capabilities_ptr) {
+    return functions->AddCapabilities(this, capabilities_ptr);
+  }
+
+  jvmtiError RelinquishCapabilities(const jvmtiCapabilities* capabilities_ptr) {
+    return functions->RelinquishCapabilities(this, capabilities_ptr);
+  }
+
+  jvmtiError GetCapabilities(jvmtiCapabilities* capabilities_ptr) {
+    return functions->GetCapabilities(this, capabilities_ptr);
+  }
+
+  jvmtiError GetCurrentThreadCpuTimerInfo(jvmtiTimerInfo* info_ptr) {
+    return functions->GetCurrentThreadCpuTimerInfo(this, info_ptr);
+  }
+
+  jvmtiError GetCurrentThreadCpuTime(jlong* nanos_ptr) {
+    return functions->GetCurrentThreadCpuTime(this, nanos_ptr);
+  }
+
+  jvmtiError GetThreadCpuTimerInfo(jvmtiTimerInfo* info_ptr) {
+    return functions->GetThreadCpuTimerInfo(this, info_ptr);
+  }
+
+  jvmtiError GetThreadCpuTime(jthread thread,
+            jlong* nanos_ptr) {
+    return functions->GetThreadCpuTime(this, thread, nanos_ptr);
+  }
+
+  jvmtiError GetTimerInfo(jvmtiTimerInfo* info_ptr) {
+    return functions->GetTimerInfo(this, info_ptr);
+  }
+
+  jvmtiError GetTime(jlong* nanos_ptr) {
+    return functions->GetTime(this, nanos_ptr);
+  }
+
+  jvmtiError GetAvailableProcessors(jint* processor_count_ptr) {
+    return functions->GetAvailableProcessors(this, processor_count_ptr);
+  }
+
+  jvmtiError AddToBootstrapClassLoaderSearch(const char* segment) {
+    return functions->AddToBootstrapClassLoaderSearch(this, segment);
+  }
+
+  jvmtiError AddToSystemClassLoaderSearch(const char* segment) {
+    return functions->AddToSystemClassLoaderSearch(this, segment);
+  }
+
+  jvmtiError GetSystemProperties(jint* count_ptr,
+            char*** property_ptr) {
+    return functions->GetSystemProperties(this, count_ptr, property_ptr);
+  }
+
+  jvmtiError GetSystemProperty(const char* property,
+            char** value_ptr) {
+    return functions->GetSystemProperty(this, property, value_ptr);
+  }
+
+  jvmtiError SetSystemProperty(const char* property,
+            const char* value) {
+    return functions->SetSystemProperty(this, property, value);
+  }
+
+  jvmtiError GetPhase(jvmtiPhase* phase_ptr) {
+    return functions->GetPhase(this, phase_ptr);
+  }
+
+  jvmtiError DisposeEnvironment() {
+    return functions->DisposeEnvironment(this);
+  }
+
+  jvmtiError SetEnvironmentLocalStorage(const void* data) {
+    return functions->SetEnvironmentLocalStorage(this, data);
+  }
+
+  jvmtiError GetEnvironmentLocalStorage(void** data_ptr) {
+    return functions->GetEnvironmentLocalStorage(this, data_ptr);
+  }
+
+  jvmtiError GetVersionNumber(jint* version_ptr) {
+    return functions->GetVersionNumber(this, version_ptr);
+  }
+
+  jvmtiError GetErrorName(jvmtiError error,
+            char** name_ptr) {
+    return functions->GetErrorName(this, error, name_ptr);
+  }
+
+  jvmtiError SetVerboseFlag(jvmtiVerboseFlag flag,
+            jboolean value) {
+    return functions->SetVerboseFlag(this, flag, value);
+  }
+
+  jvmtiError GetJLocationFormat(jvmtiJlocationFormat* format_ptr) {
+    return functions->GetJLocationFormat(this, format_ptr);
+  }
+
+#endif /* __cplusplus */
+};
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif /* __cplusplus */
+
+#endif /* !_JAVA_JVMTI_H_ */
+
diff --git a/openjdkjvmti/jvmti_allocator.h b/openjdkjvmti/jvmti_allocator.h
new file mode 100644
index 0000000..e29e034
--- /dev/null
+++ b/openjdkjvmti/jvmti_allocator.h
@@ -0,0 +1,174 @@
+/* Copyright (C) 2016 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_JVMTI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
+
+#include "base/logging.h"
+#include "base/macros.h"
+#include "jvmti.h"
+
+#include "ti_allocator.h"
+
+namespace openjdkjvmti {
+
+template <typename T> class JvmtiAllocator;
+
+template <>
+class JvmtiAllocator<void> {
+ public:
+  typedef void value_type;
+  typedef void* pointer;
+  typedef const void* const_pointer;
+
+  template <typename U>
+  struct rebind {
+    typedef JvmtiAllocator<U> other;
+  };
+
+  explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
+  explicit JvmtiAllocator() : env_(nullptr) {}
+
+  template <typename U>
+  JvmtiAllocator(const JvmtiAllocator<U>& other)  // NOLINT, implicit
+      : env_(other.env_) {}
+
+  JvmtiAllocator(const JvmtiAllocator& other) = default;
+  JvmtiAllocator& operator=(const JvmtiAllocator& other) = default;
+  ~JvmtiAllocator() = default;
+
+ private:
+  jvmtiEnv* env_;
+
+  template <typename U>
+  friend class JvmtiAllocator;
+
+  template <typename U>
+  friend bool operator==(const JvmtiAllocator<U>& lhs, const JvmtiAllocator<U>& rhs);
+};
+
+template <typename T>
+class JvmtiAllocator {
+ public:
+  typedef T value_type;
+  typedef T* pointer;
+  typedef T& reference;
+  typedef const T* const_pointer;
+  typedef const T& const_reference;
+  typedef size_t size_type;
+  typedef ptrdiff_t difference_type;
+
+  template <typename U>
+  struct rebind {
+    typedef JvmtiAllocator<U> other;
+  };
+
+  explicit JvmtiAllocator(jvmtiEnv* env) : env_(env) {}
+  explicit JvmtiAllocator() : env_(nullptr) {}
+
+  template <typename U>
+  JvmtiAllocator(const JvmtiAllocator<U>& other)  // NOLINT, implicit
+      : env_(other.env_) {}
+
+  JvmtiAllocator(const JvmtiAllocator& other) = default;
+  JvmtiAllocator& operator=(const JvmtiAllocator& other) = default;
+  ~JvmtiAllocator() = default;
+
+  size_type max_size() const {
+    return static_cast<size_type>(-1) / sizeof(T);
+  }
+
+  pointer address(reference x) const { return &x; }
+  const_pointer address(const_reference x) const { return &x; }
+
+  pointer allocate(size_type n, JvmtiAllocator<void>::pointer hint ATTRIBUTE_UNUSED = nullptr) {
+    DCHECK_LE(n, max_size());
+    if (env_ == nullptr) {
+      T* result = reinterpret_cast<T*>(AllocUtil::AllocateImpl(n * sizeof(T)));
+      CHECK(result != nullptr || n == 0u);  // Abort if AllocateImpl() fails.
+      return result;
+    } else {
+      unsigned char* result;
+      jvmtiError alloc_error = env_->Allocate(n * sizeof(T), &result);
+      CHECK(alloc_error == JVMTI_ERROR_NONE);
+      return reinterpret_cast<T*>(result);
+    }
+  }
+  void deallocate(pointer p, size_type n ATTRIBUTE_UNUSED) {
+    if (env_ == nullptr) {
+      AllocUtil::DeallocateImpl(reinterpret_cast<unsigned char*>(p));
+    } else {
+      jvmtiError dealloc_error = env_->Deallocate(reinterpret_cast<unsigned char*>(p));
+      CHECK(dealloc_error == JVMTI_ERROR_NONE);
+    }
+  }
+
+  void construct(pointer p, const_reference val) {
+    new (static_cast<void*>(p)) value_type(val);
+  }
+  template <class U, class... Args>
+  void construct(U* p, Args&&... args) {
+    ::new (static_cast<void*>(p)) U(std::forward<Args>(args)...);
+  }
+  void destroy(pointer p) {
+    p->~value_type();
+  }
+
+  inline bool operator==(JvmtiAllocator const& other) {
+    return env_ == other.env_;
+  }
+  inline bool operator!=(JvmtiAllocator const& other) {
+    return !operator==(other);
+  }
+
+ private:
+  jvmtiEnv* env_;
+
+  template <typename U>
+  friend class JvmtiAllocator;
+
+  template <typename U>
+  friend bool operator==(const JvmtiAllocator<U>& lhs, const JvmtiAllocator<U>& rhs);
+};
+
+template <typename T>
+inline bool operator==(const JvmtiAllocator<T>& lhs, const JvmtiAllocator<T>& rhs) {
+  return lhs.env_ == rhs.env_;
+}
+
+template <typename T>
+inline bool operator!=(const JvmtiAllocator<T>& lhs, const JvmtiAllocator<T>& rhs) {
+  return !(lhs == rhs);
+}
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_JVMTI_ALLOCATOR_H_
diff --git a/openjdkjvmti/jvmti_weak_table-inl.h b/openjdkjvmti/jvmti_weak_table-inl.h
new file mode 100644
index 0000000..1c82255
--- /dev/null
+++ b/openjdkjvmti/jvmti_weak_table-inl.h
@@ -0,0 +1,406 @@
+/* Copyright (C) 2017 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_JVMTI_WEAK_TABLE_INL_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
+
+#include "jvmti_weak_table.h"
+
+#include <limits>
+
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "gc/allocation_listener.h"
+#include "instrumentation.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class.h"
+#include "mirror/object.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime.h"
+
+namespace openjdkjvmti {
+
+template <typename T>
+void JvmtiWeakTable<T>::Lock() {
+  allow_disallow_lock_.ExclusiveLock(art::Thread::Current());
+}
+template <typename T>
+void JvmtiWeakTable<T>::Unlock() {
+  allow_disallow_lock_.ExclusiveUnlock(art::Thread::Current());
+}
+template <typename T>
+void JvmtiWeakTable<T>::AssertLocked() {
+  allow_disallow_lock_.AssertHeld(art::Thread::Current());
+}
+
+template <typename T>
+void JvmtiWeakTable<T>::UpdateTableWithReadBarrier() {
+  update_since_last_sweep_ = true;
+
+  auto WithReadBarrierUpdater = [&](const art::GcRoot<art::mirror::Object>& original_root,
+                                    art::mirror::Object* original_obj ATTRIBUTE_UNUSED)
+     REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return original_root.Read<art::kWithReadBarrier>();
+  };
+
+  UpdateTableWith<decltype(WithReadBarrierUpdater), kIgnoreNull>(WithReadBarrierUpdater);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::GetTagSlowPath(art::Thread* self, art::mirror::Object* obj, T* result) {
+  // Under concurrent GC, there is a window between moving objects and sweeping of system
+  // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+  // but still have from-space pointers in the table. Explicitly update the table once.
+  // Note: this will keep *all* objects in the table live, but should be a rare occurrence.
+  UpdateTableWithReadBarrier();
+  return GetTagLocked(self, obj, result);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::Remove(art::mirror::Object* obj, /* out */ T* tag) {
+  art::Thread* self = art::Thread::Current();
+  art::MutexLock mu(self, allow_disallow_lock_);
+  Wait(self);
+
+  return RemoveLocked(self, obj, tag);
+}
+template <typename T>
+bool JvmtiWeakTable<T>::RemoveLocked(art::mirror::Object* obj, T* tag) {
+  art::Thread* self = art::Thread::Current();
+  allow_disallow_lock_.AssertHeld(self);
+  Wait(self);
+
+  return RemoveLocked(self, obj, tag);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::RemoveLocked(art::Thread* self, art::mirror::Object* obj, T* tag) {
+  auto it = tagged_objects_.find(art::GcRoot<art::mirror::Object>(obj));
+  if (it != tagged_objects_.end()) {
+    if (tag != nullptr) {
+      *tag = it->second;
+    }
+    tagged_objects_.erase(it);
+    return true;
+  }
+
+  if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+    // Under concurrent GC, there is a window between moving objects and sweeping of system
+    // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+    // but still have from-space pointers in the table. Explicitly update the table once.
+    // Note: this will keep *all* objects in the table live, but should be a rare occurrence.
+
+    // Update the table.
+    UpdateTableWithReadBarrier();
+
+    // And try again.
+    return RemoveLocked(self, obj, tag);
+  }
+
+  // Not in here.
+  return false;
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::Set(art::mirror::Object* obj, T new_tag) {
+  art::Thread* self = art::Thread::Current();
+  art::MutexLock mu(self, allow_disallow_lock_);
+  Wait(self);
+
+  return SetLocked(self, obj, new_tag);
+}
+template <typename T>
+bool JvmtiWeakTable<T>::SetLocked(art::mirror::Object* obj, T new_tag) {
+  art::Thread* self = art::Thread::Current();
+  allow_disallow_lock_.AssertHeld(self);
+  Wait(self);
+
+  return SetLocked(self, obj, new_tag);
+}
+
+template <typename T>
+bool JvmtiWeakTable<T>::SetLocked(art::Thread* self, art::mirror::Object* obj, T new_tag) {
+  auto it = tagged_objects_.find(art::GcRoot<art::mirror::Object>(obj));
+  if (it != tagged_objects_.end()) {
+    it->second = new_tag;
+    return true;
+  }
+
+  if (art::kUseReadBarrier && self->GetIsGcMarking() && !update_since_last_sweep_) {
+    // Under concurrent GC, there is a window between moving objects and sweeping of system
+    // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+    // but still have from-space pointers in the table. Explicitly update the table once.
+    // Note: this will keep *all* objects in the table live, but should be a rare occurrence.
+
+    // Update the table.
+    UpdateTableWithReadBarrier();
+
+    // And try again.
+    return SetLocked(self, obj, new_tag);
+  }
+
+  // New element.
+  auto insert_it = tagged_objects_.emplace(art::GcRoot<art::mirror::Object>(obj), new_tag);
+  DCHECK(insert_it.second);
+  return false;
+}
+
+template <typename T>
+void JvmtiWeakTable<T>::Sweep(art::IsMarkedVisitor* visitor) {
+  if (DoesHandleNullOnSweep()) {
+    SweepImpl<true>(visitor);
+  } else {
+    SweepImpl<false>(visitor);
+  }
+
+  // Under concurrent GC, there is a window between moving objects and sweeping of system
+  // weaks in which mutators are active. We may receive a to-space object pointer in obj,
+  // but still have from-space pointers in the table. We explicitly update the table then
+  // to ensure we compare against to-space pointers. But we want to do this only once. Once
+  // sweeping is done, we know all objects are to-space pointers until the next GC cycle,
+  // so we re-enable the explicit update for the next marking.
+  update_since_last_sweep_ = false;
+}
+
+template <typename T>
+template <bool kHandleNull>
+void JvmtiWeakTable<T>::SweepImpl(art::IsMarkedVisitor* visitor) {
+  art::Thread* self = art::Thread::Current();
+  art::MutexLock mu(self, allow_disallow_lock_);
+
+  auto IsMarkedUpdater = [&](const art::GcRoot<art::mirror::Object>& original_root ATTRIBUTE_UNUSED,
+                             art::mirror::Object* original_obj) {
+    return visitor->IsMarked(original_obj);
+  };
+
+  UpdateTableWith<decltype(IsMarkedUpdater),
+                  kHandleNull ? kCallHandleNull : kRemoveNull>(IsMarkedUpdater);
+}
+
+template <typename T>
+template <typename Updater, typename JvmtiWeakTable<T>::TableUpdateNullTarget kTargetNull>
+ALWAYS_INLINE inline void JvmtiWeakTable<T>::UpdateTableWith(Updater& updater) {
+  // We optimistically hope that elements will still be well-distributed when re-inserting them.
+  // So play with the map mechanics, and postpone rehashing. This avoids the need of a side
+  // vector and two passes.
+  float original_max_load_factor = tagged_objects_.max_load_factor();
+  tagged_objects_.max_load_factor(std::numeric_limits<float>::max());
+  // For checking that a max load-factor actually does what we expect.
+  size_t original_bucket_count = tagged_objects_.bucket_count();
+
+  for (auto it = tagged_objects_.begin(); it != tagged_objects_.end();) {
+    DCHECK(!it->first.IsNull());
+    art::mirror::Object* original_obj = it->first.template Read<art::kWithoutReadBarrier>();
+    art::mirror::Object* target_obj = updater(it->first, original_obj);
+    if (original_obj != target_obj) {
+      if (kTargetNull == kIgnoreNull && target_obj == nullptr) {
+        // Ignore null target, don't do anything.
+      } else {
+        T tag = it->second;
+        it = tagged_objects_.erase(it);
+        if (target_obj != nullptr) {
+          tagged_objects_.emplace(art::GcRoot<art::mirror::Object>(target_obj), tag);
+          DCHECK_EQ(original_bucket_count, tagged_objects_.bucket_count());
+        } else if (kTargetNull == kCallHandleNull) {
+          HandleNullSweep(tag);
+        }
+        continue;  // Iterator was implicitly updated by erase.
+      }
+    }
+    it++;
+  }
+
+  tagged_objects_.max_load_factor(original_max_load_factor);
+  // TODO: consider rehash here.
+}
+
+template <typename T>
+template <typename Storage, class Allocator>
+struct JvmtiWeakTable<T>::ReleasableContainer {
+  using allocator_type = Allocator;
+
+  explicit ReleasableContainer(const allocator_type& alloc, size_t reserve = 10)
+      : allocator(alloc),
+        data(reserve > 0 ? allocator.allocate(reserve) : nullptr),
+        size(0),
+        capacity(reserve) {
+  }
+
+  ~ReleasableContainer() {
+    if (data != nullptr) {
+      allocator.deallocate(data, capacity);
+      capacity = 0;
+      size = 0;
+    }
+  }
+
+  Storage* Release() {
+    Storage* tmp = data;
+
+    data = nullptr;
+    size = 0;
+    capacity = 0;
+
+    return tmp;
+  }
+
+  void Resize(size_t new_capacity) {
+    CHECK_GT(new_capacity, capacity);
+
+    Storage* tmp = allocator.allocate(new_capacity);
+    DCHECK(tmp != nullptr);
+    if (data != nullptr) {
+      memcpy(tmp, data, sizeof(Storage) * size);
+    }
+    Storage* old = data;
+    data = tmp;
+    allocator.deallocate(old, capacity);
+    capacity = new_capacity;
+  }
+
+  void Pushback(const Storage& elem) {
+    if (size == capacity) {
+      size_t new_capacity = 2 * capacity + 1;
+      Resize(new_capacity);
+    }
+    data[size++] = elem;
+  }
+
+  Allocator allocator;
+  Storage* data;
+  size_t size;
+  size_t capacity;
+};
+
+template <typename T>
+jvmtiError JvmtiWeakTable<T>::GetTaggedObjects(jvmtiEnv* jvmti_env,
+                                               jint tag_count,
+                                               const T* tags,
+                                               jint* count_ptr,
+                                               jobject** object_result_ptr,
+                                               T** tag_result_ptr) {
+  if (tag_count < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (tag_count > 0) {
+    for (size_t i = 0; i != static_cast<size_t>(tag_count); ++i) {
+      if (tags[i] == 0) {
+        return ERR(ILLEGAL_ARGUMENT);
+      }
+    }
+  }
+  if (tags == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  if (count_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* self = art::Thread::Current();
+  art::MutexLock mu(self, allow_disallow_lock_);
+  Wait(self);
+
+  art::JNIEnvExt* jni_env = self->GetJniEnv();
+
+  constexpr size_t kDefaultSize = 10;
+  size_t initial_object_size;
+  size_t initial_tag_size;
+  if (tag_count == 0) {
+    initial_object_size = (object_result_ptr != nullptr) ? tagged_objects_.size() : 0;
+    initial_tag_size = (tag_result_ptr != nullptr) ? tagged_objects_.size() : 0;
+  } else {
+    initial_object_size = initial_tag_size = kDefaultSize;
+  }
+  JvmtiAllocator<void> allocator(jvmti_env);
+  ReleasableContainer<jobject, JvmtiAllocator<jobject>> selected_objects(allocator,
+                                                                         initial_object_size);
+  ReleasableContainer<T, JvmtiAllocator<T>> selected_tags(allocator, initial_tag_size);
+
+  size_t count = 0;
+  for (auto& pair : tagged_objects_) {
+    bool select;
+    if (tag_count > 0) {
+      select = false;
+      for (size_t i = 0; i != static_cast<size_t>(tag_count); ++i) {
+        if (tags[i] == pair.second) {
+          select = true;
+          break;
+        }
+      }
+    } else {
+      select = true;
+    }
+
+    if (select) {
+      art::mirror::Object* obj = pair.first.template Read<art::kWithReadBarrier>();
+      if (obj != nullptr) {
+        count++;
+        if (object_result_ptr != nullptr) {
+          selected_objects.Pushback(jni_env->AddLocalReference<jobject>(obj));
+        }
+        if (tag_result_ptr != nullptr) {
+          selected_tags.Pushback(pair.second);
+        }
+      }
+    }
+  }
+
+  if (object_result_ptr != nullptr) {
+    *object_result_ptr = selected_objects.Release();
+  }
+  if (tag_result_ptr != nullptr) {
+    *tag_result_ptr = selected_tags.Release();
+  }
+  *count_ptr = static_cast<jint>(count);
+  return ERR(NONE);
+}
+
+template <typename T>
+art::mirror::Object* JvmtiWeakTable<T>::Find(T tag) {
+  art::Thread* self = art::Thread::Current();
+  art::MutexLock mu(self, allow_disallow_lock_);
+  Wait(self);
+
+  for (auto& pair : tagged_objects_) {
+    if (tag == pair.second) {
+      art::mirror::Object* obj = pair.first.template Read<art::kWithReadBarrier>();
+      if (obj != nullptr) {
+        return obj;
+      }
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_INL_H_
diff --git a/openjdkjvmti/jvmti_weak_table.h b/openjdkjvmti/jvmti_weak_table.h
new file mode 100644
index 0000000..5a821c9
--- /dev/null
+++ b/openjdkjvmti/jvmti_weak_table.h
@@ -0,0 +1,227 @@
+/* Copyright (C) 2017 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_JVMTI_WEAK_TABLE_H_
+#define ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
+
+#include <unordered_map>
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "gc/system_weak.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jvmti.h"
+#include "jvmti_allocator.h"
+#include "mirror/object.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+// A system-weak container mapping objects to elements of the template type. This corresponds
+// to a weak hash map. For historical reasons the stored value is called "tag."
+template <typename T>
+class JvmtiWeakTable : public art::gc::SystemWeakHolder {
+ public:
+  JvmtiWeakTable()
+      : art::gc::SystemWeakHolder(art::kTaggingLockLevel),
+        update_since_last_sweep_(false) {
+  }
+
+  // Remove the mapping for the given object, returning whether such a mapping existed (and the old
+  // value).
+  ALWAYS_INLINE bool Remove(art::mirror::Object* obj, /* out */ T* tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+  ALWAYS_INLINE bool RemoveLocked(art::mirror::Object* obj, /* out */ T* tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  // Set the mapping for the given object. Returns true if this overwrites an already existing
+  // mapping.
+  ALWAYS_INLINE virtual bool Set(art::mirror::Object* obj, T tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+  ALWAYS_INLINE virtual bool SetLocked(art::mirror::Object* obj, T tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  // Return the value associated with the given object. Returns true if the mapping exists, false
+  // otherwise.
+  bool GetTag(art::mirror::Object* obj, /* out */ T* result)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_) {
+    art::Thread* self = art::Thread::Current();
+    art::MutexLock mu(self, allow_disallow_lock_);
+    Wait(self);
+
+    return GetTagLocked(self, obj, result);
+  }
+  bool GetTagLocked(art::mirror::Object* obj, /* out */ T* result)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_) {
+    art::Thread* self = art::Thread::Current();
+    allow_disallow_lock_.AssertHeld(self);
+    Wait(self);
+
+    return GetTagLocked(self, obj, result);
+  }
+
+  // Sweep the container. DO NOT CALL MANUALLY.
+  ALWAYS_INLINE void Sweep(art::IsMarkedVisitor* visitor)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+
+  // Return all objects that have a value mapping in tags.
+  ALWAYS_INLINE
+  jvmtiError GetTaggedObjects(jvmtiEnv* jvmti_env,
+                              jint tag_count,
+                              const T* tags,
+                              /* out */ jint* count_ptr,
+                              /* out */ jobject** object_result_ptr,
+                              /* out */ T** tag_result_ptr)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+
+  // Locking functions, to allow coarse-grained locking and amortization.
+  ALWAYS_INLINE  void Lock() ACQUIRE(allow_disallow_lock_);
+  ALWAYS_INLINE void Unlock() RELEASE(allow_disallow_lock_);
+  ALWAYS_INLINE void AssertLocked() ASSERT_CAPABILITY(allow_disallow_lock_);
+
+  ALWAYS_INLINE art::mirror::Object* Find(T tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+
+ protected:
+  // Should HandleNullSweep be called when Sweep detects the release of an object?
+  virtual bool DoesHandleNullOnSweep() {
+    return false;
+  }
+  // If DoesHandleNullOnSweep returns true, this function will be called.
+  virtual void HandleNullSweep(T tag ATTRIBUTE_UNUSED) {}
+
+ private:
+  ALWAYS_INLINE
+  bool SetLocked(art::Thread* self, art::mirror::Object* obj, T tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  ALWAYS_INLINE
+  bool RemoveLocked(art::Thread* self, art::mirror::Object* obj, /* out */ T* tag)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  bool GetTagLocked(art::Thread* self, art::mirror::Object* obj, /* out */ T* result)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_) {
+    auto it = tagged_objects_.find(art::GcRoot<art::mirror::Object>(obj));
+    if (it != tagged_objects_.end()) {
+      *result = it->second;
+      return true;
+    }
+
+    // Performance optimization: To avoid multiple table updates, ensure that during GC we
+    // only update once. See the comment on the implementation of GetTagSlowPath.
+    if (art::kUseReadBarrier &&
+        self != nullptr &&
+        self->GetIsGcMarking() &&
+        !update_since_last_sweep_) {
+      return GetTagSlowPath(self, obj, result);
+    }
+
+    return false;
+  }
+
+  // Slow-path for GetTag. We didn't find the object, but we might be storing from-pointers and
+  // are asked to retrieve with a to-pointer.
+  ALWAYS_INLINE
+  bool GetTagSlowPath(art::Thread* self, art::mirror::Object* obj, /* out */ T* result)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  // Update the table by doing read barriers on each element, ensuring that to-space pointers
+  // are stored.
+  ALWAYS_INLINE
+  void UpdateTableWithReadBarrier()
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  template <bool kHandleNull>
+  void SweepImpl(art::IsMarkedVisitor* visitor)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+
+  enum TableUpdateNullTarget {
+    kIgnoreNull,
+    kRemoveNull,
+    kCallHandleNull
+  };
+
+  template <typename Updater, TableUpdateNullTarget kTargetNull>
+  void UpdateTableWith(Updater& updater)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  template <typename Storage, class Allocator = JvmtiAllocator<T>>
+  struct ReleasableContainer;
+
+  struct HashGcRoot {
+    size_t operator()(const art::GcRoot<art::mirror::Object>& r) const
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      return reinterpret_cast<uintptr_t>(r.Read<art::kWithoutReadBarrier>());
+    }
+  };
+
+  struct EqGcRoot {
+    bool operator()(const art::GcRoot<art::mirror::Object>& r1,
+                    const art::GcRoot<art::mirror::Object>& r2) const
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      return r1.Read<art::kWithoutReadBarrier>() == r2.Read<art::kWithoutReadBarrier>();
+    }
+  };
+
+  using TagAllocator = JvmtiAllocator<std::pair<const art::GcRoot<art::mirror::Object>, T>>;
+  std::unordered_map<art::GcRoot<art::mirror::Object>,
+                     T,
+                     HashGcRoot,
+                     EqGcRoot,
+                     TagAllocator> tagged_objects_
+      GUARDED_BY(allow_disallow_lock_)
+      GUARDED_BY(art::Locks::mutator_lock_);
+  // To avoid repeatedly scanning the whole table, remember if we did that since the last sweep.
+  bool update_since_last_sweep_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_JVMTI_WEAK_TABLE_H_
diff --git a/openjdkjvmti/object_tagging.cc b/openjdkjvmti/object_tagging.cc
new file mode 100644
index 0000000..dcdd3ed
--- /dev/null
+++ b/openjdkjvmti/object_tagging.cc
@@ -0,0 +1,67 @@
+/* Copyright (C) 2016 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 "object_tagging.h"
+
+#include <limits>
+
+#include "art_jvmti.h"
+#include "events-inl.h"
+#include "jvmti_weak_table-inl.h"
+
+namespace openjdkjvmti {
+
+// Instantiate for jlong = JVMTI tags.
+template class JvmtiWeakTable<jlong>;
+
+bool ObjectTagTable::Set(art::mirror::Object* obj, jlong new_tag) {
+  if (new_tag == 0) {
+    jlong tmp;
+    return Remove(obj, &tmp);
+  }
+  return JvmtiWeakTable<jlong>::Set(obj, new_tag);
+}
+bool ObjectTagTable::SetLocked(art::mirror::Object* obj, jlong new_tag) {
+  if (new_tag == 0) {
+    jlong tmp;
+    return RemoveLocked(obj, &tmp);
+  }
+  return JvmtiWeakTable<jlong>::SetLocked(obj, new_tag);
+}
+
+bool ObjectTagTable::DoesHandleNullOnSweep() {
+  return event_handler_->IsEventEnabledAnywhere(ArtJvmtiEvent::kObjectFree);
+}
+void ObjectTagTable::HandleNullSweep(jlong tag) {
+  event_handler_->DispatchEvent<ArtJvmtiEvent::kObjectFree>(jvmti_env_, nullptr, tag);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/object_tagging.h b/openjdkjvmti/object_tagging.h
new file mode 100644
index 0000000..b474845
--- /dev/null
+++ b/openjdkjvmti/object_tagging.h
@@ -0,0 +1,86 @@
+/* Copyright (C) 2016 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_OBJECT_TAGGING_H_
+#define ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
+
+#include <unordered_map>
+
+#include "base/mutex.h"
+#include "globals.h"
+#include "jvmti.h"
+#include "jvmti_weak_table.h"
+#include "mirror/object.h"
+
+namespace openjdkjvmti {
+
+struct ArtJvmTiEnv;
+class EventHandler;
+
+class ObjectTagTable FINAL : public JvmtiWeakTable<jlong> {
+ public:
+  ObjectTagTable(EventHandler* event_handler, ArtJvmTiEnv* env)
+      : event_handler_(event_handler), jvmti_env_(env) {}
+
+  bool Set(art::mirror::Object* obj, jlong tag) OVERRIDE
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_);
+  bool SetLocked(art::mirror::Object* obj, jlong tag) OVERRIDE
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_);
+
+  jlong GetTagOrZero(art::mirror::Object* obj)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!allow_disallow_lock_) {
+    jlong tmp = 0;
+    GetTag(obj, &tmp);
+    return tmp;
+  }
+  jlong GetTagOrZeroLocked(art::mirror::Object* obj)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(allow_disallow_lock_) {
+    jlong tmp = 0;
+    GetTagLocked(obj, &tmp);
+    return tmp;
+  }
+
+ protected:
+  bool DoesHandleNullOnSweep() OVERRIDE;
+  void HandleNullSweep(jlong tag) OVERRIDE;
+
+ private:
+  EventHandler* event_handler_;
+  ArtJvmTiEnv* jvmti_env_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_OBJECT_TAGGING_H_
diff --git a/openjdkjvmti/ti_allocator.cc b/openjdkjvmti/ti_allocator.cc
new file mode 100644
index 0000000..575558d
--- /dev/null
+++ b/openjdkjvmti/ti_allocator.cc
@@ -0,0 +1,95 @@
+/* Copyright (C) 2016 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 "ti_allocator.h"
+
+#if defined(__APPLE__)
+// Apple doesn't have malloc.h. Just give this function a non-functional definition.
+#define malloc_usable_size(P) 0
+#else
+#include <malloc.h>
+#endif
+
+#include <atomic>
+
+#include "art_jvmti.h"
+#include "base/enums.h"
+
+namespace openjdkjvmti {
+
+std::atomic<jlong> AllocUtil::allocated;
+
+jvmtiError AllocUtil::GetGlobalJvmtiAllocationState(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                                    jlong* allocated_ptr) {
+  if (allocated_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  *allocated_ptr = allocated.load();
+  return OK;
+}
+
+jvmtiError AllocUtil::Allocate(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                               jlong size,
+                               unsigned char** mem_ptr) {
+  if (size < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (size == 0) {
+    *mem_ptr = nullptr;
+    return OK;
+  }
+  *mem_ptr = AllocateImpl(size);
+  if (UNLIKELY(*mem_ptr == nullptr)) {
+    return ERR(OUT_OF_MEMORY);
+  }
+  return OK;
+}
+
+unsigned char* AllocUtil::AllocateImpl(jlong size) {
+  unsigned char* ret = size != 0 ? reinterpret_cast<unsigned char*>(malloc(size)) : nullptr;
+  if (LIKELY(ret != nullptr)) {
+    allocated += malloc_usable_size(ret);
+  }
+  return ret;
+}
+
+jvmtiError AllocUtil::Deallocate(jvmtiEnv* env ATTRIBUTE_UNUSED, unsigned char* mem) {
+  DeallocateImpl(mem);
+  return OK;
+}
+
+void AllocUtil::DeallocateImpl(unsigned char* mem) {
+  if (mem != nullptr) {
+    allocated -= malloc_usable_size(mem);
+    free(mem);
+  }
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_allocator.h b/openjdkjvmti/ti_allocator.h
new file mode 100644
index 0000000..776cc5e
--- /dev/null
+++ b/openjdkjvmti/ti_allocator.h
@@ -0,0 +1,65 @@
+/* Copyright (C) 2016 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_TI_ALLOCATOR_H_
+#define ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include <atomic>
+#include <memory>
+
+namespace openjdkjvmti {
+
+template<typename T>
+class JvmtiAllocator;
+
+class AllocUtil {
+ public:
+  static jvmtiError Allocate(jvmtiEnv* env, jlong size, unsigned char** mem_ptr);
+  static jvmtiError Deallocate(jvmtiEnv* env, unsigned char* mem);
+  static jvmtiError GetGlobalJvmtiAllocationState(jvmtiEnv* env, jlong* total_allocated);
+
+ private:
+  static void DeallocateImpl(unsigned char* mem);
+  static unsigned char* AllocateImpl(jlong size);
+
+  static std::atomic<jlong> allocated;
+
+  template <typename T>
+  friend class JvmtiAllocator;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_ALLOCATOR_H_
+
diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc
new file mode 100644
index 0000000..f5116a8
--- /dev/null
+++ b/openjdkjvmti/ti_breakpoint.cc
@@ -0,0 +1,114 @@
+/* Copyright (C) 2017 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 <functional>
+
+#include "ti_breakpoint.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+size_t Breakpoint::hash() const {
+  return std::hash<uintptr_t> {}(reinterpret_cast<uintptr_t>(method_))
+      ^ std::hash<jlocation> {}(location_);
+}
+
+Breakpoint::Breakpoint(art::ArtMethod* m, jlocation loc) : method_(m), location_(loc) {
+  DCHECK(!m->IsDefault() || !m->IsCopied() || !m->IsInvokable())
+      << "Flags are: 0x" << std::hex << m->GetAccessFlags();
+}
+
+void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) {
+  std::vector<Breakpoint> to_remove;
+  for (const Breakpoint& b : env->breakpoints) {
+    if (b.GetMethod()->GetDeclaringClass() == klass) {
+      to_remove.push_back(b);
+    }
+  }
+  for (const Breakpoint& b : to_remove) {
+    auto it = env->breakpoints.find(b);
+    DCHECK(it != env->breakpoints.end());
+    env->breakpoints.erase(it);
+  }
+}
+
+jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  // Need to get mutator_lock_ so we can find the interface version of any default methods.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod();
+  if (location < 0 || static_cast<uint32_t>(location) >=
+      art_method->GetCodeItem()->insns_size_in_code_units_) {
+    return ERR(INVALID_LOCATION);
+  }
+  auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location});
+  if (!res_pair.second) {
+    // Didn't get inserted because it's already present!
+    return ERR(DUPLICATE);
+  }
+  return OK;
+}
+
+jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  // Need to get mutator_lock_ so we can find the interface version of any default methods.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  auto pos = env->breakpoints.find(
+      /* Breakpoint */ {art::jni::DecodeArtMethod(method)->GetCanonicalMethod(), location});
+  if (pos == env->breakpoints.end()) {
+    return ERR(NOT_FOUND);
+  }
+  env->breakpoints.erase(pos);
+  return OK;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_breakpoint.h b/openjdkjvmti/ti_breakpoint.h
new file mode 100644
index 0000000..9b08b42
--- /dev/null
+++ b/openjdkjvmti/ti_breakpoint.h
@@ -0,0 +1,94 @@
+/* Copyright (C) 2017 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_TI_BREAKPOINT_H_
+#define ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+
+namespace art {
+class ArtMethod;
+namespace mirror {
+class Class;
+}  // namespace mirror
+}  // namespace art
+
+namespace openjdkjvmti {
+
+struct ArtJvmTiEnv;
+
+class Breakpoint {
+ public:
+  Breakpoint(art::ArtMethod* m, jlocation loc);
+
+  // Get the hash code of this breakpoint.
+  size_t hash() const;
+
+  bool operator==(const Breakpoint& other) const {
+    return method_ == other.method_ && location_ == other.location_;
+  }
+
+  art::ArtMethod* GetMethod() const {
+    return method_;
+  }
+
+  jlocation GetLocation() const {
+    return location_;
+  }
+
+ private:
+  art::ArtMethod* method_;
+  jlocation location_;
+};
+
+class BreakpointUtil {
+ public:
+  static jvmtiError SetBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
+  static jvmtiError ClearBreakpoint(jvmtiEnv* env, jmethodID method, jlocation location);
+  // Used by class redefinition to remove breakpoints on redefined classes.
+  static void RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass)
+      REQUIRES(art::Locks::mutator_lock_);
+};
+
+}  // namespace openjdkjvmti
+
+namespace std {
+template<> struct hash<openjdkjvmti::Breakpoint> {
+  size_t operator()(const openjdkjvmti::Breakpoint& b) const {
+    return b.hash();
+  }
+};
+
+}  // namespace std
+#endif  // ART_OPENJDKJVMTI_TI_BREAKPOINT_H_
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
new file mode 100644
index 0000000..954b5d1
--- /dev/null
+++ b/openjdkjvmti/ti_class.cc
@@ -0,0 +1,1086 @@
+/* Copyright (C) 2016 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 "ti_class.h"
+
+#include "android-base/stringprintf.h"
+
+#include <mutex>
+#include <unordered_set>
+
+#include "art_jvmti.h"
+#include "base/array_ref.h"
+#include "base/macros.h"
+#include "class_linker.h"
+#include "class_table-inl.h"
+#include "common_throws.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "fixed_up_dex_file.h"
+#include "gc/heap-visit-objects-inl.h"
+#include "gc/heap.h"
+#include "gc_root.h"
+#include "handle.h"
+#include "jni_env_ext-inl.h"
+#include "jni_internal.h"
+#include "mirror/array-inl.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/object-inl.h"
+#include "mirror/object-refvisitor-inl.h"
+#include "mirror/object_array-inl.h"
+#include "mirror/object_reference.h"
+#include "mirror/reference.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "primitive.h"
+#include "reflection.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_class_loader.h"
+#include "ti_phase.h"
+#include "ti_redefine.h"
+#include "utils.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+using android::base::StringPrintf;
+
+static std::unique_ptr<const art::DexFile> MakeSingleDexFile(art::Thread* self,
+                                                             const char* descriptor,
+                                                             const std::string& orig_location,
+                                                             jint final_len,
+                                                             const unsigned char* final_dex_data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  // Make the mmap
+  std::string error_msg;
+  art::ArrayRef<const unsigned char> final_data(final_dex_data, final_len);
+  std::unique_ptr<art::MemMap> map(Redefiner::MoveDataToMemMap(orig_location,
+                                                               final_data,
+                                                               &error_msg));
+  if (map.get() == nullptr) {
+    LOG(WARNING) << "Unable to allocate mmap for redefined dex file! Error was: " << error_msg;
+    self->ThrowOutOfMemoryError(StringPrintf(
+        "Unable to allocate dex file for transformation of %s", descriptor).c_str());
+    return nullptr;
+  }
+
+  // Make a dex-file
+  if (map->Size() < sizeof(art::DexFile::Header)) {
+    LOG(WARNING) << "Could not read dex file header because dex_data was too short";
+    art::ThrowClassFormatError(nullptr,
+                               "Unable to read transformed dex file of %s",
+                               descriptor);
+    return nullptr;
+  }
+  uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
+  std::string map_name = map->GetName();
+  std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map_name,
+                                                                  checksum,
+                                                                  std::move(map),
+                                                                  /*verify*/true,
+                                                                  /*verify_checksum*/true,
+                                                                  &error_msg));
+  if (dex_file.get() == nullptr) {
+    LOG(WARNING) << "Unable to load modified dex file for " << descriptor << ": " << error_msg;
+    art::ThrowClassFormatError(nullptr,
+                               "Unable to read transformed dex file of %s because %s",
+                               descriptor,
+                               error_msg.c_str());
+    return nullptr;
+  }
+  if (dex_file->NumClassDefs() != 1) {
+    LOG(WARNING) << "Dex file contains more than 1 class_def. Ignoring.";
+    // TODO Throw some other sort of error here maybe?
+    art::ThrowClassFormatError(
+        nullptr,
+        "Unable to use transformed dex file of %s because it contained too many classes",
+        descriptor);
+    return nullptr;
+  }
+  return dex_file;
+}
+
+// A deleter that acts like the jvmtiEnv->Deallocate so that asan does not get tripped up.
+// TODO We should everything use the actual jvmtiEnv->Allocate/Deallocate functions once we can
+// figure out which env to use.
+template <typename T>
+class FakeJvmtiDeleter {
+ public:
+  FakeJvmtiDeleter() {}
+
+  FakeJvmtiDeleter(FakeJvmtiDeleter&) = default;
+  FakeJvmtiDeleter(FakeJvmtiDeleter&&) = default;
+  FakeJvmtiDeleter& operator=(const FakeJvmtiDeleter&) = default;
+
+  template <typename U> void operator()(const U* ptr) const {
+    if (ptr != nullptr) {
+      free(const_cast<U*>(ptr));
+    }
+  }
+};
+
+struct ClassCallback : public art::ClassLoadCallback {
+  void ClassPreDefine(const char* descriptor,
+                      art::Handle<art::mirror::Class> klass,
+                      art::Handle<art::mirror::ClassLoader> class_loader,
+                      const art::DexFile& initial_dex_file,
+                      const art::DexFile::ClassDef& initial_class_def ATTRIBUTE_UNUSED,
+                      /*out*/art::DexFile const** final_dex_file,
+                      /*out*/art::DexFile::ClassDef const** final_class_def)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    bool is_enabled =
+        event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookRetransformable) ||
+        event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassFileLoadHookNonRetransformable);
+    if (!is_enabled) {
+      return;
+    }
+    if (descriptor[0] != 'L') {
+      // It is a primitive or array. Just return
+      return;
+    }
+    jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+    if (UNLIKELY(phase != JVMTI_PHASE_START && phase != JVMTI_PHASE_LIVE)) {
+      // We want to wait until we are at least in the START phase so that all WellKnownClasses and
+      // mirror classes have been initialized and loaded. The runtime relies on these classes having
+      // specific fields and methods present. Since PreDefine hooks don't need to abide by this
+      // restriction we will simply not send the event for these classes.
+      LOG(WARNING) << "Ignoring load of class <" << descriptor << "> as it is being loaded during "
+                   << "runtime initialization.";
+      return;
+    }
+
+    // Strip the 'L' and ';' from the descriptor
+    std::string name(std::string(descriptor).substr(1, strlen(descriptor) - 2));
+
+    art::Thread* self = art::Thread::Current();
+    art::JNIEnvExt* env = self->GetJniEnv();
+    ScopedLocalRef<jobject> loader(
+        env, class_loader.IsNull() ? nullptr : env->AddLocalReference<jobject>(class_loader.Get()));
+    std::unique_ptr<FixedUpDexFile> dex_file_copy(FixedUpDexFile::Create(initial_dex_file));
+
+    // Go back to native.
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+    // Call all Non-retransformable agents.
+    jint post_no_redefine_len = 0;
+    unsigned char* post_no_redefine_dex_data = nullptr;
+    std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>
+        post_no_redefine_unique_ptr(nullptr, FakeJvmtiDeleter<const unsigned char>());
+    event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookNonRetransformable>(
+        self,
+        static_cast<JNIEnv*>(env),
+        static_cast<jclass>(nullptr),  // The class doesn't really exist yet so send null.
+        loader.get(),
+        name.c_str(),
+        static_cast<jobject>(nullptr),  // Android doesn't seem to have protection domains
+        static_cast<jint>(dex_file_copy->Size()),
+        static_cast<const unsigned char*>(dex_file_copy->Begin()),
+        static_cast<jint*>(&post_no_redefine_len),
+        static_cast<unsigned char**>(&post_no_redefine_dex_data));
+    if (post_no_redefine_dex_data == nullptr) {
+      DCHECK_EQ(post_no_redefine_len, 0);
+      post_no_redefine_dex_data = const_cast<unsigned char*>(dex_file_copy->Begin());
+      post_no_redefine_len = dex_file_copy->Size();
+    } else {
+      post_no_redefine_unique_ptr =
+          std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>(
+              post_no_redefine_dex_data, FakeJvmtiDeleter<const unsigned char>());
+      DCHECK_GT(post_no_redefine_len, 0);
+    }
+    // Call all retransformable agents.
+    jint final_len = 0;
+    unsigned char* final_dex_data = nullptr;
+    std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>
+        final_dex_unique_ptr(nullptr, FakeJvmtiDeleter<const unsigned char>());
+    event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+        self,
+        static_cast<JNIEnv*>(env),
+        static_cast<jclass>(nullptr),  // The class doesn't really exist yet so send null.
+        loader.get(),
+        name.c_str(),
+        static_cast<jobject>(nullptr),  // Android doesn't seem to have protection domains
+        static_cast<jint>(post_no_redefine_len),
+        static_cast<const unsigned char*>(post_no_redefine_dex_data),
+        static_cast<jint*>(&final_len),
+        static_cast<unsigned char**>(&final_dex_data));
+    if (final_dex_data == nullptr) {
+      DCHECK_EQ(final_len, 0);
+      final_dex_data = post_no_redefine_dex_data;
+      final_len = post_no_redefine_len;
+    } else {
+      final_dex_unique_ptr =
+          std::unique_ptr<const unsigned char, FakeJvmtiDeleter<const unsigned char>>(
+              final_dex_data, FakeJvmtiDeleter<const unsigned char>());
+      DCHECK_GT(final_len, 0);
+    }
+
+    if (final_dex_data != dex_file_copy->Begin()) {
+      LOG(WARNING) << "Changing class " << descriptor;
+      art::ScopedObjectAccess soa(self);
+      art::StackHandleScope<2> hs(self);
+      // Save the results of all the non-retransformable agents.
+      // First allocate the ClassExt
+      art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(self)));
+      // Make sure we have a ClassExt. This is fine even though we are a temporary since it will
+      // get copied.
+      if (ext.IsNull()) {
+        // We will just return failure if we fail to allocate
+        LOG(WARNING) << "Could not allocate ext-data for class '" << descriptor << "'. "
+                     << "Aborting transformation since we will be unable to store it.";
+        self->AssertPendingOOMException();
+        return;
+      }
+
+      // Allocate the byte array to store the dex file bytes in.
+      art::MutableHandle<art::mirror::Object> arr(hs.NewHandle<art::mirror::Object>(nullptr));
+      if (post_no_redefine_dex_data == dex_file_copy->Begin() && name != "java/lang/Long") {
+        // we didn't have any non-retransformable agents. We can just cache a pointer to the
+        // initial_dex_file. It will be kept live by the class_loader.
+        jlong dex_ptr = reinterpret_cast<uintptr_t>(&initial_dex_file);
+        art::JValue val;
+        val.SetJ(dex_ptr);
+        arr.Assign(art::BoxPrimitive(art::Primitive::kPrimLong, val));
+      } else {
+        arr.Assign(art::mirror::ByteArray::AllocateAndFill(
+            self,
+            reinterpret_cast<const signed char*>(post_no_redefine_dex_data),
+            post_no_redefine_len));
+      }
+      if (arr.IsNull()) {
+        LOG(WARNING) << "Unable to allocate memory for initial dex-file. Aborting transformation";
+        self->AssertPendingOOMException();
+        return;
+      }
+
+      std::unique_ptr<const art::DexFile> dex_file(MakeSingleDexFile(self,
+                                                                     descriptor,
+                                                                     initial_dex_file.GetLocation(),
+                                                                     final_len,
+                                                                     final_dex_data));
+      if (dex_file.get() == nullptr) {
+        return;
+      }
+
+      // TODO Check Redefined dex file for all invariants.
+      LOG(WARNING) << "Dex file created by class-definition time transformation of "
+                   << descriptor << " is not checked for all retransformation invariants.";
+
+      if (!ClassLoaderHelper::AddToClassLoader(self, class_loader, dex_file.get())) {
+        LOG(ERROR) << "Unable to add " << descriptor << " to class loader!";
+        return;
+      }
+
+      // Actually set the ClassExt's original bytes once we have actually succeeded.
+      ext->SetOriginalDexFile(arr.Get());
+      // Set the return values
+      *final_class_def = &dex_file->GetClassDef(0);
+      *final_dex_file = dex_file.release();
+    }
+  }
+
+  void ClassLoad(art::Handle<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassLoad)) {
+      art::Thread* thread = art::Thread::Current();
+      ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+                                    thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+      art::ObjPtr<art::mirror::Object> peer(thread->GetPeer());
+      ScopedLocalRef<jthread> thread_jni(
+          thread->GetJniEnv(),
+          peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer));
+      {
+        art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+        event_handler->DispatchEvent<ArtJvmtiEvent::kClassLoad>(
+            thread,
+            static_cast<JNIEnv*>(thread->GetJniEnv()),
+            thread_jni.get(),
+            jklass.get());
+      }
+      if (klass->IsTemp()) {
+        AddTempClass(thread, jklass.get());
+      }
+    }
+  }
+
+  void ClassPrepare(art::Handle<art::mirror::Class> temp_klass,
+                    art::Handle<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kClassPrepare)) {
+      art::Thread* thread = art::Thread::Current();
+      if (temp_klass.Get() != klass.Get()) {
+        DCHECK(temp_klass->IsTemp());
+        DCHECK(temp_klass->IsRetired());
+        HandleTempClass(thread, temp_klass, klass);
+      }
+      ScopedLocalRef<jclass> jklass(thread->GetJniEnv(),
+                                    thread->GetJniEnv()->AddLocalReference<jclass>(klass.Get()));
+      art::ObjPtr<art::mirror::Object> peer(thread->GetPeer());
+      ScopedLocalRef<jthread> thread_jni(
+          thread->GetJniEnv(),
+          peer.IsNull() ? nullptr : thread->GetJniEnv()->AddLocalReference<jthread>(peer));
+      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+      event_handler->DispatchEvent<ArtJvmtiEvent::kClassPrepare>(
+          thread,
+          static_cast<JNIEnv*>(thread->GetJniEnv()),
+          thread_jni.get(),
+          jklass.get());
+    }
+  }
+
+  // To support parallel class-loading, we need to perform some locking dances here. Namely,
+  // the fixup stage must not be holding the temp_classes lock when it fixes up the system
+  // (as that requires suspending all mutators).
+
+  void AddTempClass(art::Thread* self, jclass klass) {
+    std::unique_lock<std::mutex> mu(temp_classes_lock);
+    jclass global_klass = reinterpret_cast<jclass>(self->GetJniEnv()->NewGlobalRef(klass));
+    temp_classes.push_back(global_klass);
+  }
+
+  void HandleTempClass(art::Thread* self,
+                       art::Handle<art::mirror::Class> temp_klass,
+                       art::Handle<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    bool requires_fixup = false;
+    {
+      std::unique_lock<std::mutex> mu(temp_classes_lock);
+      if (temp_classes.empty()) {
+        return;
+      }
+
+      for (auto it = temp_classes.begin(); it != temp_classes.end(); ++it) {
+        if (temp_klass.Get() == art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(*it))) {
+          self->GetJniEnv()->DeleteGlobalRef(*it);
+          temp_classes.erase(it);
+          requires_fixup = true;
+          break;
+        }
+      }
+    }
+    if (requires_fixup) {
+      FixupTempClass(self, temp_klass, klass);
+    }
+  }
+
+  void FixupTempClass(art::Thread* self,
+                      art::Handle<art::mirror::Class> temp_klass,
+                      art::Handle<art::mirror::Class> klass)
+     REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // Suspend everything.
+    art::gc::Heap* heap = art::Runtime::Current()->GetHeap();
+    if (heap->IsGcConcurrentAndMoving()) {
+      // Need to take a heap dump while GC isn't running. See the
+      // comment in Heap::VisitObjects().
+      heap->IncrementDisableMovingGC(self);
+    }
+    {
+      art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+      art::ScopedSuspendAll ssa("FixupTempClass");
+
+      art::mirror::Class* input = temp_klass.Get();
+      art::mirror::Class* output = klass.Get();
+
+      FixupGlobalReferenceTables(input, output);
+      FixupLocalReferenceTables(self, input, output);
+      FixupHeap(input, output);
+    }
+    if (heap->IsGcConcurrentAndMoving()) {
+      heap->DecrementDisableMovingGC(self);
+    }
+  }
+
+  class RootUpdater : public art::RootVisitor {
+   public:
+    RootUpdater(const art::mirror::Class* input, art::mirror::Class* output)
+        : input_(input), output_(output) {}
+
+    void VisitRoots(art::mirror::Object*** roots,
+                    size_t count,
+                    const art::RootInfo& info ATTRIBUTE_UNUSED)
+        OVERRIDE {
+      for (size_t i = 0; i != count; ++i) {
+        if (*roots[i] == input_) {
+          *roots[i] = output_;
+        }
+      }
+    }
+
+    void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+                    size_t count,
+                    const art::RootInfo& info ATTRIBUTE_UNUSED)
+        OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      for (size_t i = 0; i != count; ++i) {
+        if (roots[i]->AsMirrorPtr() == input_) {
+          roots[i]->Assign(output_);
+        }
+      }
+    }
+
+   private:
+    const art::mirror::Class* input_;
+    art::mirror::Class* output_;
+  };
+
+  void FixupGlobalReferenceTables(art::mirror::Class* input, art::mirror::Class* output)
+      REQUIRES(art::Locks::mutator_lock_) {
+    art::JavaVMExt* java_vm = art::Runtime::Current()->GetJavaVM();
+
+    // Fix up the global table with a root visitor.
+    RootUpdater global_update(input, output);
+    java_vm->VisitRoots(&global_update);
+
+    class WeakGlobalUpdate : public art::IsMarkedVisitor {
+     public:
+      WeakGlobalUpdate(art::mirror::Class* root_input, art::mirror::Class* root_output)
+          : input_(root_input), output_(root_output) {}
+
+      art::mirror::Object* IsMarked(art::mirror::Object* obj) OVERRIDE {
+        if (obj == input_) {
+          return output_;
+        }
+        return obj;
+      }
+
+     private:
+      const art::mirror::Class* input_;
+      art::mirror::Class* output_;
+    };
+    WeakGlobalUpdate weak_global_update(input, output);
+    java_vm->SweepJniWeakGlobals(&weak_global_update);
+  }
+
+  void FixupLocalReferenceTables(art::Thread* self,
+                                 art::mirror::Class* input,
+                                 art::mirror::Class* output)
+      REQUIRES(art::Locks::mutator_lock_) {
+    class LocalUpdate {
+     public:
+      LocalUpdate(const art::mirror::Class* root_input, art::mirror::Class* root_output)
+          : input_(root_input), output_(root_output) {}
+
+      static void Callback(art::Thread* t, void* arg) REQUIRES(art::Locks::mutator_lock_) {
+        LocalUpdate* local = reinterpret_cast<LocalUpdate*>(arg);
+
+        // Fix up the local table with a root visitor.
+        RootUpdater local_update(local->input_, local->output_);
+        t->GetJniEnv()->locals.VisitRoots(
+            &local_update, art::RootInfo(art::kRootJNILocal, t->GetThreadId()));
+      }
+
+     private:
+      const art::mirror::Class* input_;
+      art::mirror::Class* output_;
+    };
+    LocalUpdate local_upd(input, output);
+    art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+    art::Runtime::Current()->GetThreadList()->ForEach(LocalUpdate::Callback, &local_upd);
+  }
+
+  void FixupHeap(art::mirror::Class* input, art::mirror::Class* output)
+        REQUIRES(art::Locks::mutator_lock_) {
+    class HeapFixupVisitor {
+     public:
+      HeapFixupVisitor(const art::mirror::Class* root_input, art::mirror::Class* root_output)
+                : input_(root_input), output_(root_output) {}
+
+      void operator()(art::mirror::Object* src,
+                      art::MemberOffset field_offset,
+                      bool is_static ATTRIBUTE_UNUSED) const
+          REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        art::mirror::HeapReference<art::mirror::Object>* trg =
+          src->GetFieldObjectReferenceAddr(field_offset);
+        if (trg->AsMirrorPtr() == input_) {
+          DCHECK_NE(field_offset.Uint32Value(), 0u);  // This shouldn't be the class field of
+                                                      // an object.
+          trg->Assign(output_);
+        }
+      }
+
+      void operator()(art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+                      art::ObjPtr<art::mirror::Reference> reference) const
+          REQUIRES_SHARED(art::Locks::mutator_lock_) {
+        art::mirror::Object* val = reference->GetReferent();
+        if (val == input_) {
+          reference->SetReferent<false>(output_);
+        }
+      }
+
+      void VisitRoot(art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED)
+          const {
+        LOG(FATAL) << "Unreachable";
+      }
+
+      void VisitRootIfNonNull(
+          art::mirror::CompressedReference<art::mirror::Object>* root ATTRIBUTE_UNUSED) const {
+        LOG(FATAL) << "Unreachable";
+      }
+
+     private:
+      const art::mirror::Class* input_;
+      art::mirror::Class* output_;
+    };
+    HeapFixupVisitor hfv(input, output);
+    auto object_visitor = [&](art::mirror::Object* obj) {
+      obj->VisitReferences<false>(hfv, hfv);  // Visit references, not native roots.
+    };
+    art::Runtime::Current()->GetHeap()->VisitObjectsPaused(object_visitor);
+  }
+
+  // A set of all the temp classes we have handed out. We have to fix up references to these.
+  // For simplicity, we store the temp classes as JNI global references in a vector. Normally a
+  // Prepare event will closely follow, so the vector should be small.
+  std::mutex temp_classes_lock;
+  std::vector<jclass> temp_classes;
+
+  EventHandler* event_handler = nullptr;
+};
+
+ClassCallback gClassCallback;
+
+void ClassUtil::Register(EventHandler* handler) {
+  gClassCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add load callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddClassLoadCallback(&gClassCallback);
+}
+
+void ClassUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove thread callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveClassLoadCallback(&gClassCallback);
+}
+
+jvmtiError ClassUtil::GetClassFields(jvmtiEnv* env,
+                                     jclass jklass,
+                                     jint* field_count_ptr,
+                                     jfieldID** fields_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  // Check if this class is a temporary class object used for loading. Since we are seeing it the
+  // class must not have been prepared yet since otherwise the fixup would have gotten the jobject
+  // to point to the final class object.
+  if (klass->IsTemp() || klass->IsRetired()) {
+    return ERR(CLASS_NOT_PREPARED);
+  }
+
+  if (field_count_ptr == nullptr || fields_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::IterationRange<art::StrideIterator<art::ArtField>> ifields = klass->GetIFields();
+  art::IterationRange<art::StrideIterator<art::ArtField>> sfields = klass->GetSFields();
+  size_t array_size = klass->NumInstanceFields() + klass->NumStaticFields();
+
+  unsigned char* out_ptr;
+  jvmtiError allocError = env->Allocate(array_size * sizeof(jfieldID), &out_ptr);
+  if (allocError != ERR(NONE)) {
+    return allocError;
+  }
+  jfieldID* field_array = reinterpret_cast<jfieldID*>(out_ptr);
+
+  size_t array_idx = 0;
+  for (art::ArtField& field : sfields) {
+    field_array[array_idx] = art::jni::EncodeArtField(&field);
+    ++array_idx;
+  }
+  for (art::ArtField& field : ifields) {
+    field_array[array_idx] = art::jni::EncodeArtField(&field);
+    ++array_idx;
+  }
+
+  *field_count_ptr = static_cast<jint>(array_size);
+  *fields_ptr = field_array;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassMethods(jvmtiEnv* env,
+                                      jclass jklass,
+                                      jint* method_count_ptr,
+                                      jmethodID** methods_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  // Check if this class is a temporary class object used for loading. Since we are seeing it the
+  // class must not have been prepared yet since otherwise the fixup would have gotten the jobject
+  // to point to the final class object.
+  if (klass->IsTemp() || klass->IsRetired()) {
+    return ERR(CLASS_NOT_PREPARED);
+  }
+
+  if (method_count_ptr == nullptr || methods_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  size_t array_size = klass->NumDeclaredVirtualMethods() + klass->NumDirectMethods();
+  unsigned char* out_ptr;
+  jvmtiError allocError = env->Allocate(array_size * sizeof(jmethodID), &out_ptr);
+  if (allocError != ERR(NONE)) {
+    return allocError;
+  }
+  jmethodID* method_array = reinterpret_cast<jmethodID*>(out_ptr);
+
+  if (art::kIsDebugBuild) {
+    size_t count = 0;
+    for (auto& m ATTRIBUTE_UNUSED : klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+      count++;
+    }
+    CHECK_EQ(count, klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods());
+  }
+
+  size_t array_idx = 0;
+  for (auto& m : klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+    method_array[array_idx] = art::jni::EncodeArtMethod(&m);
+    ++array_idx;
+  }
+
+  *method_count_ptr = static_cast<jint>(array_size);
+  *methods_ptr = method_array;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetImplementedInterfaces(jvmtiEnv* env,
+                                               jclass jklass,
+                                               jint* interface_count_ptr,
+                                               jclass** interfaces_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (interface_count_ptr == nullptr || interfaces_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  // Need to handle array specifically. Arrays implement Serializable and Cloneable, but the
+  // spec says these should not be reported.
+  if (klass->IsArrayClass()) {
+    *interface_count_ptr = 0;
+    *interfaces_ptr = nullptr;  // TODO: Should we allocate a dummy here?
+    return ERR(NONE);
+  }
+
+  size_t array_size = klass->NumDirectInterfaces();
+  unsigned char* out_ptr;
+  jvmtiError allocError = env->Allocate(array_size * sizeof(jclass), &out_ptr);
+  if (allocError != ERR(NONE)) {
+    return allocError;
+  }
+  jclass* interface_array = reinterpret_cast<jclass*>(out_ptr);
+
+  art::StackHandleScope<1> hs(soa.Self());
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(klass));
+
+  for (uint32_t idx = 0; idx != array_size; ++idx) {
+    art::ObjPtr<art::mirror::Class> inf_klass =
+        art::mirror::Class::ResolveDirectInterface(soa.Self(), h_klass, idx);
+    if (inf_klass == nullptr) {
+      soa.Self()->ClearException();
+      env->Deallocate(out_ptr);
+      // TODO: What is the right error code here?
+      return ERR(INTERNAL);
+    }
+    interface_array[idx] = soa.AddLocalReference<jclass>(inf_klass);
+  }
+
+  *interface_count_ptr = static_cast<jint>(array_size);
+  *interfaces_ptr = interface_array;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassSignature(jvmtiEnv* env,
+                                         jclass jklass,
+                                         char** signature_ptr,
+                                         char** generic_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  JvmtiUniquePtr<char[]> sig_copy;
+  if (signature_ptr != nullptr) {
+    std::string storage;
+    const char* descriptor = klass->GetDescriptor(&storage);
+
+    jvmtiError ret;
+    sig_copy = CopyString(env, descriptor, &ret);
+    if (sig_copy == nullptr) {
+      return ret;
+    }
+    *signature_ptr = sig_copy.get();
+  }
+
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+    if (!klass->IsProxyClass() && klass->GetDexCache() != nullptr) {
+      art::StackHandleScope<1> hs(soa.Self());
+      art::Handle<art::mirror::Class> h_klass = hs.NewHandle(klass);
+      art::mirror::ObjectArray<art::mirror::String>* str_array =
+          art::annotations::GetSignatureAnnotationForClass(h_klass);
+      if (str_array != nullptr) {
+        std::ostringstream oss;
+        for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+          oss << str_array->Get(i)->ToModifiedUtf8();
+        }
+        std::string output_string = oss.str();
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+        if (copy == nullptr) {
+          return ret;
+        }
+        *generic_ptr = copy.release();
+      } else if (soa.Self()->IsExceptionPending()) {
+        // TODO: Should we report an error here?
+        soa.Self()->ClearException();
+      }
+    }
+  }
+
+  // Everything is fine, release the buffers.
+  sig_copy.release();
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassStatus(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                     jclass jklass,
+                                     jint* status_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (status_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  if (klass->IsArrayClass()) {
+    *status_ptr = JVMTI_CLASS_STATUS_ARRAY;
+  } else if (klass->IsPrimitive()) {
+    *status_ptr = JVMTI_CLASS_STATUS_PRIMITIVE;
+  } else {
+    *status_ptr = JVMTI_CLASS_STATUS_VERIFIED;  // All loaded classes are structurally verified.
+    // This is finicky. If there's an error, we'll say it wasn't prepared.
+    if (klass->IsResolved()) {
+      *status_ptr |= JVMTI_CLASS_STATUS_PREPARED;
+    }
+    if (klass->IsInitialized()) {
+      *status_ptr |= JVMTI_CLASS_STATUS_INITIALIZED;
+    }
+    // Technically the class may be erroneous for other reasons, but we do not have enough info.
+    if (klass->IsErroneous()) {
+      *status_ptr |= JVMTI_CLASS_STATUS_ERROR;
+    }
+  }
+
+  return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError ClassIsT(jclass jklass, T test, jboolean* is_t_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (is_t_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *is_t_ptr = test(klass) ? JNI_TRUE : JNI_FALSE;
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::IsInterface(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                  jclass jklass,
+                                  jboolean* is_interface_ptr) {
+  auto test = [](art::ObjPtr<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return klass->IsInterface();
+  };
+  return ClassIsT(jklass, test, is_interface_ptr);
+}
+
+jvmtiError ClassUtil::IsArrayClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                   jclass jklass,
+                                   jboolean* is_array_class_ptr) {
+  auto test = [](art::ObjPtr<art::mirror::Class> klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return klass->IsArrayClass();
+  };
+  return ClassIsT(jklass, test, is_array_class_ptr);
+}
+
+// Keep this in sync with Class.getModifiers().
+static uint32_t ClassGetModifiers(art::Thread* self, art::ObjPtr<art::mirror::Class> klass)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (klass->IsArrayClass()) {
+    uint32_t component_modifiers = ClassGetModifiers(self, klass->GetComponentType());
+    if ((component_modifiers & art::kAccInterface) != 0) {
+      component_modifiers &= ~(art::kAccInterface | art::kAccStatic);
+    }
+    return art::kAccAbstract | art::kAccFinal | component_modifiers;
+  }
+
+  uint32_t modifiers = klass->GetAccessFlags() & art::kAccJavaFlagsMask;
+
+  art::StackHandleScope<1> hs(self);
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(klass));
+  return art::mirror::Class::GetInnerClassFlags(h_klass, modifiers);
+}
+
+jvmtiError ClassUtil::GetClassModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jclass jklass,
+                                        jint* modifiers_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (modifiers_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *modifiers_ptr = ClassGetModifiers(soa.Self(), klass);
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassLoader(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                     jclass jklass,
+                                     jobject* classloader_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> klass = soa.Decode<art::mirror::Class>(jklass);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (classloader_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *classloader_ptr = soa.AddLocalReference<jobject>(klass->GetClassLoader());
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassLoaderClasses(jvmtiEnv* env,
+                                            jobject initiating_loader,
+                                            jint* class_count_ptr,
+                                            jclass** classes_ptr) {
+  UNUSED(env, initiating_loader, class_count_ptr, classes_ptr);
+
+  if (class_count_ptr == nullptr || classes_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  art::Thread* self = art::Thread::Current();
+  if (!self->GetJniEnv()->IsInstanceOf(initiating_loader,
+                                       art::WellKnownClasses::java_lang_ClassLoader)) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (self->GetJniEnv()->IsInstanceOf(initiating_loader,
+                                      art::WellKnownClasses::java_lang_BootClassLoader)) {
+    // Need to use null for the BootClassLoader.
+    initiating_loader = nullptr;
+  }
+
+  art::ScopedObjectAccess soa(self);
+  art::ObjPtr<art::mirror::ClassLoader> class_loader =
+      soa.Decode<art::mirror::ClassLoader>(initiating_loader);
+
+  art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+
+  art::ReaderMutexLock mu(self, *art::Locks::classlinker_classes_lock_);
+
+  art::ClassTable* class_table = class_linker->ClassTableForClassLoader(class_loader);
+  if (class_table == nullptr) {
+    // Nothing loaded.
+    *class_count_ptr = 0;
+    *classes_ptr = nullptr;
+    return ERR(NONE);
+  }
+
+  struct ClassTableCount {
+    bool operator()(art::ObjPtr<art::mirror::Class> klass) {
+      DCHECK(klass != nullptr);
+      ++count;
+      return true;
+    }
+
+    size_t count = 0;
+  };
+  ClassTableCount ctc;
+  class_table->Visit(ctc);
+
+  if (ctc.count == 0) {
+    // Nothing loaded.
+    *class_count_ptr = 0;
+    *classes_ptr = nullptr;
+    return ERR(NONE);
+  }
+
+  unsigned char* data;
+  jvmtiError data_result = env->Allocate(ctc.count * sizeof(jclass), &data);
+  if (data_result != ERR(NONE)) {
+    return data_result;
+  }
+  jclass* class_array = reinterpret_cast<jclass*>(data);
+
+  struct ClassTableFill {
+    bool operator()(art::ObjPtr<art::mirror::Class> klass)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK(klass != nullptr);
+      DCHECK_LT(count, ctc_ref.count);
+      local_class_array[count++] = soa_ptr->AddLocalReference<jclass>(klass);
+      return true;
+    }
+
+    jclass* local_class_array;
+    const ClassTableCount& ctc_ref;
+    art::ScopedObjectAccess* soa_ptr;
+    size_t count;
+  };
+  ClassTableFill ctf = { class_array, ctc, &soa, 0 };
+  class_table->Visit(ctf);
+  DCHECK_EQ(ctc.count, ctf.count);
+
+  *class_count_ptr = ctc.count;
+  *classes_ptr = class_array;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetClassVersionNumbers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             jclass jklass,
+                                             jint* minor_version_ptr,
+                                             jint* major_version_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (jklass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+  if (!jklass_obj->IsClass()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass();
+  if (klass->IsPrimitive() || klass->IsArrayClass()) {
+    return ERR(INVALID_CLASS);
+  }
+
+  if (minor_version_ptr == nullptr || major_version_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  // Note: proxies will show the dex file version of java.lang.reflect.Proxy, as that is
+  //       what their dex cache copies from.
+  uint32_t version = klass->GetDexFile().GetHeader().GetVersion();
+
+  *major_version_ptr = static_cast<jint>(version);
+  *minor_version_ptr = 0;
+
+  return ERR(NONE);
+}
+
+jvmtiError ClassUtil::GetSourceFileName(jvmtiEnv* env, jclass jklass, char** source_name_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (jklass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+  if (!jklass_obj->IsClass()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Class> klass = jklass_obj->AsClass();
+  if (klass->IsPrimitive() || klass->IsArrayClass()) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  JvmtiUniquePtr<char[]> source_copy;
+  const char* file_name = klass->GetSourceFile();
+  if (file_name == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  jvmtiError ret;
+  source_copy = CopyString(env, file_name, &ret);
+  if (source_copy == nullptr) {
+    return ret;
+  }
+  *source_name_ptr = source_copy.release();
+  return OK;
+}
+
+jvmtiError ClassUtil::GetSourceDebugExtension(jvmtiEnv* env,
+                                              jclass jklass,
+                                              char** source_debug_extension_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (jklass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  art::ObjPtr<art::mirror::Object> jklass_obj = soa.Decode<art::mirror::Object>(jklass);
+  if (!jklass_obj->IsClass()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::StackHandleScope<1> hs(art::Thread::Current());
+  art::Handle<art::mirror::Class> klass(hs.NewHandle(jklass_obj->AsClass()));
+  if (klass->IsPrimitive() || klass->IsArrayClass()) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  JvmtiUniquePtr<char[]> ext_copy;
+  const char* data = art::annotations::GetSourceDebugExtension(klass);
+  if (data == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+  jvmtiError ret;
+  ext_copy = CopyString(env, data, &ret);
+  if (ext_copy == nullptr) {
+    return ret;
+  }
+  *source_debug_extension_ptr = ext_copy.release();
+  return OK;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class.h b/openjdkjvmti/ti_class.h
new file mode 100644
index 0000000..dd99e36
--- /dev/null
+++ b/openjdkjvmti/ti_class.h
@@ -0,0 +1,95 @@
+/* Copyright (C) 2016 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_TI_CLASS_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class ClassUtil {
+ public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
+  static jvmtiError GetClassFields(jvmtiEnv* env,
+                                   jclass klass,
+                                   jint* field_count_ptr,
+                                   jfieldID** fields_ptr);
+
+  static jvmtiError GetClassMethods(jvmtiEnv* env,
+                                    jclass klass,
+                                    jint* method_count_ptr,
+                                    jmethodID** methods_ptr);
+
+  static jvmtiError GetImplementedInterfaces(jvmtiEnv* env,
+                                             jclass klass,
+                                             jint* interface_count_ptr,
+                                             jclass** interfaces_ptr);
+
+  static jvmtiError GetClassModifiers(jvmtiEnv* env, jclass klass, jint* modifiers_ptr);
+
+  static jvmtiError GetClassSignature(jvmtiEnv* env,
+                                      jclass klass,
+                                      char** signature_ptr,
+                                      char** generic_ptr);
+
+  static jvmtiError GetClassStatus(jvmtiEnv* env, jclass klass, jint* status_ptr);
+
+  static jvmtiError GetClassLoader(jvmtiEnv* env, jclass klass, jobject* classloader_ptr);
+
+  static jvmtiError GetClassLoaderClasses(jvmtiEnv* env,
+                                          jobject initiating_loader,
+                                          jint* class_count_ptr,
+                                          jclass** classes_ptr);
+
+  static jvmtiError IsInterface(jvmtiEnv* env, jclass klass, jboolean* is_interface_ptr);
+  static jvmtiError IsArrayClass(jvmtiEnv* env, jclass klass, jboolean* is_array_class_ptr);
+
+  static jvmtiError GetClassVersionNumbers(jvmtiEnv* env,
+                                           jclass klass,
+                                           jint* minor_version_ptr,
+                                           jint* major_version_ptr);
+
+  static jvmtiError GetSourceFileName(jvmtiEnv* env, jclass klass, char** source_name_ptr);
+
+  static jvmtiError GetSourceDebugExtension(jvmtiEnv* env,
+                                            jclass klass,
+                                            char** source_debug_extension_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_CLASS_H_
diff --git a/openjdkjvmti/ti_class_definition.cc b/openjdkjvmti/ti_class_definition.cc
new file mode 100644
index 0000000..c73ef0d
--- /dev/null
+++ b/openjdkjvmti/ti_class_definition.cc
@@ -0,0 +1,179 @@
+/* Copyright (C) 2016 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 "ti_class_definition.h"
+
+#include "base/array_slice.h"
+#include "class_linker-inl.h"
+#include "dex_file.h"
+#include "fixed_up_dex_file.h"
+#include "handle.h"
+#include "handle_scope-inl.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/object-inl.h"
+#include "reflection.h"
+#include "thread.h"
+
+namespace openjdkjvmti {
+
+bool ArtClassDefinition::IsModified() const {
+  // RedefineClasses calls always are 'modified' since they need to change the original_dex_file of
+  // the class.
+  if (redefined_) {
+    return true;
+  }
+  // Check if the dex file we want to set is the same as the current one.
+  // Unfortunately we need to do this check even if no modifications have been done since it could
+  // be that agents were removed in the mean-time so we still have a different dex file. The dex
+  // checksum means this is likely to be fairly fast.
+  return static_cast<jint>(original_dex_file_.size()) != dex_len_ ||
+      memcmp(original_dex_file_.data(), dex_data_.get(), dex_len_) != 0;
+}
+
+jvmtiError ArtClassDefinition::InitCommon(ArtJvmTiEnv* env, jclass klass) {
+  JNIEnv* jni_env = GetJniEnv(env);
+  if (jni_env == nullptr) {
+    return ERR(INTERNAL);
+  }
+  art::ScopedObjectAccess soa(jni_env);
+  art::ObjPtr<art::mirror::Class> m_klass(soa.Decode<art::mirror::Class>(klass));
+  if (m_klass.IsNull()) {
+    return ERR(INVALID_CLASS);
+  }
+  klass_ = klass;
+  loader_ = soa.AddLocalReference<jobject>(m_klass->GetClassLoader());
+  std::string descriptor_store;
+  std::string descriptor(m_klass->GetDescriptor(&descriptor_store));
+  name_ = descriptor.substr(1, descriptor.size() - 2);
+  // Android doesn't really have protection domains.
+  protection_domain_ = nullptr;
+  return OK;
+}
+
+// Gets the data surrounding the given class.
+static jvmtiError GetDexDataForRetransformation(ArtJvmTiEnv* env,
+                                                art::Handle<art::mirror::Class> klass,
+                                                /*out*/jint* dex_data_len,
+                                                /*out*/unsigned char** dex_data)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  art::StackHandleScope<3> hs(art::Thread::Current());
+  art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->GetExtData()));
+  const art::DexFile* dex_file = nullptr;
+  if (!ext.IsNull()) {
+    art::Handle<art::mirror::Object> orig_dex(hs.NewHandle(ext->GetOriginalDexFile()));
+    if (!orig_dex.IsNull()) {
+      if (orig_dex->IsArrayInstance()) {
+        DCHECK(orig_dex->GetClass()->GetComponentType()->IsPrimitiveByte());
+        art::Handle<art::mirror::ByteArray> orig_dex_bytes(
+            hs.NewHandle(art::down_cast<art::mirror::ByteArray*>(orig_dex->AsArray())));
+        *dex_data_len = static_cast<jint>(orig_dex_bytes->GetLength());
+        return CopyDataIntoJvmtiBuffer(
+            env,
+            reinterpret_cast<const unsigned char*>(orig_dex_bytes->GetData()),
+            *dex_data_len,
+            /*out*/dex_data);
+      } else if (orig_dex->IsDexCache()) {
+        dex_file = orig_dex->AsDexCache()->GetDexFile();
+      } else {
+        DCHECK(orig_dex->GetClass()->DescriptorEquals("Ljava/lang/Long;"))
+            << "Expected java/lang/Long but found object of type "
+            << orig_dex->GetClass()->PrettyClass();
+        art::ObjPtr<art::mirror::Class> prim_long_class(
+            art::Runtime::Current()->GetClassLinker()->GetClassRoot(
+                art::ClassLinker::kPrimitiveLong));
+        art::JValue val;
+        if (!art::UnboxPrimitiveForResult(orig_dex.Get(), prim_long_class, &val)) {
+          // This should never happen.
+          return ERR(INTERNAL);
+        }
+        dex_file = reinterpret_cast<const art::DexFile*>(static_cast<uintptr_t>(val.GetJ()));
+      }
+    }
+  }
+  if (dex_file == nullptr) {
+    dex_file = &klass->GetDexFile();
+  }
+  std::unique_ptr<FixedUpDexFile> fixed_dex_file(FixedUpDexFile::Create(*dex_file));
+  *dex_data_len = static_cast<jint>(fixed_dex_file->Size());
+  return CopyDataIntoJvmtiBuffer(env,
+                                 fixed_dex_file->Begin(),
+                                 fixed_dex_file->Size(),
+                                 /*out*/dex_data);
+}
+
+jvmtiError ArtClassDefinition::Init(ArtJvmTiEnv* env, jclass klass) {
+  jvmtiError res = InitCommon(env, klass);
+  if (res != OK) {
+    return res;
+  }
+  unsigned char* new_data = nullptr;
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::StackHandleScope<1> hs(self);
+  art::Handle<art::mirror::Class> m_klass(hs.NewHandle(self->DecodeJObject(klass)->AsClass()));
+  res = GetDexDataForRetransformation(env, m_klass, &dex_len_, &new_data);
+  if (res != OK) {
+    return res;
+  }
+  dex_data_ = MakeJvmtiUniquePtr(env, new_data);
+  if (m_klass->GetExtData() == nullptr || m_klass->GetExtData()->GetOriginalDexFile() == nullptr) {
+    // We have never redefined class this yet. Keep track of what the (de-quickened) dex file looks
+    // like so we can tell if anything has changed. Really we would like to just always do the
+    // 'else' block but the fact that we de-quickened stuff screws us over.
+    unsigned char* original_data_memory = nullptr;
+    res = CopyDataIntoJvmtiBuffer(env, dex_data_.get(), dex_len_, &original_data_memory);
+    original_dex_file_memory_ = MakeJvmtiUniquePtr(env, original_data_memory);
+    original_dex_file_ = art::ArrayRef<const unsigned char>(original_data_memory, dex_len_);
+  } else {
+    // We know that we have been redefined at least once (there is an original_dex_file set in
+    // the class) so we can just use the current dex file directly.
+    const art::DexFile& dex_file = m_klass->GetDexFile();
+    original_dex_file_ = art::ArrayRef<const unsigned char>(dex_file.Begin(), dex_file.Size());
+  }
+  return res;
+}
+
+jvmtiError ArtClassDefinition::Init(ArtJvmTiEnv* env, const jvmtiClassDefinition& def) {
+  jvmtiError res = InitCommon(env, def.klass);
+  if (res != OK) {
+    return res;
+  }
+  unsigned char* new_data = nullptr;
+  original_dex_file_ = art::ArrayRef<const unsigned char>(def.class_bytes, def.class_byte_count);
+  redefined_ = true;
+  dex_len_ = def.class_byte_count;
+  res = CopyDataIntoJvmtiBuffer(env, def.class_bytes, def.class_byte_count, /*out*/ &new_data);
+  dex_data_ = MakeJvmtiUniquePtr(env, new_data);
+  return res;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class_definition.h b/openjdkjvmti/ti_class_definition.h
new file mode 100644
index 0000000..accc456
--- /dev/null
+++ b/openjdkjvmti/ti_class_definition.h
@@ -0,0 +1,131 @@
+/* Copyright (C) 2017 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_TI_CLASS_DEFINITION_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
+
+#include "art_jvmti.h"
+
+#include "base/array_ref.h"
+
+namespace openjdkjvmti {
+
+// A struct that stores data needed for redefining/transforming classes. This structure should only
+// even be accessed from a single thread and must not survive past the completion of the
+// redefinition/retransformation function that created it.
+class ArtClassDefinition {
+ public:
+  ArtClassDefinition()
+      : klass_(nullptr),
+        loader_(nullptr),
+        name_(),
+        protection_domain_(nullptr),
+        dex_len_(0),
+        dex_data_(nullptr),
+        original_dex_file_memory_(nullptr),
+        original_dex_file_(),
+        redefined_(false) {}
+
+  jvmtiError Init(ArtJvmTiEnv* env, jclass klass);
+  jvmtiError Init(ArtJvmTiEnv* env, const jvmtiClassDefinition& def);
+
+  ArtClassDefinition(ArtClassDefinition&& o) = default;
+  ArtClassDefinition& operator=(ArtClassDefinition&& o) = default;
+
+  void SetNewDexData(ArtJvmTiEnv* env, jint new_dex_len, unsigned char* new_dex_data) {
+    DCHECK(IsInitialized());
+    if (new_dex_data == nullptr) {
+      return;
+    } else if (new_dex_data != dex_data_.get() || new_dex_len != dex_len_) {
+      dex_len_ = new_dex_len;
+      dex_data_ = MakeJvmtiUniquePtr(env, new_dex_data);
+    }
+  }
+
+  art::ArrayRef<const unsigned char> GetNewOriginalDexFile() const {
+    DCHECK(IsInitialized());
+    if (redefined_) {
+      return original_dex_file_;
+    } else {
+      return art::ArrayRef<const unsigned char>();
+    }
+  }
+
+  bool IsModified() const;
+
+  bool IsInitialized() const {
+    return klass_ != nullptr;
+  }
+
+  jclass GetClass() const {
+    DCHECK(IsInitialized());
+    return klass_;
+  }
+
+  jobject GetLoader() const {
+    DCHECK(IsInitialized());
+    return loader_;
+  }
+
+  const std::string& GetName() const {
+    DCHECK(IsInitialized());
+    return name_;
+  }
+
+  jobject GetProtectionDomain() const {
+    DCHECK(IsInitialized());
+    return protection_domain_;
+  }
+
+  art::ArrayRef<const unsigned char> GetDexData() const {
+    DCHECK(IsInitialized());
+    return art::ArrayRef<const unsigned char>(dex_data_.get(), dex_len_);
+  }
+
+ private:
+  jvmtiError InitCommon(ArtJvmTiEnv* env, jclass klass);
+
+  jclass klass_;
+  jobject loader_;
+  std::string name_;
+  jobject protection_domain_;
+  jint dex_len_;
+  JvmtiUniquePtr<unsigned char> dex_data_;
+  JvmtiUniquePtr<unsigned char> original_dex_file_memory_;
+  art::ArrayRef<const unsigned char> original_dex_file_;
+  bool redefined_;
+
+  DISALLOW_COPY_AND_ASSIGN(ArtClassDefinition);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_CLASS_DEFINITION_H_
diff --git a/openjdkjvmti/ti_class_loader.cc b/openjdkjvmti/ti_class_loader.cc
new file mode 100644
index 0000000..e81e4bc
--- /dev/null
+++ b/openjdkjvmti/ti_class_loader.cc
@@ -0,0 +1,207 @@
+/* Copyright (C) 2017 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 "ti_class_loader.h"
+
+#include <limits>
+
+#include "android-base/stringprintf.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "instrumentation.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class.h"
+#include "mirror/class_ext.h"
+#include "mirror/object.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "transform.h"
+
+namespace openjdkjvmti {
+
+bool ClassLoaderHelper::AddToClassLoader(art::Thread* self,
+                                         art::Handle<art::mirror::ClassLoader> loader,
+                                         const art::DexFile* dex_file) {
+  art::ScopedObjectAccessUnchecked soa(self);
+  art::StackHandleScope<3> hs(self);
+  if (art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
+    art::Runtime::Current()->GetClassLinker()->AppendToBootClassPath(self, *dex_file);
+    return true;
+  }
+  art::Handle<art::mirror::Object> java_dex_file_obj(
+      hs.NewHandle(FindSourceDexFileObject(self, loader)));
+  if (java_dex_file_obj.IsNull()) {
+    return false;
+  }
+  art::Handle<art::mirror::LongArray> old_cookie(hs.NewHandle(GetDexFileCookie(java_dex_file_obj)));
+  art::Handle<art::mirror::LongArray> cookie(hs.NewHandle(
+      AllocateNewDexFileCookie(self, old_cookie, dex_file)));
+  if (cookie.IsNull()) {
+    return false;
+  }
+  art::ScopedAssertNoThreadSuspension nts("Replacing cookie fields in j.l.DexFile object");
+  UpdateJavaDexFile(java_dex_file_obj.Get(), cookie.Get());
+  return true;
+}
+
+void ClassLoaderHelper::UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+                                          art::ObjPtr<art::mirror::LongArray> new_cookie) {
+  art::ArtField* internal_cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
+      "mInternalCookie", "Ljava/lang/Object;");
+  art::ArtField* cookie_field = java_dex_file->GetClass()->FindDeclaredInstanceField(
+      "mCookie", "Ljava/lang/Object;");
+  CHECK(internal_cookie_field != nullptr);
+  art::ObjPtr<art::mirror::LongArray> orig_internal_cookie(
+      internal_cookie_field->GetObject(java_dex_file)->AsLongArray());
+  art::ObjPtr<art::mirror::LongArray> orig_cookie(
+      cookie_field->GetObject(java_dex_file)->AsLongArray());
+  internal_cookie_field->SetObject<false>(java_dex_file, new_cookie);
+  if (!orig_cookie.IsNull()) {
+    cookie_field->SetObject<false>(java_dex_file, new_cookie);
+  }
+}
+
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::GetDexFileCookie(
+    art::Handle<art::mirror::Object> java_dex_file_obj) {
+  // mCookie is nulled out if the DexFile has been closed but mInternalCookie sticks around until
+  // the object is finalized. Since they always point to the same array if mCookie is not null we
+  // just use the mInternalCookie field. We will update one or both of these fields later.
+  art::ArtField* internal_cookie_field = java_dex_file_obj->GetClass()->FindDeclaredInstanceField(
+      "mInternalCookie", "Ljava/lang/Object;");
+  // TODO Add check that mCookie is either null or same as mInternalCookie
+  CHECK(internal_cookie_field != nullptr);
+  return internal_cookie_field->GetObject(java_dex_file_obj.Get())->AsLongArray();
+}
+
+art::ObjPtr<art::mirror::LongArray> ClassLoaderHelper::AllocateNewDexFileCookie(
+    art::Thread* self,
+    art::Handle<art::mirror::LongArray> cookie,
+    const art::DexFile* dex_file) {
+  art::StackHandleScope<1> hs(self);
+  CHECK(cookie != nullptr);
+  CHECK_GE(cookie->GetLength(), 1);
+  art::Handle<art::mirror::LongArray> new_cookie(
+      hs.NewHandle(art::mirror::LongArray::Alloc(self, cookie->GetLength() + 1)));
+  if (new_cookie == nullptr) {
+    self->AssertPendingOOMException();
+    return nullptr;
+  }
+  // Copy the oat-dex field at the start.
+  new_cookie->SetWithoutChecks<false>(0, cookie->GetWithoutChecks(0));
+  // This must match the casts in runtime/native/dalvik_system_DexFile.cc:ConvertDexFilesToJavaArray
+  new_cookie->SetWithoutChecks<false>(
+      1, static_cast<int64_t>(reinterpret_cast<uintptr_t>(dex_file)));
+  new_cookie->Memcpy(2, cookie.Get(), 1, cookie->GetLength() - 1);
+  return new_cookie.Get();
+}
+
+// TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+art::ObjPtr<art::mirror::Object> ClassLoaderHelper::FindSourceDexFileObject(
+    art::Thread* self, art::Handle<art::mirror::ClassLoader> loader) {
+  const char* dex_path_list_element_array_name = "[Ldalvik/system/DexPathList$Element;";
+  const char* dex_path_list_element_name = "Ldalvik/system/DexPathList$Element;";
+  const char* dex_file_name = "Ldalvik/system/DexFile;";
+  const char* dex_path_list_name = "Ldalvik/system/DexPathList;";
+  const char* dex_class_loader_name = "Ldalvik/system/BaseDexClassLoader;";
+
+  CHECK(!self->IsExceptionPending());
+  art::StackHandleScope<5> hs(self);
+  art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+
+  art::Handle<art::mirror::ClassLoader> null_loader(hs.NewHandle<art::mirror::ClassLoader>(
+      nullptr));
+  art::Handle<art::mirror::Class> base_dex_loader_class(hs.NewHandle(class_linker->FindClass(
+      self, dex_class_loader_name, null_loader)));
+
+  // Get all the ArtFields so we can look in the BaseDexClassLoader
+  art::ArtField* path_list_field = base_dex_loader_class->FindDeclaredInstanceField(
+      "pathList", dex_path_list_name);
+  CHECK(path_list_field != nullptr);
+
+  art::ArtField* dex_path_list_element_field =
+      class_linker->FindClass(self, dex_path_list_name, null_loader)
+        ->FindDeclaredInstanceField("dexElements", dex_path_list_element_array_name);
+  CHECK(dex_path_list_element_field != nullptr);
+
+  art::ArtField* element_dex_file_field =
+      class_linker->FindClass(self, dex_path_list_element_name, null_loader)
+        ->FindDeclaredInstanceField("dexFile", dex_file_name);
+  CHECK(element_dex_file_field != nullptr);
+
+  // Check if loader is a BaseDexClassLoader
+  art::Handle<art::mirror::Class> loader_class(hs.NewHandle(loader->GetClass()));
+  // Currently only base_dex_loader is allowed to actually define classes but if this changes in the
+  // future we should make sure to support all class loader types.
+  if (!loader_class->IsSubClass(base_dex_loader_class.Get())) {
+    LOG(ERROR) << "The classloader is not a BaseDexClassLoader which is currently the only "
+               << "supported class loader type!";
+    return nullptr;
+  }
+  // Start navigating the fields of the loader (now known to be a BaseDexClassLoader derivative)
+  art::Handle<art::mirror::Object> path_list(
+      hs.NewHandle(path_list_field->GetObject(loader.Get())));
+  CHECK(path_list != nullptr);
+  CHECK(!self->IsExceptionPending());
+  art::Handle<art::mirror::ObjectArray<art::mirror::Object>> dex_elements_list(hs.NewHandle(
+      dex_path_list_element_field->GetObject(path_list.Get())->
+      AsObjectArray<art::mirror::Object>()));
+  CHECK(!self->IsExceptionPending());
+  CHECK(dex_elements_list != nullptr);
+  size_t num_elements = dex_elements_list->GetLength();
+  // Iterate over the DexPathList$Element to find the right one
+  for (size_t i = 0; i < num_elements; i++) {
+    art::ObjPtr<art::mirror::Object> current_element = dex_elements_list->Get(i);
+    CHECK(!current_element.IsNull());
+    // TODO It would be cleaner to put the art::DexFile into the dalvik.system.DexFile the class
+    // comes from but it is more annoying because we would need to find this class. It is not
+    // necessary for proper function since we just need to be in front of the classes old dex file
+    // in the path.
+    art::ObjPtr<art::mirror::Object> first_dex_file(
+        element_dex_file_field->GetObject(current_element));
+    if (!first_dex_file.IsNull()) {
+      return first_dex_file;
+    }
+  }
+  return nullptr;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_class_loader.h b/openjdkjvmti/ti_class_loader.h
new file mode 100644
index 0000000..767e258
--- /dev/null
+++ b/openjdkjvmti/ti_class_loader.h
@@ -0,0 +1,99 @@
+/* Copyright (C) 2017 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_TI_CLASS_LOADER_H_
+#define ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
+
+#include <string>
+
+#include <jni.h>
+
+#include "art_jvmti.h"
+#include "art_method.h"
+#include "base/array_slice.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti.h"
+#include "linear_alloc.h"
+#include "mem_map.h"
+#include "mirror/array-inl.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "obj_ptr.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread_list.h"
+#include "ti_class_definition.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+// Class that can redefine a single class's methods.
+// TODO We should really make this be driven by an outside class so we can do multiple classes at
+// the same time and have less required cleanup.
+class ClassLoaderHelper {
+ public:
+  static bool AddToClassLoader(art::Thread* self,
+                               art::Handle<art::mirror::ClassLoader> loader,
+                               const art::DexFile* dex_file)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  // Finds a java.lang.DexFile object that is associated with the given ClassLoader. Each of these
+  // j.l.DexFile objects holds several art::DexFile*s in it.
+  // TODO This should return the actual source java.lang.DexFile object for the klass being loaded.
+  static art::ObjPtr<art::mirror::Object> FindSourceDexFileObject(
+      art::Thread* self, art::Handle<art::mirror::ClassLoader> loader)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static art::ObjPtr<art::mirror::LongArray> GetDexFileCookie(
+      art::Handle<art::mirror::Object> java_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static art::ObjPtr<art::mirror::LongArray> AllocateNewDexFileCookie(
+      art::Thread* self,
+      art::Handle<art::mirror::LongArray> old_dex_file_cookie,
+      const art::DexFile* new_dex_file) REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+                                art::ObjPtr<art::mirror::LongArray> new_cookie)
+      REQUIRES(art::Roles::uninterruptible_) REQUIRES_SHARED(art::Locks::mutator_lock_);
+};
+
+}  // namespace openjdkjvmti
+#endif  // ART_OPENJDKJVMTI_TI_CLASS_LOADER_H_
diff --git a/openjdkjvmti/ti_dump.cc b/openjdkjvmti/ti_dump.cc
new file mode 100644
index 0000000..809a5e4
--- /dev/null
+++ b/openjdkjvmti/ti_dump.cc
@@ -0,0 +1,73 @@
+/* Copyright (C) 2017 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 "ti_dump.h"
+
+#include <limits>
+
+#include "art_jvmti.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+struct DumpCallback : public art::RuntimeSigQuitCallback {
+  void SigQuit() OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Thread* thread = art::Thread::Current();
+    art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+    event_handler->DispatchEvent<ArtJvmtiEvent::kDataDumpRequest>(nullptr);
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+static DumpCallback gDumpCallback;
+
+void DumpUtil::Register(EventHandler* handler) {
+  gDumpCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add sigquit callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimeSigQuitCallback(&gDumpCallback);
+}
+
+void DumpUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove sigquit callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimeSigQuitCallback(&gDumpCallback);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_dump.h b/openjdkjvmti/ti_dump.h
new file mode 100644
index 0000000..323bf56
--- /dev/null
+++ b/openjdkjvmti/ti_dump.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2017 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_TI_DUMP_H_
+#define ART_OPENJDKJVMTI_TI_DUMP_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class DumpUtil {
+ public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_DUMP_H_
diff --git a/openjdkjvmti/ti_field.cc b/openjdkjvmti/ti_field.cc
new file mode 100644
index 0000000..c45b926
--- /dev/null
+++ b/openjdkjvmti/ti_field.cc
@@ -0,0 +1,254 @@
+/* Copyright (C) 2016 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 "ti_field.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/enums.h"
+#include "dex_file_annotations.h"
+#include "jni_internal.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+// Note: For all these functions, we could do a check that the field actually belongs to the given
+//       class. But the spec seems to assume a certain encoding of the field ID, and so doesn't
+//       specify any errors.
+
+jvmtiError FieldUtil::GetFieldName(jvmtiEnv* env,
+                                   jclass klass,
+                                   jfieldID field,
+                                   char** name_ptr,
+                                   char** signature_ptr,
+                                   char** generic_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+
+  JvmtiUniquePtr<char[]> name_copy;
+  if (name_ptr != nullptr) {
+    const char* field_name = art_field->GetName();
+    if (field_name == nullptr) {
+      field_name = "<error>";
+    }
+    jvmtiError ret;
+    name_copy = CopyString(env, field_name, &ret);
+    if (name_copy == nullptr) {
+      return ret;
+    }
+    *name_ptr = name_copy.get();
+  }
+
+  JvmtiUniquePtr<char[]> signature_copy;
+  if (signature_ptr != nullptr) {
+    const char* sig = art_field->GetTypeDescriptor();
+    jvmtiError ret;
+    signature_copy = CopyString(env, sig, &ret);
+    if (signature_copy == nullptr) {
+      return ret;
+    }
+    *signature_ptr = signature_copy.get();
+  }
+
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+    if (!art_field->GetDeclaringClass()->IsProxyClass()) {
+      art::mirror::ObjectArray<art::mirror::String>* str_array =
+          art::annotations::GetSignatureAnnotationForField(art_field);
+      if (str_array != nullptr) {
+        std::ostringstream oss;
+        for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+          oss << str_array->Get(i)->ToModifiedUtf8();
+        }
+        std::string output_string = oss.str();
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> copy = CopyString(env, output_string.c_str(), &ret);
+        if (copy == nullptr) {
+          return ret;
+        }
+        *generic_ptr = copy.release();
+      } else if (soa.Self()->IsExceptionPending()) {
+        // TODO: Should we report an error here?
+        soa.Self()->ClearException();
+      }
+    }
+  }
+
+  // Everything is fine, release the buffers.
+  name_copy.release();
+  signature_copy.release();
+
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::GetFieldDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             jclass klass,
+                                             jfieldID field,
+                                             jclass* declaring_class_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  if (declaring_class_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+  art::ObjPtr<art::mirror::Class> field_klass = art_field->GetDeclaringClass();
+
+  *declaring_class_ptr = soa.AddLocalReference<jclass>(field_klass);
+
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::GetFieldModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jclass klass,
+                                        jfieldID field,
+                                        jint* modifiers_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  if (modifiers_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+  // Note: Keep this code in sync with Field.getModifiers.
+  uint32_t modifiers = art_field->GetAccessFlags() & 0xFFFF;
+
+  *modifiers_ptr = modifiers;
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::IsFieldSynthetic(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                       jclass klass,
+                                       jfieldID field,
+                                       jboolean* is_synthetic_ptr) {
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  if (is_synthetic_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtField* art_field = art::jni::DecodeArtField(field);
+  uint32_t modifiers = art_field->GetAccessFlags();
+
+  *is_synthetic_ptr = ((modifiers & art::kAccSynthetic) != 0) ? JNI_TRUE : JNI_FALSE;
+  return ERR(NONE);
+}
+
+jvmtiError FieldUtil::SetFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto res_pair = env->modify_watched_fields.insert(art::jni::DecodeArtField(field));
+  if (!res_pair.second) {
+    // Didn't get inserted because it's already present!
+    return ERR(DUPLICATE);
+  }
+  return OK;
+}
+
+jvmtiError FieldUtil::ClearFieldModificationWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto pos = env->modify_watched_fields.find(art::jni::DecodeArtField(field));
+  if (pos == env->modify_watched_fields.end()) {
+    return ERR(NOT_FOUND);
+  }
+  env->modify_watched_fields.erase(pos);
+  return OK;
+}
+
+jvmtiError FieldUtil::SetFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto res_pair = env->access_watched_fields.insert(art::jni::DecodeArtField(field));
+  if (!res_pair.second) {
+    // Didn't get inserted because it's already present!
+    return ERR(DUPLICATE);
+  }
+  return OK;
+}
+
+jvmtiError FieldUtil::ClearFieldAccessWatch(jvmtiEnv* jenv, jclass klass, jfieldID field) {
+  ArtJvmTiEnv* env = ArtJvmTiEnv::AsArtJvmTiEnv(jenv);
+  if (klass == nullptr) {
+    return ERR(INVALID_CLASS);
+  }
+  if (field == nullptr) {
+    return ERR(INVALID_FIELDID);
+  }
+  auto pos = env->access_watched_fields.find(art::jni::DecodeArtField(field));
+  if (pos == env->access_watched_fields.end()) {
+    return ERR(NOT_FOUND);
+  }
+  env->access_watched_fields.erase(pos);
+  return OK;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_field.h b/openjdkjvmti/ti_field.h
new file mode 100644
index 0000000..8a229ed
--- /dev/null
+++ b/openjdkjvmti/ti_field.h
@@ -0,0 +1,72 @@
+/* Copyright (C) 2016 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_TI_FIELD_H_
+#define ART_OPENJDKJVMTI_TI_FIELD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class FieldUtil {
+ public:
+  static jvmtiError GetFieldName(jvmtiEnv* env,
+                                 jclass klass,
+                                 jfieldID field,
+                                 char** name_ptr,
+                                 char** signature_ptr,
+                                 char** generic_ptr);
+
+  static jvmtiError GetFieldDeclaringClass(jvmtiEnv* env,
+                                           jclass klass,
+                                           jfieldID field,
+                                           jclass* declaring_class_ptr);
+
+  static jvmtiError GetFieldModifiers(jvmtiEnv* env,
+                                      jclass klass,
+                                      jfieldID field,
+                                      jint* modifiers_ptr);
+
+  static jvmtiError IsFieldSynthetic(jvmtiEnv* env,
+                                     jclass klass,
+                                     jfieldID field,
+                                     jboolean* is_synthetic_ptr);
+
+  static jvmtiError SetFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+  static jvmtiError ClearFieldModificationWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+  static jvmtiError SetFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+  static jvmtiError ClearFieldAccessWatch(jvmtiEnv* env, jclass klass, jfieldID field);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_FIELD_H_
diff --git a/openjdkjvmti/ti_heap.cc b/openjdkjvmti/ti_heap.cc
new file mode 100644
index 0000000..3397210
--- /dev/null
+++ b/openjdkjvmti/ti_heap.cc
@@ -0,0 +1,1522 @@
+/*
+ * 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.
+ */
+
+#include "ti_heap.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "class_linker.h"
+#include "gc/heap-visit-objects-inl.h"
+#include "gc/heap.h"
+#include "gc_root-inl.h"
+#include "java_frame_root_info.h"
+#include "jni_env_ext.h"
+#include "jni_internal.h"
+#include "jvmti_weak_table-inl.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/object_array-inl.h"
+#include "obj_ptr-inl.h"
+#include "object_tagging.h"
+#include "primitive.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+
+namespace openjdkjvmti {
+
+namespace {
+
+struct IndexCache {
+  // The number of interface fields implemented by the class. This is a prefix to all assigned
+  // field indices.
+  size_t interface_fields;
+
+  // It would be nice to also cache the following, but it is complicated to wire up into the
+  // generic visit:
+  // The number of fields in interfaces and superclasses. This is the first index assigned to
+  // fields of the class.
+  // size_t superclass_fields;
+};
+using IndexCachingTable = JvmtiWeakTable<IndexCache>;
+
+static IndexCachingTable gIndexCachingTable;
+
+// Report the contents of a string, if a callback is set.
+jint ReportString(art::ObjPtr<art::mirror::Object> obj,
+                  jvmtiEnv* env,
+                  ObjectTagTable* tag_table,
+                  const jvmtiHeapCallbacks* cb,
+                  const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (UNLIKELY(cb->string_primitive_value_callback != nullptr) && obj->IsString()) {
+    art::ObjPtr<art::mirror::String> str = obj->AsString();
+    int32_t string_length = str->GetLength();
+    JvmtiUniquePtr<uint16_t[]> data;
+
+    if (string_length > 0) {
+      jvmtiError alloc_error;
+      data = AllocJvmtiUniquePtr<uint16_t[]>(env, string_length, &alloc_error);
+      if (data == nullptr) {
+        // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+        //       back? For now just warn.
+        LOG(WARNING) << "Unable to allocate buffer for string reporting! Silently dropping value."
+                     << " >" << str->ToModifiedUtf8() << "<";
+        return 0;
+      }
+
+      if (str->IsCompressed()) {
+        uint8_t* compressed_data = str->GetValueCompressed();
+        for (int32_t i = 0; i != string_length; ++i) {
+          data[i] = compressed_data[i];
+        }
+      } else {
+        // Can copy directly.
+        memcpy(data.get(), str->GetValue(), string_length * sizeof(uint16_t));
+      }
+    }
+
+    const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+    jlong string_tag = tag_table->GetTagOrZero(obj.Ptr());
+    const jlong saved_string_tag = string_tag;
+
+    jint result = cb->string_primitive_value_callback(class_tag,
+                                                      obj->SizeOf(),
+                                                      &string_tag,
+                                                      data.get(),
+                                                      string_length,
+                                                      const_cast<void*>(user_data));
+    if (string_tag != saved_string_tag) {
+      tag_table->Set(obj.Ptr(), string_tag);
+    }
+
+    return result;
+  }
+  return 0;
+}
+
+// Report the contents of a primitive array, if a callback is set.
+jint ReportPrimitiveArray(art::ObjPtr<art::mirror::Object> obj,
+                          jvmtiEnv* env,
+                          ObjectTagTable* tag_table,
+                          const jvmtiHeapCallbacks* cb,
+                          const void* user_data) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (UNLIKELY(cb->array_primitive_value_callback != nullptr) &&
+      obj->IsArrayInstance() &&
+      !obj->IsObjectArray()) {
+    art::ObjPtr<art::mirror::Array> array = obj->AsArray();
+    int32_t array_length = array->GetLength();
+    size_t component_size = array->GetClass()->GetComponentSize();
+    art::Primitive::Type art_prim_type = array->GetClass()->GetComponentType()->GetPrimitiveType();
+    jvmtiPrimitiveType prim_type =
+        static_cast<jvmtiPrimitiveType>(art::Primitive::Descriptor(art_prim_type)[0]);
+    DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_BYTE ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_CHAR ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_SHORT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_INT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_LONG ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE);
+
+    const jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+    jlong array_tag = tag_table->GetTagOrZero(obj.Ptr());
+    const jlong saved_array_tag = array_tag;
+
+    jint result;
+    if (array_length == 0) {
+      result = cb->array_primitive_value_callback(class_tag,
+                                                  obj->SizeOf(),
+                                                  &array_tag,
+                                                  0,
+                                                  prim_type,
+                                                  nullptr,
+                                                  const_cast<void*>(user_data));
+    } else {
+      jvmtiError alloc_error;
+      JvmtiUniquePtr<char[]> data = AllocJvmtiUniquePtr<char[]>(env,
+                                                                array_length * component_size,
+                                                                &alloc_error);
+      if (data == nullptr) {
+        // TODO: Not really sure what to do here. Should we abort the iteration and go all the way
+        //       back? For now just warn.
+        LOG(WARNING) << "Unable to allocate buffer for array reporting! Silently dropping value.";
+        return 0;
+      }
+
+      memcpy(data.get(), array->GetRawData(component_size, 0), array_length * component_size);
+
+      result = cb->array_primitive_value_callback(class_tag,
+                                                  obj->SizeOf(),
+                                                  &array_tag,
+                                                  array_length,
+                                                  prim_type,
+                                                  data.get(),
+                                                  const_cast<void*>(user_data));
+    }
+
+    if (array_tag != saved_array_tag) {
+      tag_table->Set(obj.Ptr(), array_tag);
+    }
+
+    return result;
+  }
+  return 0;
+}
+
+template <typename UserData>
+bool VisitorFalse(art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+                  art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+                  art::ArtField& field ATTRIBUTE_UNUSED,
+                  size_t field_index ATTRIBUTE_UNUSED,
+                  UserData* user_data ATTRIBUTE_UNUSED) {
+  return false;
+}
+
+template <typename UserData, bool kCallVisitorOnRecursion>
+class FieldVisitor {
+ public:
+  // Report the contents of a primitive fields of the given object, if a callback is set.
+  template <typename StaticPrimitiveVisitor,
+            typename StaticReferenceVisitor,
+            typename InstancePrimitiveVisitor,
+            typename InstanceReferenceVisitor>
+  static bool ReportFields(art::ObjPtr<art::mirror::Object> obj,
+                           UserData* user_data,
+                           StaticPrimitiveVisitor& static_prim_visitor,
+                           StaticReferenceVisitor& static_ref_visitor,
+                           InstancePrimitiveVisitor& instance_prim_visitor,
+                           InstanceReferenceVisitor& instance_ref_visitor)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    FieldVisitor fv(user_data);
+
+    if (obj->IsClass()) {
+      // When visiting a class, we only visit the static fields of the given class. No field of
+      // superclasses is visited.
+      art::ObjPtr<art::mirror::Class> klass = obj->AsClass();
+      // Only report fields on resolved classes. We need valid field data.
+      if (!klass->IsResolved()) {
+        return false;
+      }
+      return fv.ReportFieldsImpl(nullptr,
+                                 obj->AsClass(),
+                                 obj->AsClass()->IsInterface(),
+                                 static_prim_visitor,
+                                 static_ref_visitor,
+                                 instance_prim_visitor,
+                                 instance_ref_visitor);
+    } else {
+      // See comment above. Just double-checking here, but an instance *should* mean the class was
+      // resolved.
+      DCHECK(obj->GetClass()->IsResolved() || obj->GetClass()->IsErroneousResolved());
+      return fv.ReportFieldsImpl(obj,
+                                 obj->GetClass(),
+                                 false,
+                                 static_prim_visitor,
+                                 static_ref_visitor,
+                                 instance_prim_visitor,
+                                 instance_ref_visitor);
+    }
+  }
+
+ private:
+  explicit FieldVisitor(UserData* user_data) : user_data_(user_data) {}
+
+  // Report the contents of fields of the given object. If obj is null, report the static fields,
+  // otherwise the instance fields.
+  template <typename StaticPrimitiveVisitor,
+            typename StaticReferenceVisitor,
+            typename InstancePrimitiveVisitor,
+            typename InstanceReferenceVisitor>
+  bool ReportFieldsImpl(art::ObjPtr<art::mirror::Object> obj,
+                        art::ObjPtr<art::mirror::Class> klass,
+                        bool skip_java_lang_object,
+                        StaticPrimitiveVisitor& static_prim_visitor,
+                        StaticReferenceVisitor& static_ref_visitor,
+                        InstancePrimitiveVisitor& instance_prim_visitor,
+                        InstanceReferenceVisitor& instance_ref_visitor)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // Compute the offset of field indices.
+    size_t interface_field_count = CountInterfaceFields(klass);
+
+    size_t tmp;
+    bool aborted = ReportFieldsRecursive(obj,
+                                         klass,
+                                         interface_field_count,
+                                         skip_java_lang_object,
+                                         static_prim_visitor,
+                                         static_ref_visitor,
+                                         instance_prim_visitor,
+                                         instance_ref_visitor,
+                                         &tmp);
+    return aborted;
+  }
+
+  // Visit primitive fields in an object (instance). Return true if the visit was aborted.
+  template <typename StaticPrimitiveVisitor,
+            typename StaticReferenceVisitor,
+            typename InstancePrimitiveVisitor,
+            typename InstanceReferenceVisitor>
+  bool ReportFieldsRecursive(art::ObjPtr<art::mirror::Object> obj,
+                             art::ObjPtr<art::mirror::Class> klass,
+                             size_t interface_fields,
+                             bool skip_java_lang_object,
+                             StaticPrimitiveVisitor& static_prim_visitor,
+                             StaticReferenceVisitor& static_ref_visitor,
+                             InstancePrimitiveVisitor& instance_prim_visitor,
+                             InstanceReferenceVisitor& instance_ref_visitor,
+                             size_t* field_index_out)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK(klass != nullptr);
+    size_t field_index;
+    if (klass->GetSuperClass() == nullptr) {
+      // j.l.Object. Start with the fields from interfaces.
+      field_index = interface_fields;
+      if (skip_java_lang_object) {
+        *field_index_out = field_index;
+        return false;
+      }
+    } else {
+      // Report superclass fields.
+      if (kCallVisitorOnRecursion) {
+        if (ReportFieldsRecursive(obj,
+                                  klass->GetSuperClass(),
+                                  interface_fields,
+                                  skip_java_lang_object,
+                                  static_prim_visitor,
+                                  static_ref_visitor,
+                                  instance_prim_visitor,
+                                  instance_ref_visitor,
+                                  &field_index)) {
+          return true;
+        }
+      } else {
+        // Still call, but with empty visitor. This is required for correct counting.
+        ReportFieldsRecursive(obj,
+                              klass->GetSuperClass(),
+                              interface_fields,
+                              skip_java_lang_object,
+                              VisitorFalse<UserData>,
+                              VisitorFalse<UserData>,
+                              VisitorFalse<UserData>,
+                              VisitorFalse<UserData>,
+                              &field_index);
+      }
+    }
+
+    // Now visit fields for the current klass.
+
+    for (auto& static_field : klass->GetSFields()) {
+      if (static_field.IsPrimitiveType()) {
+        if (static_prim_visitor(obj,
+                                klass,
+                                static_field,
+                                field_index,
+                                user_data_)) {
+          return true;
+        }
+      } else {
+        if (static_ref_visitor(obj,
+                               klass,
+                               static_field,
+                               field_index,
+                               user_data_)) {
+          return true;
+        }
+      }
+      field_index++;
+    }
+
+    for (auto& instance_field : klass->GetIFields()) {
+      if (instance_field.IsPrimitiveType()) {
+        if (instance_prim_visitor(obj,
+                                  klass,
+                                  instance_field,
+                                  field_index,
+                                  user_data_)) {
+          return true;
+        }
+      } else {
+        if (instance_ref_visitor(obj,
+                                 klass,
+                                 instance_field,
+                                 field_index,
+                                 user_data_)) {
+          return true;
+        }
+      }
+      field_index++;
+    }
+
+    *field_index_out = field_index;
+    return false;
+  }
+
+  // Implements a visit of the implemented interfaces of a given class.
+  template <typename T>
+  struct RecursiveInterfaceVisit {
+    static void VisitStatic(art::Thread* self, art::ObjPtr<art::mirror::Class> klass, T& visitor)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      RecursiveInterfaceVisit rv;
+      rv.Visit(self, klass, visitor);
+    }
+
+    void Visit(art::Thread* self, art::ObjPtr<art::mirror::Class> klass, T& visitor)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      // First visit the parent, to get the order right.
+      // (We do this in preparation for actual visiting of interface fields.)
+      if (klass->GetSuperClass() != nullptr) {
+        Visit(self, klass->GetSuperClass(), visitor);
+      }
+      for (uint32_t i = 0; i != klass->NumDirectInterfaces(); ++i) {
+        art::ObjPtr<art::mirror::Class> inf_klass =
+            art::mirror::Class::GetDirectInterface(self, klass, i);
+        DCHECK(inf_klass != nullptr);
+        VisitInterface(self, inf_klass, visitor);
+      }
+    }
+
+    void VisitInterface(art::Thread* self, art::ObjPtr<art::mirror::Class> inf_klass, T& visitor)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      auto it = visited_interfaces.find(inf_klass.Ptr());
+      if (it != visited_interfaces.end()) {
+        return;
+      }
+      visited_interfaces.insert(inf_klass.Ptr());
+
+      // Let the visitor know about this one. Note that this order is acceptable, as the ordering
+      // of these fields never matters for known visitors.
+      visitor(inf_klass);
+
+      // Now visit the superinterfaces.
+      for (uint32_t i = 0; i != inf_klass->NumDirectInterfaces(); ++i) {
+        art::ObjPtr<art::mirror::Class> super_inf_klass =
+            art::mirror::Class::GetDirectInterface(self, inf_klass, i);
+        DCHECK(super_inf_klass != nullptr);
+        VisitInterface(self, super_inf_klass, visitor);
+      }
+    }
+
+    std::unordered_set<art::mirror::Class*> visited_interfaces;
+  };
+
+  // Counting interface fields. Note that we cannot use the interface table, as that only contains
+  // "non-marker" interfaces (= interfaces with methods).
+  static size_t CountInterfaceFields(art::ObjPtr<art::mirror::Class> klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // Do we have a cached value?
+    IndexCache tmp;
+    if (gIndexCachingTable.GetTag(klass.Ptr(), &tmp)) {
+      return tmp.interface_fields;
+    }
+
+    size_t count = 0;
+    auto visitor = [&count](art::ObjPtr<art::mirror::Class> inf_klass)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      DCHECK(inf_klass->IsInterface());
+      DCHECK_EQ(0u, inf_klass->NumInstanceFields());
+      count += inf_klass->NumStaticFields();
+    };
+    RecursiveInterfaceVisit<decltype(visitor)>::VisitStatic(art::Thread::Current(), klass, visitor);
+
+    // Store this into the cache.
+    tmp.interface_fields = count;
+    gIndexCachingTable.Set(klass.Ptr(), tmp);
+
+    return count;
+  }
+
+  UserData* user_data_;
+};
+
+// Debug helper. Prints the structure of an object.
+template <bool kStatic, bool kRef>
+struct DumpVisitor {
+  static bool Callback(art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+                       art::ObjPtr<art::mirror::Class> klass ATTRIBUTE_UNUSED,
+                       art::ArtField& field,
+                       size_t field_index,
+                       void* user_data ATTRIBUTE_UNUSED)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    LOG(ERROR) << (kStatic ? "static " : "instance ")
+               << (kRef ? "ref " : "primitive ")
+               << field.PrettyField()
+               << " @ "
+               << field_index;
+    return false;
+  }
+};
+ATTRIBUTE_UNUSED
+void DumpObjectFields(art::ObjPtr<art::mirror::Object> obj)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (obj->IsClass()) {
+    FieldVisitor<void, false>:: ReportFields(obj,
+                                             nullptr,
+                                             DumpVisitor<true, false>::Callback,
+                                             DumpVisitor<true, true>::Callback,
+                                             DumpVisitor<false, false>::Callback,
+                                             DumpVisitor<false, true>::Callback);
+  } else {
+    FieldVisitor<void, true>::ReportFields(obj,
+                                           nullptr,
+                                           DumpVisitor<true, false>::Callback,
+                                           DumpVisitor<true, true>::Callback,
+                                           DumpVisitor<false, false>::Callback,
+                                           DumpVisitor<false, true>::Callback);
+  }
+}
+
+class ReportPrimitiveField {
+ public:
+  static bool Report(art::ObjPtr<art::mirror::Object> obj,
+                     ObjectTagTable* tag_table,
+                     const jvmtiHeapCallbacks* cb,
+                     const void* user_data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (UNLIKELY(cb->primitive_field_callback != nullptr)) {
+      jlong class_tag = tag_table->GetTagOrZero(obj->GetClass());
+      ReportPrimitiveField rpf(tag_table, class_tag, cb, user_data);
+      if (obj->IsClass()) {
+        return FieldVisitor<ReportPrimitiveField, false>::ReportFields(
+            obj,
+            &rpf,
+            ReportPrimitiveFieldCallback<true>,
+            VisitorFalse<ReportPrimitiveField>,
+            VisitorFalse<ReportPrimitiveField>,
+            VisitorFalse<ReportPrimitiveField>);
+      } else {
+        return FieldVisitor<ReportPrimitiveField, true>::ReportFields(
+            obj,
+            &rpf,
+            VisitorFalse<ReportPrimitiveField>,
+            VisitorFalse<ReportPrimitiveField>,
+            ReportPrimitiveFieldCallback<false>,
+            VisitorFalse<ReportPrimitiveField>);
+      }
+    }
+    return false;
+  }
+
+
+ private:
+  ReportPrimitiveField(ObjectTagTable* tag_table,
+                       jlong class_tag,
+                       const jvmtiHeapCallbacks* cb,
+                       const void* user_data)
+      : tag_table_(tag_table), class_tag_(class_tag), cb_(cb), user_data_(user_data) {}
+
+  template <bool kReportStatic>
+  static bool ReportPrimitiveFieldCallback(art::ObjPtr<art::mirror::Object> obj,
+                                           art::ObjPtr<art::mirror::Class> klass,
+                                           art::ArtField& field,
+                                           size_t field_index,
+                                           ReportPrimitiveField* user_data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::Primitive::Type art_prim_type = field.GetTypeAsPrimitiveType();
+    jvmtiPrimitiveType prim_type =
+        static_cast<jvmtiPrimitiveType>(art::Primitive::Descriptor(art_prim_type)[0]);
+    DCHECK(prim_type == JVMTI_PRIMITIVE_TYPE_BOOLEAN ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_BYTE ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_CHAR ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_SHORT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_INT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_LONG ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_FLOAT ||
+           prim_type == JVMTI_PRIMITIVE_TYPE_DOUBLE);
+    jvmtiHeapReferenceInfo info;
+    info.field.index = field_index;
+
+    jvalue value;
+    memset(&value, 0, sizeof(jvalue));
+    art::ObjPtr<art::mirror::Object> src = kReportStatic ? klass : obj;
+    switch (art_prim_type) {
+      case art::Primitive::Type::kPrimBoolean:
+        value.z = field.GetBoolean(src) == 0 ? JNI_FALSE : JNI_TRUE;
+        break;
+      case art::Primitive::Type::kPrimByte:
+        value.b = field.GetByte(src);
+        break;
+      case art::Primitive::Type::kPrimChar:
+        value.c = field.GetChar(src);
+        break;
+      case art::Primitive::Type::kPrimShort:
+        value.s = field.GetShort(src);
+        break;
+      case art::Primitive::Type::kPrimInt:
+        value.i = field.GetInt(src);
+        break;
+      case art::Primitive::Type::kPrimLong:
+        value.j = field.GetLong(src);
+        break;
+      case art::Primitive::Type::kPrimFloat:
+        value.f = field.GetFloat(src);
+        break;
+      case art::Primitive::Type::kPrimDouble:
+        value.d = field.GetDouble(src);
+        break;
+      case art::Primitive::Type::kPrimVoid:
+      case art::Primitive::Type::kPrimNot: {
+        LOG(FATAL) << "Should not reach here";
+        UNREACHABLE();
+      }
+    }
+
+    jlong obj_tag = user_data->tag_table_->GetTagOrZero(src.Ptr());
+    const jlong saved_obj_tag = obj_tag;
+
+    jint ret = user_data->cb_->primitive_field_callback(kReportStatic
+                                                            ? JVMTI_HEAP_REFERENCE_STATIC_FIELD
+                                                            : JVMTI_HEAP_REFERENCE_FIELD,
+                                                        &info,
+                                                        user_data->class_tag_,
+                                                        &obj_tag,
+                                                        value,
+                                                        prim_type,
+                                                        const_cast<void*>(user_data->user_data_));
+
+    if (saved_obj_tag != obj_tag) {
+      user_data->tag_table_->Set(src.Ptr(), obj_tag);
+    }
+
+    if ((ret & JVMTI_VISIT_ABORT) != 0) {
+      return true;
+    }
+
+    return false;
+  }
+
+  ObjectTagTable* tag_table_;
+  jlong class_tag_;
+  const jvmtiHeapCallbacks* cb_;
+  const void* user_data_;
+};
+
+struct HeapFilter {
+  explicit HeapFilter(jint heap_filter)
+      : filter_out_tagged((heap_filter & JVMTI_HEAP_FILTER_TAGGED) != 0),
+        filter_out_untagged((heap_filter & JVMTI_HEAP_FILTER_UNTAGGED) != 0),
+        filter_out_class_tagged((heap_filter & JVMTI_HEAP_FILTER_CLASS_TAGGED) != 0),
+        filter_out_class_untagged((heap_filter & JVMTI_HEAP_FILTER_CLASS_UNTAGGED) != 0),
+        any_filter(filter_out_tagged ||
+                   filter_out_untagged ||
+                   filter_out_class_tagged ||
+                   filter_out_class_untagged) {
+  }
+
+  bool ShouldReportByHeapFilter(jlong tag, jlong class_tag) const {
+    if (!any_filter) {
+      return true;
+    }
+
+    if ((tag == 0 && filter_out_untagged) || (tag != 0 && filter_out_tagged)) {
+      return false;
+    }
+
+    if ((class_tag == 0 && filter_out_class_untagged) ||
+        (class_tag != 0 && filter_out_class_tagged)) {
+      return false;
+    }
+
+    return true;
+  }
+
+  const bool filter_out_tagged;
+  const bool filter_out_untagged;
+  const bool filter_out_class_tagged;
+  const bool filter_out_class_untagged;
+  const bool any_filter;
+};
+
+}  // namespace
+
+void HeapUtil::Register() {
+  art::Runtime::Current()->AddSystemWeakHolder(&gIndexCachingTable);
+}
+
+void HeapUtil::Unregister() {
+  art::Runtime::Current()->RemoveSystemWeakHolder(&gIndexCachingTable);
+}
+
+template <typename T>
+static jvmtiError DoIterateThroughHeap(T fn,
+                                       jvmtiEnv* env,
+                                       ObjectTagTable* tag_table,
+                                       jint heap_filter_int,
+                                       jclass klass,
+                                       const jvmtiHeapCallbacks* callbacks,
+                                       const void* user_data) {
+  if (callbacks == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);      // Now we know we have the shared lock.
+
+  bool stop_reports = false;
+  const HeapFilter heap_filter(heap_filter_int);
+  art::ObjPtr<art::mirror::Class> filter_klass = soa.Decode<art::mirror::Class>(klass);
+  auto visitor = [&](art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    // Early return, as we can't really stop visiting.
+    if (stop_reports) {
+      return;
+    }
+
+    art::ScopedAssertNoThreadSuspension no_suspension("IterateThroughHeapCallback");
+
+    jlong tag = 0;
+    tag_table->GetTag(obj, &tag);
+
+    jlong class_tag = 0;
+    art::ObjPtr<art::mirror::Class> klass = obj->GetClass();
+    tag_table->GetTag(klass.Ptr(), &class_tag);
+    // For simplicity, even if we find a tag = 0, assume 0 = not tagged.
+
+    if (!heap_filter.ShouldReportByHeapFilter(tag, class_tag)) {
+      return;
+    }
+
+    if (filter_klass != nullptr) {
+      if (filter_klass != klass) {
+        return;
+      }
+    }
+
+    jlong size = obj->SizeOf();
+
+    jint length = -1;
+    if (obj->IsArrayInstance()) {
+      length = obj->AsArray()->GetLength();
+    }
+
+    jlong saved_tag = tag;
+    jint ret = fn(obj, callbacks, class_tag, size, &tag, length, const_cast<void*>(user_data));
+
+    if (tag != saved_tag) {
+      tag_table->Set(obj, tag);
+    }
+
+    stop_reports = (ret & JVMTI_VISIT_ABORT) != 0;
+
+    if (!stop_reports) {
+      jint string_ret = ReportString(obj, env, tag_table, callbacks, user_data);
+      stop_reports = (string_ret & JVMTI_VISIT_ABORT) != 0;
+    }
+
+    if (!stop_reports) {
+      jint array_ret = ReportPrimitiveArray(obj, env, tag_table, callbacks, user_data);
+      stop_reports = (array_ret & JVMTI_VISIT_ABORT) != 0;
+    }
+
+    if (!stop_reports) {
+      stop_reports = ReportPrimitiveField::Report(obj, tag_table, callbacks, user_data);
+    }
+  };
+  art::Runtime::Current()->GetHeap()->VisitObjects(visitor);
+
+  return ERR(NONE);
+}
+
+jvmtiError HeapUtil::IterateThroughHeap(jvmtiEnv* env,
+                                        jint heap_filter,
+                                        jclass klass,
+                                        const jvmtiHeapCallbacks* callbacks,
+                                        const void* user_data) {
+  auto JvmtiIterateHeap = [](art::mirror::Object* obj ATTRIBUTE_UNUSED,
+                             const jvmtiHeapCallbacks* cb_callbacks,
+                             jlong class_tag,
+                             jlong size,
+                             jlong* tag,
+                             jint length,
+                             void* cb_user_data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return cb_callbacks->heap_iteration_callback(class_tag,
+                                                 size,
+                                                 tag,
+                                                 length,
+                                                 cb_user_data);
+  };
+  return DoIterateThroughHeap(JvmtiIterateHeap,
+                              env,
+                              ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get(),
+                              heap_filter,
+                              klass,
+                              callbacks,
+                              user_data);
+}
+
+class FollowReferencesHelper FINAL {
+ public:
+  FollowReferencesHelper(HeapUtil* h,
+                         jvmtiEnv* jvmti_env,
+                         art::ObjPtr<art::mirror::Object> initial_object,
+                         const jvmtiHeapCallbacks* callbacks,
+                         art::ObjPtr<art::mirror::Class> class_filter,
+                         jint heap_filter,
+                         const void* user_data)
+      : env(jvmti_env),
+        tag_table_(h->GetTags()),
+        initial_object_(initial_object),
+        callbacks_(callbacks),
+        class_filter_(class_filter),
+        heap_filter_(heap_filter),
+        user_data_(user_data),
+        start_(0),
+        stop_reports_(false) {
+  }
+
+  void Init()
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    if (initial_object_.IsNull()) {
+      CollectAndReportRootsVisitor carrv(this, tag_table_, &worklist_, &visited_);
+
+      // We need precise info (e.g., vregs).
+      constexpr art::VisitRootFlags kRootFlags = static_cast<art::VisitRootFlags>(
+          art::VisitRootFlags::kVisitRootFlagAllRoots | art::VisitRootFlags::kVisitRootFlagPrecise);
+      art::Runtime::Current()->VisitRoots(&carrv, kRootFlags);
+
+      art::Runtime::Current()->VisitImageRoots(&carrv);
+      stop_reports_ = carrv.IsStopReports();
+
+      if (stop_reports_) {
+        worklist_.clear();
+      }
+    } else {
+      visited_.insert(initial_object_.Ptr());
+      worklist_.push_back(initial_object_.Ptr());
+    }
+  }
+
+  void Work()
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    // Currently implemented as a BFS. To lower overhead, we don't erase elements immediately
+    // from the head of the work list, instead postponing until there's a gap that's "large."
+    //
+    // Alternatively, we can implement a DFS and use the work list as a stack.
+    while (start_ < worklist_.size()) {
+      art::mirror::Object* cur_obj = worklist_[start_];
+      start_++;
+
+      if (start_ >= kMaxStart) {
+        worklist_.erase(worklist_.begin(), worklist_.begin() + start_);
+        start_ = 0;
+      }
+
+      VisitObject(cur_obj);
+
+      if (stop_reports_) {
+        break;
+      }
+    }
+  }
+
+ private:
+  class CollectAndReportRootsVisitor FINAL : public art::RootVisitor {
+   public:
+    CollectAndReportRootsVisitor(FollowReferencesHelper* helper,
+                                 ObjectTagTable* tag_table,
+                                 std::vector<art::mirror::Object*>* worklist,
+                                 std::unordered_set<art::mirror::Object*>* visited)
+        : helper_(helper),
+          tag_table_(tag_table),
+          worklist_(worklist),
+          visited_(visited),
+          stop_reports_(false) {}
+
+    void VisitRoots(art::mirror::Object*** roots, size_t count, const art::RootInfo& info)
+        OVERRIDE
+        REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!*helper_->tag_table_->GetAllowDisallowLock()) {
+      for (size_t i = 0; i != count; ++i) {
+        AddRoot(*roots[i], info);
+      }
+    }
+
+    void VisitRoots(art::mirror::CompressedReference<art::mirror::Object>** roots,
+                    size_t count,
+                    const art::RootInfo& info)
+        OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!*helper_->tag_table_->GetAllowDisallowLock()) {
+      for (size_t i = 0; i != count; ++i) {
+        AddRoot(roots[i]->AsMirrorPtr(), info);
+      }
+    }
+
+    bool IsStopReports() {
+      return stop_reports_;
+    }
+
+   private:
+    void AddRoot(art::mirror::Object* root_obj, const art::RootInfo& info)
+        REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+      if (stop_reports_) {
+        return;
+      }
+      bool add_to_worklist = ReportRoot(root_obj, info);
+      // We use visited_ to mark roots already so we do not need another set.
+      if (visited_->find(root_obj) == visited_->end()) {
+        if (add_to_worklist) {
+          visited_->insert(root_obj);
+          worklist_->push_back(root_obj);
+        }
+      }
+    }
+
+    // Remove NO_THREAD_SAFETY_ANALYSIS once ASSERT_CAPABILITY works correctly.
+    art::Thread* FindThread(const art::RootInfo& info) NO_THREAD_SAFETY_ANALYSIS {
+      art::Locks::thread_list_lock_->AssertExclusiveHeld(art::Thread::Current());
+      return art::Runtime::Current()->GetThreadList()->FindThreadByThreadId(info.GetThreadId());
+    }
+
+    jvmtiHeapReferenceKind GetReferenceKind(const art::RootInfo& info,
+                                            jvmtiHeapReferenceInfo* ref_info)
+        REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      // TODO: Fill in ref_info.
+      memset(ref_info, 0, sizeof(jvmtiHeapReferenceInfo));
+
+      switch (info.GetType()) {
+        case art::RootType::kRootJNIGlobal:
+          return JVMTI_HEAP_REFERENCE_JNI_GLOBAL;
+
+        case art::RootType::kRootJNILocal:
+        {
+          uint32_t thread_id = info.GetThreadId();
+          ref_info->jni_local.thread_id = thread_id;
+
+          art::Thread* thread = FindThread(info);
+          if (thread != nullptr) {
+            art::mirror::Object* thread_obj;
+            if (thread->IsStillStarting()) {
+              thread_obj = nullptr;
+            } else {
+              thread_obj = thread->GetPeerFromOtherThread();
+            }
+            if (thread_obj != nullptr) {
+              ref_info->jni_local.thread_tag = tag_table_->GetTagOrZero(thread_obj);
+            }
+          }
+
+          // TODO: We don't have this info.
+          if (thread != nullptr) {
+            ref_info->jni_local.depth = 0;
+            art::ArtMethod* method = thread->GetCurrentMethod(nullptr, false /* abort_on_error */);
+            if (method != nullptr) {
+              ref_info->jni_local.method = art::jni::EncodeArtMethod(method);
+            }
+          }
+
+          return JVMTI_HEAP_REFERENCE_JNI_LOCAL;
+        }
+
+        case art::RootType::kRootJavaFrame:
+        {
+          uint32_t thread_id = info.GetThreadId();
+          ref_info->stack_local.thread_id = thread_id;
+
+          art::Thread* thread = FindThread(info);
+          if (thread != nullptr) {
+            art::mirror::Object* thread_obj;
+            if (thread->IsStillStarting()) {
+              thread_obj = nullptr;
+            } else {
+              thread_obj = thread->GetPeerFromOtherThread();
+            }
+            if (thread_obj != nullptr) {
+              ref_info->stack_local.thread_tag = tag_table_->GetTagOrZero(thread_obj);
+            }
+          }
+
+          auto& java_info = static_cast<const art::JavaFrameRootInfo&>(info);
+          ref_info->stack_local.slot = static_cast<jint>(java_info.GetVReg());
+          const art::StackVisitor* visitor = java_info.GetVisitor();
+          ref_info->stack_local.location =
+              static_cast<jlocation>(visitor->GetDexPc(false /* abort_on_failure */));
+          ref_info->stack_local.depth = static_cast<jint>(visitor->GetFrameDepth());
+          art::ArtMethod* method = visitor->GetMethod();
+          if (method != nullptr) {
+            ref_info->stack_local.method = art::jni::EncodeArtMethod(method);
+          }
+
+          return JVMTI_HEAP_REFERENCE_STACK_LOCAL;
+        }
+
+        case art::RootType::kRootNativeStack:
+        case art::RootType::kRootThreadBlock:
+        case art::RootType::kRootThreadObject:
+          return JVMTI_HEAP_REFERENCE_THREAD;
+
+        case art::RootType::kRootStickyClass:
+        case art::RootType::kRootInternedString:
+          // Note: this isn't a root in the RI.
+          return JVMTI_HEAP_REFERENCE_SYSTEM_CLASS;
+
+        case art::RootType::kRootMonitorUsed:
+        case art::RootType::kRootJNIMonitor:
+          return JVMTI_HEAP_REFERENCE_MONITOR;
+
+        case art::RootType::kRootFinalizing:
+        case art::RootType::kRootDebugger:
+        case art::RootType::kRootReferenceCleanup:
+        case art::RootType::kRootVMInternal:
+        case art::RootType::kRootUnknown:
+          return JVMTI_HEAP_REFERENCE_OTHER;
+      }
+      LOG(FATAL) << "Unreachable";
+      UNREACHABLE();
+    }
+
+    bool ReportRoot(art::mirror::Object* root_obj, const art::RootInfo& info)
+        REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+      jvmtiHeapReferenceInfo ref_info;
+      jvmtiHeapReferenceKind kind = GetReferenceKind(info, &ref_info);
+      jint result = helper_->ReportReference(kind, &ref_info, nullptr, root_obj);
+      if ((result & JVMTI_VISIT_ABORT) != 0) {
+        stop_reports_ = true;
+      }
+      return (result & JVMTI_VISIT_OBJECTS) != 0;
+    }
+
+   private:
+    FollowReferencesHelper* helper_;
+    ObjectTagTable* tag_table_;
+    std::vector<art::mirror::Object*>* worklist_;
+    std::unordered_set<art::mirror::Object*>* visited_;
+    bool stop_reports_;
+  };
+
+  void VisitObject(art::mirror::Object* obj)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    if (obj->IsClass()) {
+      VisitClass(obj->AsClass());
+      return;
+    }
+    if (obj->IsArrayInstance()) {
+      VisitArray(obj);
+      return;
+    }
+
+    // All instance fields.
+    auto report_instance_field = [&](art::ObjPtr<art::mirror::Object> src,
+                                     art::ObjPtr<art::mirror::Class> obj_klass ATTRIBUTE_UNUSED,
+                                     art::ArtField& field,
+                                     size_t field_index,
+                                     void* user_data ATTRIBUTE_UNUSED)
+        REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+      art::ObjPtr<art::mirror::Object> field_value = field.GetObject(src);
+      if (field_value != nullptr) {
+        jvmtiHeapReferenceInfo reference_info;
+        memset(&reference_info, 0, sizeof(reference_info));
+
+        reference_info.field.index = field_index;
+
+        jvmtiHeapReferenceKind kind =
+            field.GetOffset().Int32Value() == art::mirror::Object::ClassOffset().Int32Value()
+                ? JVMTI_HEAP_REFERENCE_CLASS
+                : JVMTI_HEAP_REFERENCE_FIELD;
+        const jvmtiHeapReferenceInfo* reference_info_ptr =
+            kind == JVMTI_HEAP_REFERENCE_CLASS ? nullptr : &reference_info;
+
+        return !ReportReferenceMaybeEnqueue(kind, reference_info_ptr, src.Ptr(), field_value.Ptr());
+      }
+      return false;
+    };
+    stop_reports_ = FieldVisitor<void, true>::ReportFields(obj,
+                                                           nullptr,
+                                                           VisitorFalse<void>,
+                                                           VisitorFalse<void>,
+                                                           VisitorFalse<void>,
+                                                           report_instance_field);
+    if (stop_reports_) {
+      return;
+    }
+
+    jint string_ret = ReportString(obj, env, tag_table_, callbacks_, user_data_);
+    stop_reports_ = (string_ret & JVMTI_VISIT_ABORT) != 0;
+    if (stop_reports_) {
+      return;
+    }
+
+    stop_reports_ = ReportPrimitiveField::Report(obj, tag_table_, callbacks_, user_data_);
+  }
+
+  void VisitArray(art::mirror::Object* array)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_CLASS,
+                                                 nullptr,
+                                                 array,
+                                                 array->GetClass());
+    if (stop_reports_) {
+      return;
+    }
+
+    if (array->IsObjectArray()) {
+      art::mirror::ObjectArray<art::mirror::Object>* obj_array =
+          array->AsObjectArray<art::mirror::Object>();
+      int32_t length = obj_array->GetLength();
+      for (int32_t i = 0; i != length; ++i) {
+        art::mirror::Object* elem = obj_array->GetWithoutChecks(i);
+        if (elem != nullptr) {
+          jvmtiHeapReferenceInfo reference_info;
+          reference_info.array.index = i;
+          stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT,
+                                                       &reference_info,
+                                                       array,
+                                                       elem);
+          if (stop_reports_) {
+            break;
+          }
+        }
+      }
+    } else {
+      if (!stop_reports_) {
+        jint array_ret = ReportPrimitiveArray(array, env, tag_table_, callbacks_, user_data_);
+        stop_reports_ = (array_ret & JVMTI_VISIT_ABORT) != 0;
+      }
+    }
+  }
+
+  void VisitClass(art::mirror::Class* klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    // TODO: Are erroneous classes reported? Are non-prepared ones? For now, just use resolved ones.
+    if (!klass->IsResolved()) {
+      return;
+    }
+
+    // Superclass.
+    stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_SUPERCLASS,
+                                                 nullptr,
+                                                 klass,
+                                                 klass->GetSuperClass());
+    if (stop_reports_) {
+      return;
+    }
+
+    // Directly implemented or extended interfaces.
+    art::Thread* self = art::Thread::Current();
+    art::StackHandleScope<1> hs(self);
+    art::Handle<art::mirror::Class> h_klass(hs.NewHandle<art::mirror::Class>(klass));
+    for (size_t i = 0; i < h_klass->NumDirectInterfaces(); ++i) {
+      art::ObjPtr<art::mirror::Class> inf_klass =
+          art::mirror::Class::ResolveDirectInterface(self, h_klass, i);
+      if (inf_klass == nullptr) {
+        // TODO: With a resolved class this should not happen...
+        self->ClearException();
+        break;
+      }
+
+      stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_INTERFACE,
+                                                   nullptr,
+                                                   klass,
+                                                   inf_klass.Ptr());
+      if (stop_reports_) {
+        return;
+      }
+    }
+
+    // Classloader.
+    // TODO: What about the boot classpath loader? We'll skip for now, but do we have to find the
+    //       fake BootClassLoader?
+    if (klass->GetClassLoader() != nullptr) {
+      stop_reports_ = !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_CLASS_LOADER,
+                                                   nullptr,
+                                                   klass,
+                                                   klass->GetClassLoader());
+      if (stop_reports_) {
+        return;
+      }
+    }
+    DCHECK_EQ(h_klass.Get(), klass);
+
+    // Declared static fields.
+    auto report_static_field = [&](art::ObjPtr<art::mirror::Object> obj ATTRIBUTE_UNUSED,
+                                   art::ObjPtr<art::mirror::Class> obj_klass,
+                                   art::ArtField& field,
+                                   size_t field_index,
+                                   void* user_data ATTRIBUTE_UNUSED)
+        REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+      art::ObjPtr<art::mirror::Object> field_value = field.GetObject(obj_klass);
+      if (field_value != nullptr) {
+        jvmtiHeapReferenceInfo reference_info;
+        memset(&reference_info, 0, sizeof(reference_info));
+
+        reference_info.field.index = static_cast<jint>(field_index);
+
+        return !ReportReferenceMaybeEnqueue(JVMTI_HEAP_REFERENCE_STATIC_FIELD,
+                                            &reference_info,
+                                            obj_klass.Ptr(),
+                                            field_value.Ptr());
+      }
+      return false;
+    };
+    stop_reports_ = FieldVisitor<void, false>::ReportFields(klass,
+                                                            nullptr,
+                                                            VisitorFalse<void>,
+                                                            report_static_field,
+                                                            VisitorFalse<void>,
+                                                            VisitorFalse<void>);
+    if (stop_reports_) {
+      return;
+    }
+
+    stop_reports_ = ReportPrimitiveField::Report(klass, tag_table_, callbacks_, user_data_);
+  }
+
+  void MaybeEnqueue(art::mirror::Object* obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (visited_.find(obj) == visited_.end()) {
+      worklist_.push_back(obj);
+      visited_.insert(obj);
+    }
+  }
+
+  bool ReportReferenceMaybeEnqueue(jvmtiHeapReferenceKind kind,
+                                   const jvmtiHeapReferenceInfo* reference_info,
+                                   art::mirror::Object* referree,
+                                   art::mirror::Object* referrer)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    jint result = ReportReference(kind, reference_info, referree, referrer);
+    if ((result & JVMTI_VISIT_ABORT) == 0) {
+      if ((result & JVMTI_VISIT_OBJECTS) != 0) {
+        MaybeEnqueue(referrer);
+      }
+      return true;
+    } else {
+      return false;
+    }
+  }
+
+  jint ReportReference(jvmtiHeapReferenceKind kind,
+                       const jvmtiHeapReferenceInfo* reference_info,
+                       art::mirror::Object* referrer,
+                       art::mirror::Object* referree)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!*tag_table_->GetAllowDisallowLock()) {
+    if (referree == nullptr || stop_reports_) {
+      return 0;
+    }
+
+    if (UNLIKELY(class_filter_ != nullptr) && class_filter_ != referree->GetClass()) {
+      return JVMTI_VISIT_OBJECTS;
+    }
+
+    const jlong class_tag = tag_table_->GetTagOrZero(referree->GetClass());
+    jlong tag = tag_table_->GetTagOrZero(referree);
+
+    if (!heap_filter_.ShouldReportByHeapFilter(tag, class_tag)) {
+      return JVMTI_VISIT_OBJECTS;
+    }
+
+    const jlong referrer_class_tag =
+        referrer == nullptr ? 0 : tag_table_->GetTagOrZero(referrer->GetClass());
+    const jlong size = static_cast<jlong>(referree->SizeOf());
+    jlong saved_tag = tag;
+    jlong referrer_tag = 0;
+    jlong saved_referrer_tag = 0;
+    jlong* referrer_tag_ptr;
+    if (referrer == nullptr) {
+      referrer_tag_ptr = nullptr;
+    } else {
+      if (referrer == referree) {
+        referrer_tag_ptr = &tag;
+      } else {
+        referrer_tag = saved_referrer_tag = tag_table_->GetTagOrZero(referrer);
+        referrer_tag_ptr = &referrer_tag;
+      }
+    }
+
+    jint length = -1;
+    if (referree->IsArrayInstance()) {
+      length = referree->AsArray()->GetLength();
+    }
+
+    jint result = callbacks_->heap_reference_callback(kind,
+                                                      reference_info,
+                                                      class_tag,
+                                                      referrer_class_tag,
+                                                      size,
+                                                      &tag,
+                                                      referrer_tag_ptr,
+                                                      length,
+                                                      const_cast<void*>(user_data_));
+
+    if (tag != saved_tag) {
+      tag_table_->Set(referree, tag);
+    }
+    if (referrer_tag != saved_referrer_tag) {
+      tag_table_->Set(referrer, referrer_tag);
+    }
+
+    return result;
+  }
+
+  jvmtiEnv* env;
+  ObjectTagTable* tag_table_;
+  art::ObjPtr<art::mirror::Object> initial_object_;
+  const jvmtiHeapCallbacks* callbacks_;
+  art::ObjPtr<art::mirror::Class> class_filter_;
+  const HeapFilter heap_filter_;
+  const void* user_data_;
+
+  std::vector<art::mirror::Object*> worklist_;
+  size_t start_;
+  static constexpr size_t kMaxStart = 1000000U;
+
+  std::unordered_set<art::mirror::Object*> visited_;
+
+  bool stop_reports_;
+
+  friend class CollectAndReportRootsVisitor;
+};
+
+jvmtiError HeapUtil::FollowReferences(jvmtiEnv* env,
+                                      jint heap_filter,
+                                      jclass klass,
+                                      jobject initial_object,
+                                      const jvmtiHeapCallbacks* callbacks,
+                                      const void* user_data) {
+  if (callbacks == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* self = art::Thread::Current();
+
+  art::gc::Heap* heap = art::Runtime::Current()->GetHeap();
+  if (heap->IsGcConcurrentAndMoving()) {
+    // Need to take a heap dump while GC isn't running. See the
+    // comment in Heap::VisitObjects().
+    heap->IncrementDisableMovingGC(self);
+  }
+  {
+    art::ScopedObjectAccess soa(self);      // Now we know we have the shared lock.
+    art::ScopedThreadSuspension sts(self, art::kWaitingForVisitObjects);
+    art::ScopedSuspendAll ssa("FollowReferences");
+
+    art::ObjPtr<art::mirror::Class> class_filter = klass == nullptr
+        ? nullptr
+        : art::ObjPtr<art::mirror::Class>::DownCast(self->DecodeJObject(klass));
+    FollowReferencesHelper frh(this,
+                               env,
+                               self->DecodeJObject(initial_object),
+                               callbacks,
+                               class_filter,
+                               heap_filter,
+                               user_data);
+    frh.Init();
+    frh.Work();
+  }
+  if (heap->IsGcConcurrentAndMoving()) {
+    heap->DecrementDisableMovingGC(self);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError HeapUtil::GetLoadedClasses(jvmtiEnv* env,
+                                      jint* class_count_ptr,
+                                      jclass** classes_ptr) {
+  if (class_count_ptr == nullptr || classes_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  class ReportClassVisitor : public art::ClassVisitor {
+   public:
+    explicit ReportClassVisitor(art::Thread* self) : self_(self) {}
+
+    bool operator()(art::ObjPtr<art::mirror::Class> klass)
+        OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+      if (klass->IsLoaded() || klass->IsErroneous()) {
+        classes_.push_back(self_->GetJniEnv()->AddLocalReference<jclass>(klass));
+      }
+      return true;
+    }
+
+    art::Thread* self_;
+    std::vector<jclass> classes_;
+  };
+
+  art::Thread* self = art::Thread::Current();
+  ReportClassVisitor rcv(self);
+  {
+    art::ScopedObjectAccess soa(self);
+    art::Runtime::Current()->GetClassLinker()->VisitClasses(&rcv);
+  }
+
+  size_t size = rcv.classes_.size();
+  jclass* classes = nullptr;
+  jvmtiError alloc_ret = env->Allocate(static_cast<jlong>(size * sizeof(jclass)),
+                                       reinterpret_cast<unsigned char**>(&classes));
+  if (alloc_ret != ERR(NONE)) {
+    return alloc_ret;
+  }
+
+  for (size_t i = 0; i < size; ++i) {
+    classes[i] = rcv.classes_[i];
+  }
+  *classes_ptr = classes;
+  *class_count_ptr = static_cast<jint>(size);
+
+  return ERR(NONE);
+}
+
+jvmtiError HeapUtil::ForceGarbageCollection(jvmtiEnv* env ATTRIBUTE_UNUSED) {
+  art::Runtime::Current()->GetHeap()->CollectGarbage(false);
+
+  return ERR(NONE);
+}
+
+static constexpr jint kHeapIdDefault = 0;
+static constexpr jint kHeapIdImage = 1;
+static constexpr jint kHeapIdZygote = 2;
+static constexpr jint kHeapIdApp = 3;
+
+static jint GetHeapId(art::ObjPtr<art::mirror::Object> obj)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (obj == nullptr) {
+    return -1;
+  }
+
+  art::gc::Heap* const heap = art::Runtime::Current()->GetHeap();
+  const art::gc::space::ContinuousSpace* const space =
+      heap->FindContinuousSpaceFromObject(obj, true);
+  jint heap_type = kHeapIdApp;
+  if (space != nullptr) {
+    if (space->IsZygoteSpace()) {
+      heap_type = kHeapIdZygote;
+    } else if (space->IsImageSpace() && heap->ObjectIsInBootImageSpace(obj)) {
+      // Only count objects in the boot image as HPROF_HEAP_IMAGE, this leaves app image objects
+      // as HPROF_HEAP_APP. b/35762934
+      heap_type = kHeapIdImage;
+    }
+  } else {
+    const auto* los = heap->GetLargeObjectsSpace();
+    if (los->Contains(obj.Ptr()) && los->IsZygoteLargeObject(art::Thread::Current(), obj.Ptr())) {
+      heap_type = kHeapIdZygote;
+    }
+  }
+  return heap_type;
+};
+
+jvmtiError HeapExtensions::GetObjectHeapId(jvmtiEnv* env, jlong tag, jint* heap_id, ...) {
+  if (heap_id == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* self = art::Thread::Current();
+
+  auto work = [&]() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    ObjectTagTable* tag_table = ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get();
+    art::ObjPtr<art::mirror::Object> obj = tag_table->Find(tag);
+    jint heap_type = GetHeapId(obj);
+    if (heap_type == -1) {
+      return ERR(NOT_FOUND);
+    }
+    *heap_id = heap_type;
+    return ERR(NONE);
+  };
+
+  if (!art::Locks::mutator_lock_->IsSharedHeld(self)) {
+    if (!self->IsThreadSuspensionAllowable()) {
+      return ERR(INTERNAL);
+    }
+    art::ScopedObjectAccess soa(self);
+    return work();
+  } else {
+    // We cannot use SOA in this case. We might be holding the lock, but may not be in the
+    // runnable state (e.g., during GC).
+    art::Locks::mutator_lock_->AssertSharedHeld(self);
+    // TODO: Investigate why ASSERT_SHARED_CAPABILITY doesn't work.
+    auto annotalysis_workaround = [&]() NO_THREAD_SAFETY_ANALYSIS {
+      return work();
+    };
+    return annotalysis_workaround();
+  }
+}
+
+static jvmtiError CopyStringAndReturn(jvmtiEnv* env, const char* in, char** out) {
+  jvmtiError error;
+  JvmtiUniquePtr<char[]> param_name = CopyString(env, in, &error);
+  if (param_name == nullptr) {
+    return error;
+  }
+  *out = param_name.release();
+  return ERR(NONE);
+}
+
+static constexpr const char* kHeapIdDefaultName = "default";
+static constexpr const char* kHeapIdImageName = "image";
+static constexpr const char* kHeapIdZygoteName = "zygote";
+static constexpr const char* kHeapIdAppName = "app";
+
+jvmtiError HeapExtensions::GetHeapName(jvmtiEnv* env, jint heap_id, char** heap_name, ...) {
+  switch (heap_id) {
+    case kHeapIdDefault:
+      return CopyStringAndReturn(env, kHeapIdDefaultName, heap_name);
+    case kHeapIdImage:
+      return CopyStringAndReturn(env, kHeapIdImageName, heap_name);
+    case kHeapIdZygote:
+      return CopyStringAndReturn(env, kHeapIdZygoteName, heap_name);
+    case kHeapIdApp:
+      return CopyStringAndReturn(env, kHeapIdAppName, heap_name);
+
+    default:
+      return ERR(ILLEGAL_ARGUMENT);
+  }
+}
+
+jvmtiError HeapExtensions::IterateThroughHeapExt(jvmtiEnv* env,
+                                                 jint heap_filter,
+                                                 jclass klass,
+                                                 const jvmtiHeapCallbacks* callbacks,
+                                                 const void* user_data) {
+  if (ArtJvmTiEnv::AsArtJvmTiEnv(env)->capabilities.can_tag_objects != 1) { \
+    return ERR(MUST_POSSESS_CAPABILITY); \
+  }
+
+  // ART extension API: Also pass the heap id.
+  auto ArtIterateHeap = [](art::mirror::Object* obj,
+                           const jvmtiHeapCallbacks* cb_callbacks,
+                           jlong class_tag,
+                           jlong size,
+                           jlong* tag,
+                           jint length,
+                           void* cb_user_data)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    jint heap_id = GetHeapId(obj);
+    using ArtExtensionAPI = jint (*)(jlong, jlong, jlong*, jint length, void*, jint);
+    return reinterpret_cast<ArtExtensionAPI>(cb_callbacks->heap_iteration_callback)(
+        class_tag, size, tag, length, cb_user_data, heap_id);
+  };
+  return DoIterateThroughHeap(ArtIterateHeap,
+                              env,
+                              ArtJvmTiEnv::AsArtJvmTiEnv(env)->object_tag_table.get(),
+                              heap_filter,
+                              klass,
+                              callbacks,
+                              user_data);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_heap.h b/openjdkjvmti/ti_heap.h
new file mode 100644
index 0000000..62761b5
--- /dev/null
+++ b/openjdkjvmti/ti_heap.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+#ifndef ART_OPENJDKJVMTI_TI_HEAP_H_
+#define ART_OPENJDKJVMTI_TI_HEAP_H_
+
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ObjectTagTable;
+
+class HeapUtil {
+ public:
+  explicit HeapUtil(ObjectTagTable* tags) : tags_(tags) {
+  }
+
+  jvmtiError GetLoadedClasses(jvmtiEnv* env, jint* class_count_ptr, jclass** classes_ptr);
+
+  jvmtiError IterateThroughHeap(jvmtiEnv* env,
+                                jint heap_filter,
+                                jclass klass,
+                                const jvmtiHeapCallbacks* callbacks,
+                                const void* user_data);
+
+  jvmtiError FollowReferences(jvmtiEnv* env,
+                              jint heap_filter,
+                              jclass klass,
+                              jobject initial_object,
+                              const jvmtiHeapCallbacks* callbacks,
+                              const void* user_data);
+
+  static jvmtiError ForceGarbageCollection(jvmtiEnv* env);
+
+  ObjectTagTable* GetTags() {
+    return tags_;
+  }
+
+  static void Register();
+  static void Unregister();
+
+ private:
+  ObjectTagTable* tags_;
+};
+
+class HeapExtensions {
+ public:
+  static jvmtiError JNICALL GetObjectHeapId(jvmtiEnv* env, jlong tag, jint* heap_id, ...);
+  static jvmtiError JNICALL GetHeapName(jvmtiEnv* env, jint heap_id, char** heap_name, ...);
+
+  static jvmtiError JNICALL IterateThroughHeapExt(jvmtiEnv* env,
+                                                  jint heap_filter,
+                                                  jclass klass,
+                                                  const jvmtiHeapCallbacks* callbacks,
+                                                  const void* user_data);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_HEAP_H_
diff --git a/openjdkjvmti/ti_jni.cc b/openjdkjvmti/ti_jni.cc
new file mode 100644
index 0000000..dd2dda1
--- /dev/null
+++ b/openjdkjvmti/ti_jni.cc
@@ -0,0 +1,91 @@
+/* Copyright (C) 2017 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 "ti_jni.h"
+
+#include "jni.h"
+
+#include "art_jvmti.h"
+#include "base/mutex.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext.h"
+#include "runtime.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError JNIUtil::SetJNIFunctionTable(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        const jniNativeInterface* function_table) {
+  // While we supporting setting null (which will reset the table), the spec says no.
+  if (function_table == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::JNIEnvExt::SetTableOverride(function_table);
+  return ERR(NONE);
+}
+
+jvmtiError JNIUtil::GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table) {
+  if (function_table == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  // We use the generic JNIEnvExt::GetFunctionTable instead of querying a specific JNIEnv, as
+  // this has to work in the start phase.
+
+  // Figure out which table is current. Conservatively assume check-jni is off.
+  bool check_jni = false;
+  art::Runtime* runtime = art::Runtime::Current();
+  if (runtime != nullptr && runtime->GetJavaVM() != nullptr) {
+    check_jni = runtime->GetJavaVM()->IsCheckJniEnabled();
+  }
+
+  // Get that table.
+  const JNINativeInterface* current_table;
+  {
+    art::MutexLock mu(art::Thread::Current(), *art::Locks::jni_function_table_lock_);
+    current_table = art::JNIEnvExt::GetFunctionTable(check_jni);
+  }
+
+  // Allocate memory and copy the table.
+  unsigned char* data;
+  jvmtiError data_result = env->Allocate(sizeof(JNINativeInterface), &data);
+  if (data_result != ERR(NONE)) {
+    return data_result;
+  }
+  memcpy(data, current_table, sizeof(JNINativeInterface));
+
+  *function_table = reinterpret_cast<JNINativeInterface*>(data);
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_jni.h b/openjdkjvmti/ti_jni.h
new file mode 100644
index 0000000..590fd54
--- /dev/null
+++ b/openjdkjvmti/ti_jni.h
@@ -0,0 +1,58 @@
+/* Copyright (C) 2017 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_TI_JNI_H_
+#define ART_OPENJDKJVMTI_TI_JNI_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+// Note: Currently, JNI function table changes are sensitive to the order of operations wrt/
+//       CheckJNI. If an agent sets the function table, and a program than late-enables CheckJNI,
+//       CheckJNI will not be working (as the agent will forward to the non-CheckJNI table).
+//
+//       This behavior results from our usage of the function table to avoid a check of the
+//       CheckJNI flag. A future implementation may install on loading of this plugin an
+//       intermediate function table that explicitly checks the flag, so that switching CheckJNI
+//       is transparently handled.
+
+class JNIUtil {
+ public:
+  static jvmtiError SetJNIFunctionTable(jvmtiEnv* env, const jniNativeInterface* function_table);
+
+  static jvmtiError GetJNIFunctionTable(jvmtiEnv* env, jniNativeInterface** function_table);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_JNI_H_
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
new file mode 100644
index 0000000..8f72714
--- /dev/null
+++ b/openjdkjvmti/ti_method.cc
@@ -0,0 +1,1071 @@
+/* Copyright (C) 2016 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 "ti_method.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "base/mutex-inl.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
+#include "mirror/object-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_thread.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+struct TiMethodCallback : public art::MethodCallback {
+  void RegisterNativeMethod(art::ArtMethod* method,
+                            const void* cur_method,
+                            /*out*/void** new_method)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (event_handler->IsEventEnabledAnywhere(ArtJvmtiEvent::kNativeMethodBind)) {
+      art::Thread* thread = art::Thread::Current();
+      art::JNIEnvExt* jnienv = thread->GetJniEnv();
+      ScopedLocalRef<jthread> thread_jni(
+          jnienv, PhaseUtil::IsLivePhase() ? jnienv->AddLocalReference<jthread>(thread->GetPeer())
+                                           : nullptr);
+      art::ScopedThreadSuspension sts(thread, art::ThreadState::kNative);
+      event_handler->DispatchEvent<ArtJvmtiEvent::kNativeMethodBind>(
+          thread,
+          static_cast<JNIEnv*>(jnienv),
+          thread_jni.get(),
+          art::jni::EncodeArtMethod(method),
+          const_cast<void*>(cur_method),
+          new_method);
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+TiMethodCallback gMethodCallback;
+
+void MethodUtil::Register(EventHandler* handler) {
+  gMethodCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add method callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddMethodCallback(&gMethodCallback);
+}
+
+void MethodUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove method callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveMethodCallback(&gMethodCallback);
+}
+
+jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
+                                    jmethodID method,
+                                    jint* size_ptr,
+                                    unsigned char** bytecode_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (size_ptr == nullptr || bytecode_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+  if (code_item == nullptr) {
+    *size_ptr = 0;
+    *bytecode_ptr = nullptr;
+    return OK;
+  }
+  // 2 bytes per instruction for dex code.
+  *size_ptr = code_item->insns_size_in_code_units_ * 2;
+  jvmtiError err = env->Allocate(*size_ptr, bytecode_ptr);
+  if (err != OK) {
+    return err;
+  }
+  memcpy(*bytecode_ptr, code_item->insns_, *size_ptr);
+  return OK;
+}
+
+jvmtiError MethodUtil::GetArgumentsSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jmethodID method,
+                                        jint* size_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (size_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+    // Use the shorty.
+    art::ArtMethod* base_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+    size_t arg_count = art::ArtMethod::NumArgRegisters(base_method->GetShorty());
+    if (!base_method->IsStatic()) {
+      arg_count++;
+    }
+    *size_ptr = static_cast<jint>(arg_count);
+    return ERR(NONE);
+  }
+
+  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+  *size_ptr = art_method->GetCodeItem()->ins_size_;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetLocalVariableTable(jvmtiEnv* env,
+                                             jmethodID method,
+                                             jint* entry_count_ptr,
+                                             jvmtiLocalVariableEntry** table_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  const art::DexFile* dex_file = art_method->GetDexFile();
+  const art::DexFile::CodeItem* code_item = art_method->GetCodeItem();
+  // TODO code_item == nullptr means that the method is abstract (or native, but we check that
+  // earlier). We should check what is returned by the RI in this situation since it's not clear
+  // what the appropriate return value is from the spec.
+  if (dex_file == nullptr || code_item == nullptr) {
+    return ERR(ABSENT_INFORMATION);
+  }
+
+  struct LocalVariableContext {
+    explicit LocalVariableContext(jvmtiEnv* jenv) : env_(jenv), variables_(), err_(OK) {}
+
+    static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+      reinterpret_cast<LocalVariableContext*>(raw_ctx)->Insert(entry);
+    }
+
+    void Insert(const art::DexFile::LocalInfo& entry) {
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> name_str = CopyString(env_, entry.name_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> sig_str = CopyString(env_, entry.descriptor_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      JvmtiUniquePtr<char[]> generic_sig_str = CopyString(env_, entry.signature_, &err_);
+      if (err_ != OK) {
+        return;
+      }
+      variables_.push_back({
+        .start_location = static_cast<jlocation>(entry.start_address_),
+        .length = static_cast<jint>(entry.end_address_ - entry.start_address_),
+        .name = name_str.release(),
+        .signature = sig_str.release(),
+        .generic_signature = generic_sig_str.release(),
+        .slot = entry.reg_,
+      });
+    }
+
+    jvmtiError Release(jint* out_entry_count_ptr, jvmtiLocalVariableEntry** out_table_ptr) {
+      jlong table_size = sizeof(jvmtiLocalVariableEntry) * variables_.size();
+      if (err_ != OK ||
+          (err_ = env_->Allocate(table_size,
+                                 reinterpret_cast<unsigned char**>(out_table_ptr))) != OK) {
+        Cleanup();
+        return err_;
+      } else {
+        *out_entry_count_ptr = variables_.size();
+        memcpy(*out_table_ptr, variables_.data(), table_size);
+        return OK;
+      }
+    }
+
+    void Cleanup() {
+      for (jvmtiLocalVariableEntry& e : variables_) {
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.name));
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.signature));
+        env_->Deallocate(reinterpret_cast<unsigned char*>(e.generic_signature));
+      }
+    }
+
+    jvmtiEnv* env_;
+    std::vector<jvmtiLocalVariableEntry> variables_;
+    jvmtiError err_;
+  };
+
+  LocalVariableContext context(env);
+  if (!dex_file->DecodeDebugLocalInfo(code_item,
+                                      art_method->IsStatic(),
+                                      art_method->GetDexMethodIndex(),
+                                      LocalVariableContext::Callback,
+                                      &context)) {
+    // Something went wrong with decoding the debug information. It might as well not be there.
+    return ERR(ABSENT_INFORMATION);
+  } else {
+    return context.Release(entry_count_ptr, table_ptr);
+  }
+}
+
+jvmtiError MethodUtil::GetMaxLocals(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                    jmethodID method,
+                                    jint* max_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (max_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+    // This isn't specified as an error case, so return 0.
+    *max_ptr = 0;
+    return ERR(NONE);
+  }
+
+  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+  *max_ptr = art_method->GetCodeItem()->registers_size_;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodName(jvmtiEnv* env,
+                                     jmethodID method,
+                                     char** name_ptr,
+                                     char** signature_ptr,
+                                     char** generic_ptr) {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  art_method = art_method->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+
+  JvmtiUniquePtr<char[]> name_copy;
+  if (name_ptr != nullptr) {
+    const char* method_name = art_method->GetName();
+    if (method_name == nullptr) {
+      method_name = "<error>";
+    }
+    jvmtiError ret;
+    name_copy = CopyString(env, method_name, &ret);
+    if (name_copy == nullptr) {
+      return ret;
+    }
+    *name_ptr = name_copy.get();
+  }
+
+  JvmtiUniquePtr<char[]> signature_copy;
+  if (signature_ptr != nullptr) {
+    const art::Signature sig = art_method->GetSignature();
+    std::string str = sig.ToString();
+    jvmtiError ret;
+    signature_copy = CopyString(env, str.c_str(), &ret);
+    if (signature_copy == nullptr) {
+      return ret;
+    }
+    *signature_ptr = signature_copy.get();
+  }
+
+  if (generic_ptr != nullptr) {
+    *generic_ptr = nullptr;
+    if (!art_method->GetDeclaringClass()->IsProxyClass()) {
+      art::mirror::ObjectArray<art::mirror::String>* str_array =
+          art::annotations::GetSignatureAnnotationForMethod(art_method);
+      if (str_array != nullptr) {
+        std::ostringstream oss;
+        for (int32_t i = 0; i != str_array->GetLength(); ++i) {
+          oss << str_array->Get(i)->ToModifiedUtf8();
+        }
+        std::string output_string = oss.str();
+        jvmtiError ret;
+        JvmtiUniquePtr<char[]> generic_copy = CopyString(env, output_string.c_str(), &ret);
+        if (generic_copy == nullptr) {
+          return ret;
+        }
+        *generic_ptr = generic_copy.release();
+      } else if (soa.Self()->IsExceptionPending()) {
+        // TODO: Should we report an error here?
+        soa.Self()->ClearException();
+      }
+    }
+  }
+
+  // Everything is fine, release the buffers.
+  name_copy.release();
+  signature_copy.release();
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodDeclaringClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jmethodID method,
+                                               jclass* declaring_class_ptr) {
+  if (declaring_class_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  // Note: No GetInterfaceMethodIfProxy, we want to actual class.
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::mirror::Class* klass = art_method->GetDeclaringClass();
+  *declaring_class_ptr = soa.AddLocalReference<jclass>(klass);
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                         jmethodID method,
+                                         jlocation* start_location_ptr,
+                                         jlocation* end_location_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+
+  if (art_method->IsNative()) {
+    return ERR(NATIVE_METHOD);
+  }
+
+  if (start_location_ptr == nullptr || end_location_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (art_method->IsProxyMethod() || art_method->IsAbstract()) {
+    // This isn't specified as an error case, so return -1/-1 as the RI does.
+    *start_location_ptr = -1;
+    *end_location_ptr = -1;
+    return ERR(NONE);
+  }
+
+  DCHECK_NE(art_method->GetCodeItemOffset(), 0u);
+  *start_location_ptr = 0;
+  *end_location_ptr = art_method->GetCodeItem()->insns_size_in_code_units_ - 1;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::GetMethodModifiers(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                          jmethodID method,
+                                          jint* modifiers_ptr) {
+  if (modifiers_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  uint32_t modifiers = art_method->GetAccessFlags();
+
+  // Note: Keep this code in sync with Executable.fixMethodFlags.
+  if ((modifiers & art::kAccAbstract) != 0) {
+    modifiers &= ~art::kAccNative;
+  }
+  modifiers &= ~art::kAccSynchronized;
+  if ((modifiers & art::kAccDeclaredSynchronized) != 0) {
+    modifiers |= art::kAccSynchronized;
+  }
+  modifiers &= art::kAccJavaFlagsMask;
+
+  *modifiers_ptr = modifiers;
+  return ERR(NONE);
+}
+
+using LineNumberContext = std::vector<jvmtiLineNumberEntry>;
+
+static bool CollectLineNumbers(void* void_context, const art::DexFile::PositionInfo& entry) {
+  LineNumberContext* context = reinterpret_cast<LineNumberContext*>(void_context);
+  jvmtiLineNumberEntry jvmti_entry = { static_cast<jlocation>(entry.address_),
+                                       static_cast<jint>(entry.line_) };
+  context->push_back(jvmti_entry);
+  return false;  // Collect all, no early exit.
+}
+
+jvmtiError MethodUtil::GetLineNumberTable(jvmtiEnv* env,
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLineNumberEntry** table_ptr) {
+  if (method == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  DCHECK(!art_method->IsRuntimeMethod());
+
+  const art::DexFile::CodeItem* code_item;
+  const art::DexFile* dex_file;
+  {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+
+    if (art_method->IsProxyMethod()) {
+      return ERR(ABSENT_INFORMATION);
+    }
+    if (art_method->IsNative()) {
+      return ERR(NATIVE_METHOD);
+    }
+    if (entry_count_ptr == nullptr || table_ptr == nullptr) {
+      return ERR(NULL_POINTER);
+    }
+
+    code_item = art_method->GetCodeItem();
+    dex_file = art_method->GetDexFile();
+    DCHECK(code_item != nullptr) << art_method->PrettyMethod() << " " << dex_file->GetLocation();
+  }
+
+  LineNumberContext context;
+  bool success = dex_file->DecodeDebugPositionInfo(code_item, CollectLineNumbers, &context);
+  if (!success) {
+    return ERR(ABSENT_INFORMATION);
+  }
+
+  unsigned char* data;
+  jlong mem_size = context.size() * sizeof(jvmtiLineNumberEntry);
+  jvmtiError alloc_error = env->Allocate(mem_size, &data);
+  if (alloc_error != ERR(NONE)) {
+    return alloc_error;
+  }
+  *table_ptr = reinterpret_cast<jvmtiLineNumberEntry*>(data);
+  memcpy(*table_ptr, context.data(), mem_size);
+  *entry_count_ptr = static_cast<jint>(context.size());
+
+  return ERR(NONE);
+}
+
+template <typename T>
+static jvmtiError IsMethodT(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                            jmethodID method,
+                            T test,
+                            jboolean* is_t_ptr) {
+  if (method == nullptr) {
+    return ERR(INVALID_METHODID);
+  }
+  if (is_t_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ArtMethod* art_method = art::jni::DecodeArtMethod(method);
+  *is_t_ptr = test(art_method) ? JNI_TRUE : JNI_FALSE;
+
+  return ERR(NONE);
+}
+
+jvmtiError MethodUtil::IsMethodNative(jvmtiEnv* env, jmethodID m, jboolean* is_native_ptr) {
+  auto test = [](art::ArtMethod* method) {
+    return method->IsNative();
+  };
+  return IsMethodT(env, m, test, is_native_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodObsolete(jvmtiEnv* env, jmethodID m, jboolean* is_obsolete_ptr) {
+  auto test = [](art::ArtMethod* method) {
+    return method->IsObsolete();
+  };
+  return IsMethodT(env, m, test, is_obsolete_ptr);
+}
+
+jvmtiError MethodUtil::IsMethodSynthetic(jvmtiEnv* env, jmethodID m, jboolean* is_synthetic_ptr) {
+  auto test = [](art::ArtMethod* method) {
+    return method->IsSynthetic();
+  };
+  return IsMethodT(env, m, test, is_synthetic_ptr);
+}
+
+struct FindFrameAtDepthVisitor : art::StackVisitor {
+ public:
+  FindFrameAtDepthVisitor(art::Thread* target, art::Context* ctx, jint depth)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      : art::StackVisitor(target, ctx, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        found_frame_(false),
+        cnt_(0),
+        depth_(static_cast<size_t>(depth)) { }
+
+  bool FoundFrame() {
+    return found_frame_;
+  }
+
+  bool VisitFrame() NO_THREAD_SAFETY_ANALYSIS {
+    if (GetMethod()->IsRuntimeMethod()) {
+      return true;
+    }
+    if (cnt_ == depth_) {
+      // We found our frame, exit.
+      found_frame_ = true;
+      return false;
+    } else {
+      cnt_++;
+      return true;
+    }
+  }
+
+ private:
+  bool found_frame_;
+  size_t cnt_;
+  size_t depth_;
+};
+
+class CommonLocalVariableClosure : public art::Closure {
+ public:
+  CommonLocalVariableClosure(art::Thread* caller,
+                             jint depth,
+                             jint slot)
+      : result_(ERR(INTERNAL)), caller_(caller), depth_(depth), slot_(slot) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      // Must have been a bad depth.
+      result_ = ERR(NO_MORE_FRAMES);
+      return;
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (method->IsNative() || !visitor.IsShadowFrame()) {
+      // TODO We really should support get/set for non-shadow frames.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    } else if (method->GetCodeItem()->registers_size_ <= slot_) {
+      result_ = ERR(INVALID_SLOT);
+      return;
+    }
+    uint32_t pc = visitor.GetDexPc(/*abort_on_failure*/ false);
+    if (pc == art::DexFile::kDexNoIndex) {
+      // Cannot figure out current PC.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    }
+    std::string descriptor;
+    art::Primitive::Type slot_type = art::Primitive::kPrimVoid;
+    jvmtiError err = GetSlotType(method, pc, &descriptor, &slot_type);
+    if (err != OK) {
+      result_ = err;
+      return;
+    }
+
+    err = GetTypeError(method, slot_type, descriptor);
+    if (err != OK) {
+      result_ = err;
+      return;
+    }
+    result_ = Execute(method, visitor);
+  }
+
+  jvmtiError GetResult() const {
+    return result_;
+  }
+
+ protected:
+  virtual jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      REQUIRES(art::Locks::mutator_lock_) = 0;
+  virtual jvmtiError GetTypeError(art::ArtMethod* method,
+                                  art::Primitive::Type type,
+                                  const std::string& descriptor)
+      REQUIRES(art::Locks::mutator_lock_)  = 0;
+
+  jvmtiError GetSlotType(art::ArtMethod* method,
+                         uint32_t dex_pc,
+                         /*out*/std::string* descriptor,
+                         /*out*/art::Primitive::Type* type)
+      REQUIRES(art::Locks::mutator_lock_) {
+    const art::DexFile* dex_file = method->GetDexFile();
+    const art::DexFile::CodeItem* code_item = method->GetCodeItem();
+    if (dex_file == nullptr || code_item == nullptr) {
+      return ERR(OPAQUE_FRAME);
+    }
+
+    struct GetLocalVariableInfoContext {
+      explicit GetLocalVariableInfoContext(jint slot,
+                                          uint32_t pc,
+                                          std::string* out_descriptor,
+                                          art::Primitive::Type* out_type)
+          : found_(false), jslot_(slot), pc_(pc), descriptor_(out_descriptor), type_(out_type) {
+        *descriptor_ = "";
+        *type_ = art::Primitive::kPrimVoid;
+      }
+
+      static void Callback(void* raw_ctx, const art::DexFile::LocalInfo& entry) {
+        reinterpret_cast<GetLocalVariableInfoContext*>(raw_ctx)->Handle(entry);
+      }
+
+      void Handle(const art::DexFile::LocalInfo& entry) {
+        if (found_) {
+          return;
+        } else if (entry.start_address_ <= pc_ &&
+                   entry.end_address_ > pc_ &&
+                   entry.reg_ == jslot_) {
+          found_ = true;
+          *type_ = art::Primitive::GetType(entry.descriptor_[0]);
+          *descriptor_ = entry.descriptor_;
+        }
+        return;
+      }
+
+      bool found_;
+      jint jslot_;
+      uint32_t pc_;
+      std::string* descriptor_;
+      art::Primitive::Type* type_;
+    };
+
+    GetLocalVariableInfoContext context(slot_, dex_pc, descriptor, type);
+    if (!dex_file->DecodeDebugLocalInfo(code_item,
+                                        method->IsStatic(),
+                                        method->GetDexMethodIndex(),
+                                        GetLocalVariableInfoContext::Callback,
+                                        &context) || !context.found_) {
+      // Something went wrong with decoding the debug information. It might as well not be there.
+      return ERR(INVALID_SLOT);
+    } else {
+      return OK;
+    }
+  }
+
+  jvmtiError result_;
+  art::Thread* caller_;
+  jint depth_;
+  jint slot_;
+};
+
+class GetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+  GetLocalVariableClosure(art::Thread* caller,
+                          jint depth,
+                          jint slot,
+                          art::Primitive::Type type,
+                          jvalue* val)
+      : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+  jvmtiError GetTypeError(art::ArtMethod* method ATTRIBUTE_UNUSED,
+                          art::Primitive::Type slot_type,
+                          const std::string& descriptor ATTRIBUTE_UNUSED)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (slot_type) {
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimBoolean:
+        return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimFloat:
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimNot:
+        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected primitive type " << slot_type;
+        UNREACHABLE();
+    }
+  }
+
+  jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (type_) {
+      case art::Primitive::kPrimNot: {
+        uint32_t ptr_val;
+        if (!visitor.GetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             art::kReferenceVReg,
+                             &ptr_val)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        art::ObjPtr<art::mirror::Object> obj(reinterpret_cast<art::mirror::Object*>(ptr_val));
+        val_->l = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+        break;
+      }
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimFloat: {
+        if (!visitor.GetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             type_ == art::Primitive::kPrimFloat ? art::kFloatVReg : art::kIntVReg,
+                             reinterpret_cast<uint32_t*>(&val_->i))) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimLong: {
+        auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+        auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+        if (!visitor.GetVRegPair(method,
+                                 static_cast<uint16_t>(slot_),
+                                 lo_type,
+                                 high_type,
+                                 reinterpret_cast<uint64_t*>(&val_->j))) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      default: {
+        LOG(FATAL) << "unexpected register type " << type_;
+        UNREACHABLE();
+      }
+    }
+    return OK;
+  }
+
+ private:
+  art::Primitive::Type type_;
+  jvalue* val_;
+};
+
+jvmtiError MethodUtil::GetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jthread thread,
+                                               jint depth,
+                                               jint slot,
+                                               art::Primitive::Type type,
+                                               jvalue* val) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  GetLocalVariableClosure c(self, depth, slot, type, val);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+class SetLocalVariableClosure : public CommonLocalVariableClosure {
+ public:
+  SetLocalVariableClosure(art::Thread* caller,
+                          jint depth,
+                          jint slot,
+                          art::Primitive::Type type,
+                          jvalue val)
+      : CommonLocalVariableClosure(caller, depth, slot), type_(type), val_(val) {}
+
+ protected:
+  jvmtiError GetTypeError(art::ArtMethod* method,
+                          art::Primitive::Type slot_type,
+                          const std::string& descriptor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (slot_type) {
+      case art::Primitive::kPrimNot: {
+        if (type_ != art::Primitive::kPrimNot) {
+          return ERR(TYPE_MISMATCH);
+        } else if (val_.l == nullptr) {
+          return OK;
+        } else {
+          art::ClassLinker* cl = art::Runtime::Current()->GetClassLinker();
+          art::ObjPtr<art::mirror::Class> set_class =
+              caller_->DecodeJObject(val_.l)->GetClass();
+          art::ObjPtr<art::mirror::ClassLoader> loader =
+              method->GetDeclaringClass()->GetClassLoader();
+          art::ObjPtr<art::mirror::Class> slot_class =
+              cl->LookupClass(caller_, descriptor.c_str(), loader);
+          DCHECK(!slot_class.IsNull());
+          return slot_class->IsAssignableFrom(set_class) ? OK : ERR(TYPE_MISMATCH);
+        }
+      }
+      case art::Primitive::kPrimByte:
+      case art::Primitive::kPrimChar:
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimShort:
+      case art::Primitive::kPrimBoolean:
+        return type_ == art::Primitive::kPrimInt ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimLong:
+      case art::Primitive::kPrimFloat:
+      case art::Primitive::kPrimDouble:
+        return type_ == slot_type ? OK : ERR(TYPE_MISMATCH);
+      case art::Primitive::kPrimVoid:
+        LOG(FATAL) << "Unexpected primitive type " << slot_type;
+        UNREACHABLE();
+    }
+  }
+
+  jvmtiError Execute(art::ArtMethod* method, art::StackVisitor& visitor)
+      OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    switch (type_) {
+      case art::Primitive::kPrimNot: {
+        uint32_t ptr_val;
+        art::ObjPtr<art::mirror::Object> obj(caller_->DecodeJObject(val_.l));
+        ptr_val = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(obj.Ptr()));
+        if (!visitor.SetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             ptr_val,
+                             art::kReferenceVReg)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimInt:
+      case art::Primitive::kPrimFloat: {
+        if (!visitor.SetVReg(method,
+                             static_cast<uint16_t>(slot_),
+                             static_cast<uint32_t>(val_.i),
+                             type_ == art::Primitive::kPrimFloat ? art::kFloatVReg
+                                                                 : art::kIntVReg)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      case art::Primitive::kPrimDouble:
+      case art::Primitive::kPrimLong: {
+        auto lo_type = type_ == art::Primitive::kPrimLong ? art::kLongLoVReg : art::kDoubleLoVReg;
+        auto high_type = type_ == art::Primitive::kPrimLong ? art::kLongHiVReg : art::kDoubleHiVReg;
+        if (!visitor.SetVRegPair(method,
+                                 static_cast<uint16_t>(slot_),
+                                 static_cast<uint64_t>(val_.j),
+                                 lo_type,
+                                 high_type)) {
+          return ERR(OPAQUE_FRAME);
+        }
+        break;
+      }
+      default: {
+        LOG(FATAL) << "unexpected register type " << type_;
+        UNREACHABLE();
+      }
+    }
+    return OK;
+  }
+
+ private:
+  art::Primitive::Type type_;
+  jvalue val_;
+};
+
+jvmtiError MethodUtil::SetLocalVariableGeneric(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                               jthread thread,
+                                               jint depth,
+                                               jint slot,
+                                               art::Primitive::Type type,
+                                               jvalue val) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  SetLocalVariableClosure c(self, depth, slot, type, val);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+class GetLocalInstanceClosure : public art::Closure {
+ public:
+  GetLocalInstanceClosure(art::Thread* caller, jint depth, jobject* val)
+      : result_(ERR(INTERNAL)),
+        caller_(caller),
+        depth_(depth),
+        val_(val) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::Locks::mutator_lock_->AssertSharedHeld(art::Thread::Current());
+    std::unique_ptr<art::Context> context(art::Context::Create());
+    FindFrameAtDepthVisitor visitor(self, context.get(), depth_);
+    visitor.WalkStack();
+    if (!visitor.FoundFrame()) {
+      // Must have been a bad depth.
+      result_ = ERR(NO_MORE_FRAMES);
+      return;
+    }
+    art::ArtMethod* method = visitor.GetMethod();
+    if (!visitor.IsShadowFrame() && !method->IsNative() && !method->IsProxyMethod()) {
+      // TODO We really should support get/set for non-shadow frames.
+      result_ = ERR(OPAQUE_FRAME);
+      return;
+    }
+    result_ = OK;
+    art::ObjPtr<art::mirror::Object> obj = visitor.GetThisObject();
+    *val_ = obj.IsNull() ? nullptr : caller_->GetJniEnv()->AddLocalReference<jobject>(obj);
+  }
+
+  jvmtiError GetResult() const {
+    return result_;
+  }
+
+ private:
+  jvmtiError result_;
+  art::Thread* caller_;
+  jint depth_;
+  jobject* val_;
+};
+
+jvmtiError MethodUtil::GetLocalInstance(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jthread thread,
+                                        jint depth,
+                                        jobject* data) {
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = ThreadUtil::GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+  GetLocalInstanceClosure c(self, depth, data);
+  if (!target->RequestSynchronousCheckpoint(&c)) {
+    return ERR(THREAD_NOT_ALIVE);
+  } else {
+    return c.GetResult();
+  }
+}
+
+#define FOR_JVMTI_JVALUE_TYPES(fn) \
+    fn(jint, art::Primitive::kPrimInt, i) \
+    fn(jlong, art::Primitive::kPrimLong, j) \
+    fn(jfloat, art::Primitive::kPrimFloat, f) \
+    fn(jdouble, art::Primitive::kPrimDouble, d) \
+    fn(jobject, art::Primitive::kPrimNot, l)
+
+namespace impl {
+
+template<typename T> void WriteJvalue(T, jvalue*);
+template<typename T> void ReadJvalue(jvalue, T*);
+template<typename T> art::Primitive::Type GetJNIType();
+
+#define JNI_TYPE_CHAR(type, prim, id) \
+template<> art::Primitive::Type GetJNIType<type>() { \
+  return prim; \
+}
+
+FOR_JVMTI_JVALUE_TYPES(JNI_TYPE_CHAR);
+
+#undef JNI_TYPE_CHAR
+
+#define RW_JVALUE(type, prim, id) \
+    template<> void ReadJvalue<type>(jvalue in, type* out) { \
+      *out = in.id; \
+    } \
+    template<> void WriteJvalue<type>(type in, jvalue* out) { \
+      out->id = in; \
+    }
+
+FOR_JVMTI_JVALUE_TYPES(RW_JVALUE);
+
+#undef RW_JVALUE
+
+}  // namespace impl
+
+template<typename T>
+jvmtiError MethodUtil::SetLocalVariable(jvmtiEnv* env,
+                                        jthread thread,
+                                        jint depth,
+                                        jint slot,
+                                        T data) {
+  jvalue v = {.j = 0};
+  art::Primitive::Type type = impl::GetJNIType<T>();
+  impl::WriteJvalue(data, &v);
+  return SetLocalVariableGeneric(env, thread, depth, slot, type, v);
+}
+
+template<typename T>
+jvmtiError MethodUtil::GetLocalVariable(jvmtiEnv* env,
+                                        jthread thread,
+                                        jint depth,
+                                        jint slot,
+                                        T* data) {
+  if (data == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvalue v = {.j = 0};
+  art::Primitive::Type type = impl::GetJNIType<T>();
+  jvmtiError err = GetLocalVariableGeneric(env, thread, depth, slot, type, &v);
+  if (err != OK) {
+    return err;
+  } else {
+    impl::ReadJvalue(v, data);
+    return OK;
+  }
+}
+
+#define GET_SET_LV(type, prim, id) \
+    template jvmtiError MethodUtil::GetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type*); \
+    template jvmtiError MethodUtil::SetLocalVariable<type>(jvmtiEnv*, jthread, jint, jint, type);
+
+FOR_JVMTI_JVALUE_TYPES(GET_SET_LV);
+
+#undef GET_SET_LV
+
+#undef FOR_JVMTI_JVALUE_TYPES
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_method.h b/openjdkjvmti/ti_method.h
new file mode 100644
index 0000000..e3578a4
--- /dev/null
+++ b/openjdkjvmti/ti_method.h
@@ -0,0 +1,114 @@
+/* Copyright (C) 2016 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_TI_METHOD_H_
+#define ART_OPENJDKJVMTI_TI_METHOD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+#include "primitive.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class MethodUtil {
+ public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
+  static jvmtiError GetBytecodes(jvmtiEnv* env,
+                                 jmethodID method,
+                                 jint* count_ptr,
+                                 unsigned char** bytecodes);
+
+  static jvmtiError GetArgumentsSize(jvmtiEnv* env, jmethodID method, jint* size_ptr);
+
+  static jvmtiError GetMaxLocals(jvmtiEnv* env, jmethodID method, jint* max_ptr);
+
+  static jvmtiError GetMethodName(jvmtiEnv* env,
+                                  jmethodID method,
+                                  char** name_ptr,
+                                  char** signature_ptr,
+                                  char** generic_ptr);
+
+  static jvmtiError GetMethodDeclaringClass(jvmtiEnv* env,
+                                            jmethodID method,
+                                            jclass* declaring_class_ptr);
+
+  static jvmtiError GetMethodLocation(jvmtiEnv* env,
+                                      jmethodID method,
+                                      jlocation* start_location_ptr,
+                                      jlocation* end_location_ptr);
+
+  static jvmtiError GetMethodModifiers(jvmtiEnv* env,
+                                       jmethodID method,
+                                       jint* modifiers_ptr);
+
+  static jvmtiError GetLineNumberTable(jvmtiEnv* env,
+                                       jmethodID method,
+                                       jint* entry_count_ptr,
+                                       jvmtiLineNumberEntry** table_ptr);
+
+  static jvmtiError IsMethodNative(jvmtiEnv* env, jmethodID method, jboolean* is_native_ptr);
+  static jvmtiError IsMethodObsolete(jvmtiEnv* env, jmethodID method, jboolean* is_obsolete_ptr);
+  static jvmtiError IsMethodSynthetic(jvmtiEnv* env, jmethodID method, jboolean* is_synthetic_ptr);
+  static jvmtiError GetLocalVariableTable(jvmtiEnv* env,
+                                          jmethodID method,
+                                          jint* entry_count_ptr,
+                                          jvmtiLocalVariableEntry** table_ptr);
+
+  template<typename T>
+  static jvmtiError SetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T data);
+
+  template<typename T>
+  static jvmtiError GetLocalVariable(jvmtiEnv* env, jthread thread, jint depth, jint slot, T* data);
+
+  static jvmtiError GetLocalInstance(jvmtiEnv* env, jthread thread, jint depth, jobject* data);
+
+ private:
+  static jvmtiError SetLocalVariableGeneric(jvmtiEnv* env,
+                                            jthread thread,
+                                            jint depth,
+                                            jint slot,
+                                            art::Primitive::Type type,
+                                            jvalue value);
+  static jvmtiError GetLocalVariableGeneric(jvmtiEnv* env,
+                                            jthread thread,
+                                            jint depth,
+                                            jint slot,
+                                            art::Primitive::Type type,
+                                            jvalue* value);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_METHOD_H_
diff --git a/openjdkjvmti/ti_monitor.cc b/openjdkjvmti/ti_monitor.cc
new file mode 100644
index 0000000..61bf533
--- /dev/null
+++ b/openjdkjvmti/ti_monitor.cc
@@ -0,0 +1,302 @@
+/* Copyright (C) 2017 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 "ti_monitor.h"
+
+#include <atomic>
+#include <chrono>
+#include <condition_variable>
+#include <mutex>
+
+#include "art_jvmti.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+// We cannot use ART monitors, as they require the mutator lock for contention locking. We
+// also cannot use pthread mutexes and condition variables (or C++11 abstractions) directly,
+// as the do not have the right semantics for recursive mutexes and waiting (wait only unlocks
+// the mutex once).
+// So go ahead and use a wrapper that does the counting explicitly.
+
+class JvmtiMonitor {
+ public:
+  JvmtiMonitor() : owner_(nullptr), count_(0) {
+  }
+
+  static bool Destroy(art::Thread* self, JvmtiMonitor* monitor) NO_THREAD_SAFETY_ANALYSIS {
+    // Check whether this thread holds the monitor, or nobody does.
+    art::Thread* owner_thread = monitor->owner_.load(std::memory_order_relaxed);
+    if (owner_thread != nullptr && self != owner_thread) {
+      return false;
+    }
+
+    if (monitor->count_ > 0) {
+      monitor->count_ = 0;
+      monitor->owner_.store(nullptr, std::memory_order_relaxed);
+      monitor->mutex_.unlock();
+    }
+
+    delete monitor;
+    return true;
+  }
+
+  void MonitorEnter(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS {
+    // Check for recursive enter.
+    if (IsOwner(self)) {
+      count_++;
+      return;
+    }
+
+    mutex_.lock();
+
+    DCHECK(owner_.load(std::memory_order_relaxed) == nullptr);
+    owner_.store(self, std::memory_order_relaxed);
+    DCHECK_EQ(0u, count_);
+    count_ = 1;
+  }
+
+  bool MonitorExit(art::Thread* self) NO_THREAD_SAFETY_ANALYSIS {
+    if (!IsOwner(self)) {
+      return false;
+    }
+
+    --count_;
+    if (count_ == 0u) {
+      owner_.store(nullptr, std::memory_order_relaxed);
+      mutex_.unlock();
+    }
+
+    return true;
+  }
+
+  bool Wait(art::Thread* self) {
+    auto wait_without_timeout = [&](std::unique_lock<std::mutex>& lk) {
+      cond_.wait(lk);
+    };
+    return Wait(self, wait_without_timeout);
+  }
+
+  bool Wait(art::Thread* self, uint64_t timeout_in_ms) {
+    auto wait_with_timeout = [&](std::unique_lock<std::mutex>& lk) {
+      cond_.wait_for(lk, std::chrono::milliseconds(timeout_in_ms));
+    };
+    return Wait(self, wait_with_timeout);
+  }
+
+  bool Notify(art::Thread* self) {
+    return Notify(self, [&]() { cond_.notify_one(); });
+  }
+
+  bool NotifyAll(art::Thread* self) {
+    return Notify(self, [&]() { cond_.notify_all(); });
+  }
+
+ private:
+  bool IsOwner(art::Thread* self) {
+    // There's a subtle correctness argument here for a relaxed load outside the critical section.
+    // A thread is guaranteed to see either its own latest store or another thread's store. If a
+    // thread sees another thread's store than it cannot be holding the lock.
+    art::Thread* owner_thread = owner_.load(std::memory_order_relaxed);
+    return self == owner_thread;
+  }
+
+  template <typename T>
+  bool Wait(art::Thread* self, T how_to_wait) {
+    if (!IsOwner(self)) {
+      return false;
+    }
+
+    size_t old_count = count_;
+
+    count_ = 0;
+    owner_.store(nullptr, std::memory_order_relaxed);
+
+    {
+      std::unique_lock<std::mutex> lk(mutex_, std::adopt_lock);
+      how_to_wait(lk);
+      lk.release();  // Do not unlock the mutex.
+    }
+
+    DCHECK(owner_.load(std::memory_order_relaxed) == nullptr);
+    owner_.store(self, std::memory_order_relaxed);
+    DCHECK_EQ(0u, count_);
+    count_ = old_count;
+
+    return true;
+  }
+
+  template <typename T>
+  bool Notify(art::Thread* self, T how_to_notify) {
+    if (!IsOwner(self)) {
+      return false;
+    }
+
+    how_to_notify();
+
+    return true;
+  }
+
+  std::mutex mutex_;
+  std::condition_variable cond_;
+  std::atomic<art::Thread*> owner_;
+  size_t count_;
+};
+
+static jrawMonitorID EncodeMonitor(JvmtiMonitor* monitor) {
+  return reinterpret_cast<jrawMonitorID>(monitor);
+}
+
+static JvmtiMonitor* DecodeMonitor(jrawMonitorID id) {
+  return reinterpret_cast<JvmtiMonitor*>(id);
+}
+
+jvmtiError MonitorUtil::CreateRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                         const char* name,
+                                         jrawMonitorID* monitor_ptr) {
+  if (name == nullptr || monitor_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  JvmtiMonitor* monitor = new JvmtiMonitor();
+  *monitor_ptr = EncodeMonitor(monitor);
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::DestroyRawMonitor(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!JvmtiMonitor::Destroy(self, monitor)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorEnter(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  monitor->MonitorEnter(self);
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorExit(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!monitor->MonitorExit(self)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorWait(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                       jrawMonitorID id,
+                                       jlong millis) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  // This is not in the spec, but it's the only thing that makes sense (and agrees with
+  // Object.wait).
+  if (millis < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  bool result = (millis > 0)
+      ? monitor->Wait(self, static_cast<uint64_t>(millis))
+      : monitor->Wait(self);
+
+  if (!result) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  // TODO: Make sure that is really what we should be checking here.
+  if (self->IsInterrupted()) {
+    return ERR(INTERRUPT);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorNotify(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!monitor->Notify(self)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError MonitorUtil::RawMonitorNotifyAll(jvmtiEnv* env ATTRIBUTE_UNUSED, jrawMonitorID id) {
+  if (id == nullptr) {
+    return ERR(INVALID_MONITOR);
+  }
+
+  JvmtiMonitor* monitor = DecodeMonitor(id);
+  art::Thread* self = art::Thread::Current();
+
+  if (!monitor->NotifyAll(self)) {
+    return ERR(NOT_MONITOR_OWNER);
+  }
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_monitor.h b/openjdkjvmti/ti_monitor.h
new file mode 100644
index 0000000..add089c
--- /dev/null
+++ b/openjdkjvmti/ti_monitor.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2017 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_TI_MONITOR_H_
+#define ART_OPENJDKJVMTI_TI_MONITOR_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class MonitorUtil {
+ public:
+  static jvmtiError CreateRawMonitor(jvmtiEnv* env, const char* name, jrawMonitorID* monitor_ptr);
+
+  static jvmtiError DestroyRawMonitor(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorEnter(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorExit(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorWait(jvmtiEnv* env, jrawMonitorID monitor, jlong millis);
+
+  static jvmtiError RawMonitorNotify(jvmtiEnv* env, jrawMonitorID monitor);
+
+  static jvmtiError RawMonitorNotifyAll(jvmtiEnv* env, jrawMonitorID monitor);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_MONITOR_H_
diff --git a/openjdkjvmti/ti_object.cc b/openjdkjvmti/ti_object.cc
new file mode 100644
index 0000000..2506aca
--- /dev/null
+++ b/openjdkjvmti/ti_object.cc
@@ -0,0 +1,76 @@
+/* Copyright (C) 2017 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 "ti_object.h"
+
+#include "art_jvmti.h"
+#include "mirror/object-inl.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError ObjectUtil::GetObjectSize(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                     jobject jobject,
+                                     jlong* size_ptr) {
+  if (jobject == nullptr) {
+    return ERR(INVALID_OBJECT);
+  }
+  if (size_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Object> object = soa.Decode<art::mirror::Object>(jobject);
+
+  *size_ptr = object->SizeOf();
+  return ERR(NONE);
+}
+
+jvmtiError ObjectUtil::GetObjectHashCode(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                         jobject jobject,
+                                         jint* hash_code_ptr) {
+  if (jobject == nullptr) {
+    return ERR(INVALID_OBJECT);
+  }
+  if (hash_code_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Object> object = soa.Decode<art::mirror::Object>(jobject);
+
+  *hash_code_ptr = object->IdentityHashCode();
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_object.h b/openjdkjvmti/ti_object.h
new file mode 100644
index 0000000..fa3bd0f
--- /dev/null
+++ b/openjdkjvmti/ti_object.h
@@ -0,0 +1,49 @@
+/* Copyright (C) 2017 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_TI_OBJECT_H_
+#define ART_OPENJDKJVMTI_TI_OBJECT_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ObjectUtil {
+ public:
+  static jvmtiError GetObjectSize(jvmtiEnv* env, jobject object, jlong* size_ptr);
+
+  static jvmtiError GetObjectHashCode(jvmtiEnv* env, jobject object, jint* hash_code_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_OBJECT_H_
diff --git a/openjdkjvmti/ti_phase.cc b/openjdkjvmti/ti_phase.cc
new file mode 100644
index 0000000..8893c9b
--- /dev/null
+++ b/openjdkjvmti/ti_phase.cc
@@ -0,0 +1,155 @@
+/* Copyright (C) 2017 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 "ti_phase.h"
+
+#include "art_jvmti.h"
+#include "base/macros.h"
+#include "events-inl.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_thread.h"
+
+namespace openjdkjvmti {
+
+jvmtiPhase PhaseUtil::current_phase_ = static_cast<jvmtiPhase>(0);
+
+struct PhaseUtil::PhaseCallback : public art::RuntimePhaseCallback {
+  inline static JNIEnv* GetJniEnv() {
+    return reinterpret_cast<JNIEnv*>(art::Thread::Current()->GetJniEnv());
+  }
+
+  inline static jthread GetCurrentJThread() {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+    return soa.AddLocalReference<jthread>(soa.Self()->GetPeer());
+  }
+
+  void NextRuntimePhase(RuntimePhase phase) REQUIRES_SHARED(art::Locks::mutator_lock_) OVERRIDE {
+    switch (phase) {
+      case RuntimePhase::kInitialAgents:
+        PhaseUtil::current_phase_ = JVMTI_PHASE_PRIMORDIAL;
+        break;
+      case RuntimePhase::kStart:
+        {
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent<ArtJvmtiEvent::kVmStart>(nullptr, GetJniEnv());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_START;
+        }
+        break;
+      case RuntimePhase::kInit:
+        {
+          ThreadUtil::CacheData();
+          ScopedLocalRef<jthread> thread(GetJniEnv(), GetCurrentJThread());
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent<ArtJvmtiEvent::kVmInit>(nullptr, GetJniEnv(), thread.get());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+        }
+        break;
+      case RuntimePhase::kDeath:
+        {
+          art::ScopedThreadSuspension sts(art::Thread::Current(), art::ThreadState::kNative);
+          event_handler->DispatchEvent<ArtJvmtiEvent::kVmDeath>(nullptr, GetJniEnv());
+          PhaseUtil::current_phase_ = JVMTI_PHASE_DEAD;
+        }
+        // TODO: Block events now.
+        break;
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+};
+
+PhaseUtil::PhaseCallback gPhaseCallback;
+
+jvmtiError PhaseUtil::GetPhase(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiPhase* phase_ptr) {
+  if (phase_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvmtiPhase now = PhaseUtil::current_phase_;
+  DCHECK(now == JVMTI_PHASE_ONLOAD ||
+         now == JVMTI_PHASE_PRIMORDIAL ||
+         now == JVMTI_PHASE_START ||
+         now == JVMTI_PHASE_LIVE ||
+         now == JVMTI_PHASE_DEAD);
+  *phase_ptr = now;
+  return ERR(NONE);
+}
+
+bool PhaseUtil::IsLivePhase() {
+  jvmtiPhase now = PhaseUtil::current_phase_;
+  DCHECK(now == JVMTI_PHASE_ONLOAD ||
+         now == JVMTI_PHASE_PRIMORDIAL ||
+         now == JVMTI_PHASE_START ||
+         now == JVMTI_PHASE_LIVE ||
+         now == JVMTI_PHASE_DEAD);
+  return now == JVMTI_PHASE_LIVE;
+}
+
+void PhaseUtil::SetToOnLoad() {
+  DCHECK_EQ(0u, static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToPrimordial() {
+  DCHECK_EQ(static_cast<size_t>(JVMTI_PHASE_ONLOAD), static_cast<size_t>(PhaseUtil::current_phase_));
+  PhaseUtil::current_phase_ = JVMTI_PHASE_ONLOAD;
+}
+
+void PhaseUtil::SetToLive() {
+  DCHECK_EQ(static_cast<size_t>(0), static_cast<size_t>(PhaseUtil::current_phase_));
+  ThreadUtil::CacheData();
+  PhaseUtil::current_phase_ = JVMTI_PHASE_LIVE;
+}
+
+void PhaseUtil::Register(EventHandler* handler) {
+  gPhaseCallback.event_handler = handler;
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add phase callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gPhaseCallback);
+}
+
+void PhaseUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove phase callback");
+  art::Runtime::Current()->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gPhaseCallback);
+}
+
+jvmtiPhase PhaseUtil::GetPhaseUnchecked() {
+  return PhaseUtil::current_phase_;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_phase.h b/openjdkjvmti/ti_phase.h
new file mode 100644
index 0000000..d4ed86b
--- /dev/null
+++ b/openjdkjvmti/ti_phase.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 2017 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_TI_PHASE_H_
+#define ART_OPENJDKJVMTI_TI_PHASE_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class PhaseUtil {
+ public:
+  static jvmtiError GetPhase(jvmtiEnv* env, jvmtiPhase* phase_ptr);
+  static bool IsLivePhase();
+
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
+  // Move the phase from unitialized to LOAD.
+  static void SetToOnLoad();
+
+  // Move the phase from LOAD to PRIMORDIAL.
+  static void SetToPrimordial();
+
+  // Move the phase from unitialized to LIVE.
+  static void SetToLive();
+
+  struct PhaseCallback;
+
+  static jvmtiPhase GetPhaseUnchecked();
+
+ private:
+  static jvmtiPhase current_phase_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_PHASE_H_
diff --git a/openjdkjvmti/ti_properties.cc b/openjdkjvmti/ti_properties.cc
new file mode 100644
index 0000000..c412814
--- /dev/null
+++ b/openjdkjvmti/ti_properties.cc
@@ -0,0 +1,236 @@
+/* Copyright (C) 2017 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 "ti_properties.h"
+
+#include <string.h>
+#include <vector>
+
+#include "jni.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "nativehelper/ScopedUtfChars.h"
+
+#include "art_jvmti.h"
+#include "runtime.h"
+#include "thread-current-inl.h"
+#include "ti_phase.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+// Hardcoded properties. Tests ensure that these are consistent with libcore's view, as seen
+// in System.java and AndroidHardcodedSystemProperties.java.
+static constexpr const char* kProperties[][2] = {
+    // Recommended by the spec.
+    { "java.vm.vendor", "The Android Project" },
+    { "java.vm.version", "2.1.0" },  // This is Runtime::GetVersion().
+    { "java.vm.name", "Dalvik" },
+    // Android does not provide java.vm.info.
+    //
+    // These are other values provided by AndroidHardcodedSystemProperties.
+    { "java.class.version", "50.0" },
+    { "java.version", "0" },
+    { "java.compiler", "" },
+    { "java.ext.dirs", "" },
+
+    { "java.specification.name", "Dalvik Core Library" },
+    { "java.specification.vendor", "The Android Project" },
+    { "java.specification.version", "0.9" },
+
+    { "java.vendor", "The Android Project" },
+    { "java.vendor.url", "http://www.android.com/" },
+    { "java.vm.name", "Dalvik" },
+    { "java.vm.specification.name", "Dalvik Virtual Machine Specification" },
+    { "java.vm.specification.vendor", "The Android Project" },
+    { "java.vm.specification.version", "0.9" },
+    { "java.vm.vendor", "The Android Project" },
+
+    { "java.vm.vendor.url", "http://www.android.com/" },
+
+    { "java.net.preferIPv6Addresses", "false" },
+
+    { "file.encoding", "UTF-8" },
+
+    { "file.separator", "/" },
+    { "line.separator", "\n" },
+    { "path.separator", ":" },
+
+    { "os.name", "Linux" },
+};
+static constexpr size_t kPropertiesSize = arraysize(kProperties);
+static constexpr const char* kPropertyLibraryPath = "java.library.path";
+static constexpr const char* kPropertyClassPath = "java.class.path";
+
+jvmtiError PropertiesUtil::GetSystemProperties(jvmtiEnv* env,
+                                               jint* count_ptr,
+                                               char*** property_ptr) {
+  if (count_ptr == nullptr || property_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  jvmtiError array_alloc_result;
+  JvmtiUniquePtr<char*[]> array_data_ptr = AllocJvmtiUniquePtr<char*[]>(env,
+                                                                        kPropertiesSize + 2,
+                                                                        &array_alloc_result);
+  if (array_data_ptr == nullptr) {
+    return array_alloc_result;
+  }
+
+  std::vector<JvmtiUniquePtr<char[]>> property_copies;
+
+  {
+    jvmtiError libpath_result;
+    JvmtiUniquePtr<char[]> libpath_data = CopyString(env, kPropertyLibraryPath, &libpath_result);
+    if (libpath_data == nullptr) {
+      return libpath_result;
+    }
+    array_data_ptr.get()[0] = libpath_data.get();
+    property_copies.push_back(std::move(libpath_data));
+  }
+
+  {
+    jvmtiError classpath_result;
+    JvmtiUniquePtr<char[]> classpath_data = CopyString(env, kPropertyClassPath, &classpath_result);
+    if (classpath_data == nullptr) {
+      return classpath_result;
+    }
+    array_data_ptr.get()[1] = classpath_data.get();
+    property_copies.push_back(std::move(classpath_data));
+  }
+
+  for (size_t i = 0; i != kPropertiesSize; ++i) {
+    jvmtiError data_result;
+    JvmtiUniquePtr<char[]> data = CopyString(env, kProperties[i][0], &data_result);
+    if (data == nullptr) {
+      return data_result;
+    }
+    array_data_ptr.get()[i + 2] = data.get();
+    property_copies.push_back(std::move(data));
+  }
+
+  // Everything is OK, release the data.
+  *count_ptr = kPropertiesSize + 2;
+  *property_ptr = array_data_ptr.release();
+  for (auto& uptr : property_copies) {
+    uptr.release();
+  }
+
+  return ERR(NONE);
+}
+
+static jvmtiError Copy(jvmtiEnv* env, const char* in, char** out) {
+  jvmtiError result;
+  JvmtiUniquePtr<char[]> data = CopyString(env, in, &result);
+  *out = data.release();
+  return result;
+}
+
+// See dalvik_system_VMRuntime.cpp.
+static const char* DefaultToDot(const std::string& class_path) {
+  return class_path.empty() ? "." : class_path.c_str();
+}
+
+// Handle kPropertyLibraryPath.
+static jvmtiError GetLibraryPath(jvmtiEnv* env, char** value_ptr) {
+  const std::vector<std::string>& runtime_props = art::Runtime::Current()->GetProperties();
+  for (const std::string& prop_assignment : runtime_props) {
+    size_t assign_pos = prop_assignment.find('=');
+    if (assign_pos != std::string::npos && assign_pos > 0) {
+      if (prop_assignment.substr(0, assign_pos) == kPropertyLibraryPath) {
+        return Copy(env, prop_assignment.substr(assign_pos + 1).c_str(), value_ptr);
+      }
+    }
+  }
+  if (!PhaseUtil::IsLivePhase()) {
+    return ERR(NOT_AVAILABLE);
+  }
+  // We expect this call to be rare. So don't optimize.
+  DCHECK(art::Thread::Current() != nullptr);
+  JNIEnv* jni_env = art::Thread::Current()->GetJniEnv();
+  jmethodID get_prop = jni_env->GetStaticMethodID(art::WellKnownClasses::java_lang_System,
+                                                  "getProperty",
+                                                  "(Ljava/lang/String;)Ljava/lang/String;");
+  CHECK(get_prop != nullptr);
+
+  ScopedLocalRef<jobject> input_str(jni_env, jni_env->NewStringUTF(kPropertyLibraryPath));
+  if (input_str.get() == nullptr) {
+    jni_env->ExceptionClear();
+    return ERR(OUT_OF_MEMORY);
+  }
+
+  ScopedLocalRef<jobject> prop_res(
+      jni_env, jni_env->CallStaticObjectMethod(art::WellKnownClasses::java_lang_System,
+                                               get_prop,
+                                               input_str.get()));
+  if (jni_env->ExceptionCheck() == JNI_TRUE) {
+    jni_env->ExceptionClear();
+    return ERR(INTERNAL);
+  }
+  if (prop_res.get() == nullptr) {
+    *value_ptr = nullptr;
+    return ERR(NONE);
+  }
+
+  ScopedUtfChars chars(jni_env, reinterpret_cast<jstring>(prop_res.get()));
+  return Copy(env, chars.c_str(), value_ptr);
+}
+
+jvmtiError PropertiesUtil::GetSystemProperty(jvmtiEnv* env,
+                                             const char* property,
+                                             char** value_ptr) {
+  if (property == nullptr || value_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  if (strcmp(property, kPropertyLibraryPath) == 0) {
+    return GetLibraryPath(env, value_ptr);
+  }
+
+  if (strcmp(property, kPropertyClassPath) == 0) {
+    return Copy(env, DefaultToDot(art::Runtime::Current()->GetClassPathString()), value_ptr);
+  }
+
+  for (size_t i = 0; i != kPropertiesSize; ++i) {
+    if (strcmp(property, kProperties[i][0]) == 0) {
+      return Copy(env, kProperties[i][1], value_ptr);
+    }
+  }
+
+  return ERR(NOT_AVAILABLE);
+}
+
+jvmtiError PropertiesUtil::SetSystemProperty(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             const char* property ATTRIBUTE_UNUSED,
+                                             const char* value ATTRIBUTE_UNUSED) {
+  // We do not allow manipulation of any property here.
+  return ERR(NOT_AVAILABLE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_properties.h b/openjdkjvmti/ti_properties.h
new file mode 100644
index 0000000..187b85d
--- /dev/null
+++ b/openjdkjvmti/ti_properties.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2017 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_TI_PROPERTIES_H_
+#define ART_OPENJDKJVMTI_TI_PROPERTIES_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class PropertiesUtil {
+ public:
+  static jvmtiError GetSystemProperties(jvmtiEnv* env, jint* count_ptr, char*** property_ptr);
+
+  static jvmtiError GetSystemProperty(jvmtiEnv* env, const char* property, char** value_ptr);
+
+  static jvmtiError SetSystemProperty(jvmtiEnv* env, const char* property, const char* value);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_PROPERTIES_H_
diff --git a/openjdkjvmti/ti_redefine.cc b/openjdkjvmti/ti_redefine.cc
new file mode 100644
index 0000000..c679d73
--- /dev/null
+++ b/openjdkjvmti/ti_redefine.cc
@@ -0,0 +1,1517 @@
+/* Copyright (C) 2016 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 "ti_redefine.h"
+
+#include <limits>
+
+#include "android-base/stringprintf.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/array_ref.h"
+#include "base/logging.h"
+#include "base/stringpiece.h"
+#include "class_linker-inl.h"
+#include "debugger.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc/allocation_listener.h"
+#include "gc/heap.h"
+#include "instrumentation.h"
+#include "intern_table.h"
+#include "jdwp/jdwp.h"
+#include "jdwp/jdwp_constants.h"
+#include "jdwp/jdwp_event.h"
+#include "jdwp/object_registry.h"
+#include "jit/jit.h"
+#include "jit/jit_code_cache.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti_allocator.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/object.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "non_debuggable_classes.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "ti_breakpoint.h"
+#include "ti_class_loader.h"
+#include "transform.h"
+#include "verifier/method_verifier.h"
+#include "verifier/verifier_enums.h"
+
+namespace openjdkjvmti {
+
+using android::base::StringPrintf;
+
+// A helper that fills in a classes obsolete_methods_ and obsolete_dex_caches_ classExt fields as
+// they are created. This ensures that we can always call any method of an obsolete ArtMethod object
+// almost as soon as they are created since the GetObsoleteDexCache method will succeed.
+class ObsoleteMap {
+ public:
+  art::ArtMethod* FindObsoleteVersion(art::ArtMethod* original)
+      REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
+    auto method_pair = id_map_.find(original);
+    if (method_pair != id_map_.end()) {
+      art::ArtMethod* res = obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(
+          method_pair->second, art::kRuntimePointerSize);
+      DCHECK(res != nullptr);
+      DCHECK_EQ(original, res->GetNonObsoleteMethod());
+      return res;
+    } else {
+      return nullptr;
+    }
+  }
+
+  void RecordObsolete(art::ArtMethod* original, art::ArtMethod* obsolete)
+      REQUIRES(art::Locks::mutator_lock_, art::Roles::uninterruptible_) {
+    DCHECK(original != nullptr);
+    DCHECK(obsolete != nullptr);
+    int32_t slot = next_free_slot_++;
+    DCHECK_LT(slot, obsolete_methods_->GetLength());
+    DCHECK(nullptr ==
+           obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(slot, art::kRuntimePointerSize));
+    DCHECK(nullptr == obsolete_dex_caches_->Get(slot));
+    obsolete_methods_->SetElementPtrSize(slot, obsolete, art::kRuntimePointerSize);
+    obsolete_dex_caches_->Set(slot, original_dex_cache_);
+    id_map_.insert({original, slot});
+  }
+
+  ObsoleteMap(art::ObjPtr<art::mirror::PointerArray> obsolete_methods,
+              art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_dex_caches,
+              art::ObjPtr<art::mirror::DexCache> original_dex_cache)
+      : next_free_slot_(0),
+        obsolete_methods_(obsolete_methods),
+        obsolete_dex_caches_(obsolete_dex_caches),
+        original_dex_cache_(original_dex_cache) {
+    // Figure out where the first unused slot in the obsolete_methods_ array is.
+    while (obsolete_methods_->GetElementPtrSize<art::ArtMethod*>(
+        next_free_slot_, art::kRuntimePointerSize) != nullptr) {
+      DCHECK(obsolete_dex_caches_->Get(next_free_slot_) != nullptr);
+      next_free_slot_++;
+    }
+    // Sanity check that the same slot in obsolete_dex_caches_ is free.
+    DCHECK(obsolete_dex_caches_->Get(next_free_slot_) == nullptr);
+  }
+
+ private:
+  int32_t next_free_slot_;
+  std::unordered_map<art::ArtMethod*, int32_t> id_map_;
+  // Pointers to the fields in mirror::ClassExt. These can be held as ObjPtr since this is only used
+  // when we have an exclusive mutator_lock_ (i.e. all threads are suspended).
+  art::ObjPtr<art::mirror::PointerArray> obsolete_methods_;
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::DexCache>> obsolete_dex_caches_;
+  art::ObjPtr<art::mirror::DexCache> original_dex_cache_;
+};
+
+// This visitor walks thread stacks and allocates and sets up the obsolete methods. It also does
+// some basic sanity checks that the obsolete method is sane.
+class ObsoleteMethodStackVisitor : public art::StackVisitor {
+ protected:
+  ObsoleteMethodStackVisitor(
+      art::Thread* thread,
+      art::LinearAlloc* allocator,
+      const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+      ObsoleteMap* obsolete_maps)
+        : StackVisitor(thread,
+                       /*context*/nullptr,
+                       StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+          allocator_(allocator),
+          obsoleted_methods_(obsoleted_methods),
+          obsolete_maps_(obsolete_maps) { }
+
+  ~ObsoleteMethodStackVisitor() OVERRIDE {}
+
+ public:
+  // Returns true if we successfully installed obsolete methods on this thread, filling
+  // obsolete_maps_ with the translations if needed. Returns false and fills error_msg if we fail.
+  // The stack is cleaned up when we fail.
+  static void UpdateObsoleteFrames(
+      art::Thread* thread,
+      art::LinearAlloc* allocator,
+      const std::unordered_set<art::ArtMethod*>& obsoleted_methods,
+      ObsoleteMap* obsolete_maps)
+        REQUIRES(art::Locks::mutator_lock_) {
+    ObsoleteMethodStackVisitor visitor(thread,
+                                       allocator,
+                                       obsoleted_methods,
+                                       obsolete_maps);
+    visitor.WalkStack();
+  }
+
+  bool VisitFrame() OVERRIDE REQUIRES(art::Locks::mutator_lock_) {
+    art::ScopedAssertNoThreadSuspension snts("Fixing up the stack for obsolete methods.");
+    art::ArtMethod* old_method = GetMethod();
+    if (obsoleted_methods_.find(old_method) != obsoleted_methods_.end()) {
+      // We cannot ensure that the right dex file is used in inlined frames so we don't support
+      // redefining them.
+      DCHECK(!IsInInlinedFrame()) << "Inlined frames are not supported when using redefinition";
+      art::ArtMethod* new_obsolete_method = obsolete_maps_->FindObsoleteVersion(old_method);
+      if (new_obsolete_method == nullptr) {
+        // Create a new Obsolete Method and put it in the list.
+        art::Runtime* runtime = art::Runtime::Current();
+        art::ClassLinker* cl = runtime->GetClassLinker();
+        auto ptr_size = cl->GetImagePointerSize();
+        const size_t method_size = art::ArtMethod::Size(ptr_size);
+        auto* method_storage = allocator_->Alloc(art::Thread::Current(), method_size);
+        CHECK(method_storage != nullptr) << "Unable to allocate storage for obsolete version of '"
+                                         << old_method->PrettyMethod() << "'";
+        new_obsolete_method = new (method_storage) art::ArtMethod();
+        new_obsolete_method->CopyFrom(old_method, ptr_size);
+        DCHECK_EQ(new_obsolete_method->GetDeclaringClass(), old_method->GetDeclaringClass());
+        new_obsolete_method->SetIsObsolete();
+        new_obsolete_method->SetDontCompile();
+        cl->SetEntryPointsForObsoleteMethod(new_obsolete_method);
+        obsolete_maps_->RecordObsolete(old_method, new_obsolete_method);
+        // Update JIT Data structures to point to the new method.
+        art::jit::Jit* jit = art::Runtime::Current()->GetJit();
+        if (jit != nullptr) {
+          // Notify the JIT we are making this obsolete method. It will update the jit's internal
+          // structures to keep track of the new obsolete method.
+          jit->GetCodeCache()->MoveObsoleteMethod(old_method, new_obsolete_method);
+        }
+      }
+      DCHECK(new_obsolete_method != nullptr);
+      SetMethod(new_obsolete_method);
+    }
+    return true;
+  }
+
+ private:
+  // The linear allocator we should use to make new methods.
+  art::LinearAlloc* allocator_;
+  // The set of all methods which could be obsoleted.
+  const std::unordered_set<art::ArtMethod*>& obsoleted_methods_;
+  // A map from the original to the newly allocated obsolete method for frames on this thread. The
+  // values in this map are added to the obsolete_methods_ (and obsolete_dex_caches_) fields of
+  // the redefined classes ClassExt as it is filled.
+  ObsoleteMap* obsolete_maps_;
+};
+
+jvmtiError Redefiner::IsModifiableClass(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                        jclass klass,
+                                        jboolean* is_redefinable) {
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::StackHandleScope<1> hs(self);
+  art::ObjPtr<art::mirror::Object> obj(self->DecodeJObject(klass));
+  if (obj.IsNull()) {
+    return ERR(INVALID_CLASS);
+  }
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(obj->AsClass()));
+  std::string err_unused;
+  *is_redefinable =
+      Redefiner::GetClassRedefinitionError(h_klass, &err_unused) == OK ? JNI_TRUE : JNI_FALSE;
+  return OK;
+}
+
+jvmtiError Redefiner::GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
+                                                /*out*/std::string* error_msg) {
+  if (klass->IsPrimitive()) {
+    *error_msg = "Modification of primitive classes is not supported";
+    return ERR(UNMODIFIABLE_CLASS);
+  } else if (klass->IsInterface()) {
+    *error_msg = "Modification of Interface classes is currently not supported";
+    return ERR(UNMODIFIABLE_CLASS);
+  } else if (klass->IsStringClass()) {
+    *error_msg = "Modification of String class is not supported";
+    return ERR(UNMODIFIABLE_CLASS);
+  } else if (klass->IsArrayClass()) {
+    *error_msg = "Modification of Array classes is not supported";
+    return ERR(UNMODIFIABLE_CLASS);
+  } else if (klass->IsProxyClass()) {
+    *error_msg = "Modification of proxy classes is not supported";
+    return ERR(UNMODIFIABLE_CLASS);
+  }
+
+  for (jclass c : art::NonDebuggableClasses::GetNonDebuggableClasses()) {
+    if (klass.Get() == art::Thread::Current()->DecodeJObject(c)->AsClass()) {
+      *error_msg = "Class might have stack frames that cannot be made obsolete";
+      return ERR(UNMODIFIABLE_CLASS);
+    }
+  }
+
+  return OK;
+}
+
+// Moves dex data to an anonymous, read-only mmap'd region.
+std::unique_ptr<art::MemMap> Redefiner::MoveDataToMemMap(const std::string& original_location,
+                                                         art::ArrayRef<const unsigned char> data,
+                                                         std::string* error_msg) {
+  std::unique_ptr<art::MemMap> map(art::MemMap::MapAnonymous(
+      StringPrintf("%s-transformed", original_location.c_str()).c_str(),
+      nullptr,
+      data.size(),
+      PROT_READ|PROT_WRITE,
+      /*low_4gb*/false,
+      /*reuse*/false,
+      error_msg));
+  if (map == nullptr) {
+    return map;
+  }
+  memcpy(map->Begin(), data.data(), data.size());
+  // Make the dex files mmap read only. This matches how other DexFiles are mmaped and prevents
+  // programs from corrupting it.
+  map->Protect(PROT_READ);
+  return map;
+}
+
+Redefiner::ClassRedefinition::ClassRedefinition(
+    Redefiner* driver,
+    jclass klass,
+    const art::DexFile* redefined_dex_file,
+    const char* class_sig,
+    art::ArrayRef<const unsigned char> orig_dex_file) :
+      driver_(driver),
+      klass_(klass),
+      dex_file_(redefined_dex_file),
+      class_sig_(class_sig),
+      original_dex_file_(orig_dex_file) {
+  GetMirrorClass()->MonitorEnter(driver_->self_);
+}
+
+Redefiner::ClassRedefinition::~ClassRedefinition() {
+  if (driver_ != nullptr) {
+    GetMirrorClass()->MonitorExit(driver_->self_);
+  }
+}
+
+jvmtiError Redefiner::RedefineClasses(ArtJvmTiEnv* env,
+                                      EventHandler* event_handler,
+                                      art::Runtime* runtime,
+                                      art::Thread* self,
+                                      jint class_count,
+                                      const jvmtiClassDefinition* definitions,
+                                      /*out*/std::string* error_msg) {
+  if (env == nullptr) {
+    *error_msg = "env was null!";
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (class_count < 0) {
+    *error_msg = "class_count was less then 0";
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (class_count == 0) {
+    // We don't actually need to do anything. Just return OK.
+    return OK;
+  } else if (definitions == nullptr) {
+    *error_msg = "null definitions!";
+    return ERR(NULL_POINTER);
+  }
+  std::vector<ArtClassDefinition> def_vector;
+  def_vector.reserve(class_count);
+  for (jint i = 0; i < class_count; i++) {
+    jboolean is_modifiable = JNI_FALSE;
+    jvmtiError res = env->IsModifiableClass(definitions[i].klass, &is_modifiable);
+    if (res != OK) {
+      return res;
+    } else if (!is_modifiable) {
+      return ERR(UNMODIFIABLE_CLASS);
+    }
+    // We make a copy of the class_bytes to pass into the retransformation.
+    // This makes cleanup easier (since we unambiguously own the bytes) and also is useful since we
+    // will need to keep the original bytes around unaltered for subsequent RetransformClasses calls
+    // to get the passed in bytes.
+    unsigned char* class_bytes_copy = nullptr;
+    res = env->Allocate(definitions[i].class_byte_count, &class_bytes_copy);
+    if (res != OK) {
+      return res;
+    }
+    memcpy(class_bytes_copy, definitions[i].class_bytes, definitions[i].class_byte_count);
+
+    ArtClassDefinition def;
+    res = def.Init(env, definitions[i]);
+    if (res != OK) {
+      return res;
+    }
+    def_vector.push_back(std::move(def));
+  }
+  // Call all the transformation events.
+  jvmtiError res = Transformer::RetransformClassesDirect(env,
+                                                         event_handler,
+                                                         self,
+                                                         &def_vector);
+  if (res != OK) {
+    // Something went wrong with transformation!
+    return res;
+  }
+  return RedefineClassesDirect(env, runtime, self, def_vector, error_msg);
+}
+
+jvmtiError Redefiner::RedefineClassesDirect(ArtJvmTiEnv* env,
+                                            art::Runtime* runtime,
+                                            art::Thread* self,
+                                            const std::vector<ArtClassDefinition>& definitions,
+                                            std::string* error_msg) {
+  DCHECK(env != nullptr);
+  if (definitions.size() == 0) {
+    // We don't actually need to do anything. Just return OK.
+    return OK;
+  }
+  // Stop JIT for the duration of this redefine since the JIT might concurrently compile a method we
+  // are going to redefine.
+  art::jit::ScopedJitSuspend suspend_jit;
+  // Get shared mutator lock so we can lock all the classes.
+  art::ScopedObjectAccess soa(self);
+  Redefiner r(env, runtime, self, error_msg);
+  for (const ArtClassDefinition& def : definitions) {
+    // Only try to transform classes that have been modified.
+    if (def.IsModified()) {
+      jvmtiError res = r.AddRedefinition(env, def);
+      if (res != OK) {
+        return res;
+      }
+    }
+  }
+  return r.Run();
+}
+
+jvmtiError Redefiner::AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def) {
+  std::string original_dex_location;
+  jvmtiError ret = OK;
+  if ((ret = GetClassLocation(env, def.GetClass(), &original_dex_location))) {
+    *error_msg_ = "Unable to get original dex file location!";
+    return ret;
+  }
+  char* generic_ptr_unused = nullptr;
+  char* signature_ptr = nullptr;
+  if ((ret = env->GetClassSignature(def.GetClass(), &signature_ptr, &generic_ptr_unused)) != OK) {
+    *error_msg_ = "Unable to get class signature!";
+    return ret;
+  }
+  JvmtiUniquePtr<char> generic_unique_ptr(MakeJvmtiUniquePtr(env, generic_ptr_unused));
+  JvmtiUniquePtr<char> signature_unique_ptr(MakeJvmtiUniquePtr(env, signature_ptr));
+  std::unique_ptr<art::MemMap> map(MoveDataToMemMap(original_dex_location,
+                                                    def.GetDexData(),
+                                                    error_msg_));
+  std::ostringstream os;
+  if (map.get() == nullptr) {
+    os << "Failed to create anonymous mmap for modified dex file of class " << def.GetName()
+       << "in dex file " << original_dex_location << " because: " << *error_msg_;
+    *error_msg_ = os.str();
+    return ERR(OUT_OF_MEMORY);
+  }
+  if (map->Size() < sizeof(art::DexFile::Header)) {
+    *error_msg_ = "Could not read dex file header because dex_data was too short";
+    return ERR(INVALID_CLASS_FORMAT);
+  }
+  uint32_t checksum = reinterpret_cast<const art::DexFile::Header*>(map->Begin())->checksum_;
+  std::unique_ptr<const art::DexFile> dex_file(art::DexFile::Open(map->GetName(),
+                                                                  checksum,
+                                                                  std::move(map),
+                                                                  /*verify*/true,
+                                                                  /*verify_checksum*/true,
+                                                                  error_msg_));
+  if (dex_file.get() == nullptr) {
+    os << "Unable to load modified dex file for " << def.GetName() << ": " << *error_msg_;
+    *error_msg_ = os.str();
+    return ERR(INVALID_CLASS_FORMAT);
+  }
+  redefinitions_.push_back(
+      Redefiner::ClassRedefinition(this,
+                                   def.GetClass(),
+                                   dex_file.release(),
+                                   signature_ptr,
+                                   def.GetNewOriginalDexFile()));
+  return OK;
+}
+
+art::mirror::Class* Redefiner::ClassRedefinition::GetMirrorClass() {
+  return driver_->self_->DecodeJObject(klass_)->AsClass();
+}
+
+art::mirror::ClassLoader* Redefiner::ClassRedefinition::GetClassLoader() {
+  return GetMirrorClass()->GetClassLoader();
+}
+
+art::mirror::DexCache* Redefiner::ClassRedefinition::CreateNewDexCache(
+    art::Handle<art::mirror::ClassLoader> loader) {
+  art::StackHandleScope<2> hs(driver_->self_);
+  art::ClassLinker* cl = driver_->runtime_->GetClassLinker();
+  art::Handle<art::mirror::DexCache> cache(hs.NewHandle(
+      art::ObjPtr<art::mirror::DexCache>::DownCast(
+          cl->GetClassRoot(art::ClassLinker::kJavaLangDexCache)->AllocObject(driver_->self_))));
+  if (cache.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    return nullptr;
+  }
+  art::Handle<art::mirror::String> location(hs.NewHandle(
+      cl->GetInternTable()->InternStrong(dex_file_->GetLocation().c_str())));
+  if (location.IsNull()) {
+    driver_->self_->AssertPendingOOMException();
+    return nullptr;
+  }
+  art::WriterMutexLock mu(driver_->self_, *art::Locks::dex_lock_);
+  art::mirror::DexCache::InitializeDexCache(driver_->self_,
+                                            cache.Get(),
+                                            location.Get(),
+                                            dex_file_.get(),
+                                            loader.IsNull() ? driver_->runtime_->GetLinearAlloc()
+                                                            : loader->GetAllocator(),
+                                            art::kRuntimePointerSize);
+  return cache.Get();
+}
+
+void Redefiner::RecordFailure(jvmtiError result,
+                              const std::string& class_sig,
+                              const std::string& error_msg) {
+  *error_msg_ = StringPrintf("Unable to perform redefinition of '%s': %s",
+                             class_sig.c_str(),
+                             error_msg.c_str());
+  result_ = result;
+}
+
+art::mirror::Object* Redefiner::ClassRedefinition::AllocateOrGetOriginalDexFile() {
+  // If we have been specifically given a new set of bytes use that
+  if (original_dex_file_.size() != 0) {
+    return art::mirror::ByteArray::AllocateAndFill(
+        driver_->self_,
+        reinterpret_cast<const signed char*>(original_dex_file_.data()),
+        original_dex_file_.size());
+  }
+
+  // See if we already have one set.
+  art::ObjPtr<art::mirror::ClassExt> ext(GetMirrorClass()->GetExtData());
+  if (!ext.IsNull()) {
+    art::ObjPtr<art::mirror::Object> old_original_dex_file(ext->GetOriginalDexFile());
+    if (!old_original_dex_file.IsNull()) {
+      // We do. Use it.
+      return old_original_dex_file.Ptr();
+    }
+  }
+
+  // return the current dex_cache which has the dex file in it.
+  art::ObjPtr<art::mirror::DexCache> current_dex_cache(GetMirrorClass()->GetDexCache());
+  // TODO Handle this or make it so it cannot happen.
+  if (current_dex_cache->GetDexFile()->NumClassDefs() != 1) {
+    LOG(WARNING) << "Current dex file has more than one class in it. Calling RetransformClasses "
+                 << "on this class might fail if no transformations are applied to it!";
+  }
+  return current_dex_cache.Ptr();
+}
+
+struct CallbackCtx {
+  ObsoleteMap* obsolete_map;
+  art::LinearAlloc* allocator;
+  std::unordered_set<art::ArtMethod*> obsolete_methods;
+
+  explicit CallbackCtx(ObsoleteMap* map, art::LinearAlloc* alloc)
+      : obsolete_map(map), allocator(alloc) {}
+};
+
+void DoAllocateObsoleteMethodsCallback(art::Thread* t, void* vdata) NO_THREAD_SAFETY_ANALYSIS {
+  CallbackCtx* data = reinterpret_cast<CallbackCtx*>(vdata);
+  ObsoleteMethodStackVisitor::UpdateObsoleteFrames(t,
+                                                   data->allocator,
+                                                   data->obsolete_methods,
+                                                   data->obsolete_map);
+}
+
+// This creates any ArtMethod* structures needed for obsolete methods and ensures that the stack is
+// updated so they will be run.
+// TODO Rewrite so we can do this only once regardless of how many redefinitions there are.
+void Redefiner::ClassRedefinition::FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass) {
+  art::ScopedAssertNoThreadSuspension ns("No thread suspension during thread stack walking");
+  art::mirror::ClassExt* ext = art_klass->GetExtData();
+  CHECK(ext->GetObsoleteMethods() != nullptr);
+  art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
+  // This holds pointers to the obsolete methods map fields which are updated as needed.
+  ObsoleteMap map(ext->GetObsoleteMethods(), ext->GetObsoleteDexCaches(), art_klass->GetDexCache());
+  CallbackCtx ctx(&map, linker->GetAllocatorForClassLoader(art_klass->GetClassLoader()));
+  // Add all the declared methods to the map
+  for (auto& m : art_klass->GetDeclaredMethods(art::kRuntimePointerSize)) {
+    if (m.IsIntrinsic()) {
+      LOG(WARNING) << "Redefining intrinsic method " << m.PrettyMethod() << ". This may cause the "
+                   << "unexpected use of the original definition of " << m.PrettyMethod() << "in "
+                   << "methods that have already been compiled.";
+    }
+    // It is possible to simply filter out some methods where they cannot really become obsolete,
+    // such as native methods and keep their original (possibly optimized) implementations. We don't
+    // do this, however, since we would need to mark these functions (still in the classes
+    // declared_methods array) as obsolete so we will find the correct dex file to get meta-data
+    // from (for example about stack-frame size). Furthermore we would be unable to get some useful
+    // error checking from the interpreter which ensure we don't try to start executing obsolete
+    // methods.
+    ctx.obsolete_methods.insert(&m);
+  }
+  {
+    art::MutexLock mu(driver_->self_, *art::Locks::thread_list_lock_);
+    art::ThreadList* list = art::Runtime::Current()->GetThreadList();
+    list->ForEach(DoAllocateObsoleteMethodsCallback, static_cast<void*>(&ctx));
+  }
+}
+
+// Try and get the declared method. First try to get a virtual method then a direct method if that's
+// not found.
+static art::ArtMethod* FindMethod(art::Handle<art::mirror::Class> klass,
+                                  art::StringPiece name,
+                                  art::Signature sig) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  DCHECK(!klass->IsProxyClass());
+  for (art::ArtMethod& m : klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize)) {
+    if (m.GetName() == name && m.GetSignature() == sig) {
+      return &m;
+    }
+  }
+  return nullptr;
+}
+
+bool Redefiner::ClassRedefinition::CheckSameMethods() {
+  art::StackHandleScope<1> hs(driver_->self_);
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
+  DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+
+  art::ClassDataItemIterator new_iter(*dex_file_,
+                                      dex_file_->GetClassData(dex_file_->GetClassDef(0)));
+
+  // Make sure we have the same number of methods.
+  uint32_t num_new_method = new_iter.NumVirtualMethods() + new_iter.NumDirectMethods();
+  uint32_t num_old_method = h_klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size();
+  if (num_new_method != num_old_method) {
+    bool bigger = num_new_method > num_old_method;
+    RecordFailure(bigger ? ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED)
+                         : ERR(UNSUPPORTED_REDEFINITION_METHOD_DELETED),
+                  StringPrintf("Total number of declared methods changed from %d to %d",
+                               num_old_method, num_new_method));
+    return false;
+  }
+
+  // Skip all of the fields. We should have already checked this.
+  new_iter.SkipAllFields();
+  // Check each of the methods. NB we don't need to specifically check for removals since the 2 dex
+  // files have the same number of methods, which means there must be an equal amount of additions
+  // and removals.
+  for (; new_iter.HasNextVirtualMethod() || new_iter.HasNextDirectMethod(); new_iter.Next()) {
+    // Get the data on the method we are searching for
+    const art::DexFile::MethodId& new_method_id = dex_file_->GetMethodId(new_iter.GetMemberIndex());
+    const char* new_method_name = dex_file_->GetMethodName(new_method_id);
+    art::Signature new_method_signature = dex_file_->GetMethodSignature(new_method_id);
+    art::ArtMethod* old_method = FindMethod(h_klass, new_method_name, new_method_signature);
+    // If we got past the check for the same number of methods above that means there must be at
+    // least one added and one removed method. We will return the ADDED failure message since it is
+    // easier to get a useful error report for it.
+    if (old_method == nullptr) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_ADDED),
+                    StringPrintf("Unknown method '%s' (sig: %s) was added!",
+                                  new_method_name,
+                                  new_method_signature.ToString().c_str()));
+      return false;
+    }
+    // Since direct methods have different flags than virtual ones (specifically direct methods must
+    // have kAccPrivate or kAccStatic or kAccConstructor flags) we can tell if a method changes from
+    // virtual to direct.
+    uint32_t new_flags = new_iter.GetMethodAccessFlags() & ~art::kAccPreviouslyWarm;
+    if (new_flags != (old_method->GetAccessFlags() & (art::kAccValidMethodFlags ^ art::kAccPreviouslyWarm))) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_METHOD_MODIFIERS_CHANGED),
+                    StringPrintf("method '%s' (sig: %s) had different access flags",
+                                 new_method_name,
+                                 new_method_signature.ToString().c_str()));
+      return false;
+    }
+  }
+  return true;
+}
+
+bool Redefiner::ClassRedefinition::CheckSameFields() {
+  art::StackHandleScope<1> hs(driver_->self_);
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
+  DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+  art::ClassDataItemIterator new_iter(*dex_file_,
+                                      dex_file_->GetClassData(dex_file_->GetClassDef(0)));
+  const art::DexFile& old_dex_file = h_klass->GetDexFile();
+  art::ClassDataItemIterator old_iter(old_dex_file,
+                                      old_dex_file.GetClassData(*h_klass->GetClassDef()));
+  // Instance and static fields can be differentiated by their flags so no need to check them
+  // separately.
+  while (new_iter.HasNextInstanceField() || new_iter.HasNextStaticField()) {
+    // Get the data on the method we are searching for
+    const art::DexFile::FieldId& new_field_id = dex_file_->GetFieldId(new_iter.GetMemberIndex());
+    const char* new_field_name = dex_file_->GetFieldName(new_field_id);
+    const char* new_field_type = dex_file_->GetFieldTypeDescriptor(new_field_id);
+
+    if (!(old_iter.HasNextInstanceField() || old_iter.HasNextStaticField())) {
+      // We are missing the old version of this method!
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+                    StringPrintf("Unknown field '%s' (type: %s) added!",
+                                  new_field_name,
+                                  new_field_type));
+      return false;
+    }
+
+    const art::DexFile::FieldId& old_field_id = old_dex_file.GetFieldId(old_iter.GetMemberIndex());
+    const char* old_field_name = old_dex_file.GetFieldName(old_field_id);
+    const char* old_field_type = old_dex_file.GetFieldTypeDescriptor(old_field_id);
+
+    // Check name and type.
+    if (strcmp(old_field_name, new_field_name) != 0 ||
+        strcmp(old_field_type, new_field_type) != 0) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+                    StringPrintf("Field changed from '%s' (sig: %s) to '%s' (sig: %s)!",
+                                  old_field_name,
+                                  old_field_type,
+                                  new_field_name,
+                                  new_field_type));
+      return false;
+    }
+
+    // Since static fields have different flags than instance ones (specifically static fields must
+    // have the kAccStatic flag) we can tell if a field changes from static to instance.
+    if (new_iter.GetFieldAccessFlags() != old_iter.GetFieldAccessFlags()) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+                    StringPrintf("Field '%s' (sig: %s) had different access flags",
+                                  new_field_name,
+                                  new_field_type));
+      return false;
+    }
+
+    new_iter.Next();
+    old_iter.Next();
+  }
+  if (old_iter.HasNextInstanceField() || old_iter.HasNextStaticField()) {
+    RecordFailure(ERR(UNSUPPORTED_REDEFINITION_SCHEMA_CHANGED),
+                  StringPrintf("field '%s' (sig: %s) is missing!",
+                                old_dex_file.GetFieldName(old_dex_file.GetFieldId(
+                                    old_iter.GetMemberIndex())),
+                                old_dex_file.GetFieldTypeDescriptor(old_dex_file.GetFieldId(
+                                    old_iter.GetMemberIndex()))));
+    return false;
+  }
+  return true;
+}
+
+bool Redefiner::ClassRedefinition::CheckClass() {
+  art::StackHandleScope<1> hs(driver_->self_);
+  // Easy check that only 1 class def is present.
+  if (dex_file_->NumClassDefs() != 1) {
+    RecordFailure(ERR(ILLEGAL_ARGUMENT),
+                  StringPrintf("Expected 1 class def in dex file but found %d",
+                               dex_file_->NumClassDefs()));
+    return false;
+  }
+  // Get the ClassDef from the new DexFile.
+  // Since the dex file has only a single class def the index is always 0.
+  const art::DexFile::ClassDef& def = dex_file_->GetClassDef(0);
+  // Get the class as it is now.
+  art::Handle<art::mirror::Class> current_class(hs.NewHandle(GetMirrorClass()));
+
+  // Check the access flags didn't change.
+  if (def.GetJavaAccessFlags() != (current_class->GetAccessFlags() & art::kAccValidClassFlags)) {
+    RecordFailure(ERR(UNSUPPORTED_REDEFINITION_CLASS_MODIFIERS_CHANGED),
+                  "Cannot change modifiers of class by redefinition");
+    return false;
+  }
+
+  // Check class name.
+  // These should have been checked by the dexfile verifier on load.
+  DCHECK_NE(def.class_idx_, art::dex::TypeIndex::Invalid()) << "Invalid type index";
+  const char* descriptor = dex_file_->StringByTypeIdx(def.class_idx_);
+  DCHECK(descriptor != nullptr) << "Invalid dex file structure!";
+  if (!current_class->DescriptorEquals(descriptor)) {
+    std::string storage;
+    RecordFailure(ERR(NAMES_DONT_MATCH),
+                  StringPrintf("expected file to contain class called '%s' but found '%s'!",
+                               current_class->GetDescriptor(&storage),
+                               descriptor));
+    return false;
+  }
+  if (current_class->IsObjectClass()) {
+    if (def.superclass_idx_ != art::dex::TypeIndex::Invalid()) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Superclass added!");
+      return false;
+    }
+  } else {
+    const char* super_descriptor = dex_file_->StringByTypeIdx(def.superclass_idx_);
+    DCHECK(descriptor != nullptr) << "Invalid dex file structure!";
+    if (!current_class->GetSuperClass()->DescriptorEquals(super_descriptor)) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Superclass changed");
+      return false;
+    }
+  }
+  const art::DexFile::TypeList* interfaces = dex_file_->GetInterfacesList(def);
+  if (interfaces == nullptr) {
+    if (current_class->NumDirectInterfaces() != 0) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added");
+      return false;
+    }
+  } else {
+    DCHECK(!current_class->IsProxyClass());
+    const art::DexFile::TypeList* current_interfaces = current_class->GetInterfaceTypeList();
+    if (current_interfaces == nullptr || current_interfaces->Size() != interfaces->Size()) {
+      RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED), "Interfaces added or removed");
+      return false;
+    }
+    // The order of interfaces is (barely) meaningful so we error if it changes.
+    const art::DexFile& orig_dex_file = current_class->GetDexFile();
+    for (uint32_t i = 0; i < interfaces->Size(); i++) {
+      if (strcmp(
+            dex_file_->StringByTypeIdx(interfaces->GetTypeItem(i).type_idx_),
+            orig_dex_file.StringByTypeIdx(current_interfaces->GetTypeItem(i).type_idx_)) != 0) {
+        RecordFailure(ERR(UNSUPPORTED_REDEFINITION_HIERARCHY_CHANGED),
+                      "Interfaces changed or re-ordered");
+        return false;
+      }
+    }
+  }
+  return true;
+}
+
+bool Redefiner::ClassRedefinition::CheckRedefinable() {
+  std::string err;
+  art::StackHandleScope<1> hs(driver_->self_);
+
+  art::Handle<art::mirror::Class> h_klass(hs.NewHandle(GetMirrorClass()));
+  jvmtiError res = Redefiner::GetClassRedefinitionError(h_klass, &err);
+  if (res != OK) {
+    RecordFailure(res, err);
+    return false;
+  } else {
+    return true;
+  }
+}
+
+bool Redefiner::ClassRedefinition::CheckRedefinitionIsValid() {
+  return CheckRedefinable() &&
+      CheckClass() &&
+      CheckSameFields() &&
+      CheckSameMethods();
+}
+
+class RedefinitionDataIter;
+
+// A wrapper that lets us hold onto the arbitrary sized data needed for redefinitions in a
+// reasonably sane way. This adds no fields to the normal ObjectArray. By doing this we can avoid
+// having to deal with the fact that we need to hold an arbitrary number of references live.
+class RedefinitionDataHolder {
+ public:
+  enum DataSlot : int32_t {
+    kSlotSourceClassLoader = 0,
+    kSlotJavaDexFile = 1,
+    kSlotNewDexFileCookie = 2,
+    kSlotNewDexCache = 3,
+    kSlotMirrorClass = 4,
+    kSlotOrigDexFile = 5,
+    kSlotOldObsoleteMethods = 6,
+    kSlotOldDexCaches = 7,
+
+    // Must be last one.
+    kNumSlots = 8,
+  };
+
+  // This needs to have a HandleScope passed in that is capable of creating a new Handle without
+  // overflowing. Only one handle will be created. This object has a lifetime identical to that of
+  // the passed in handle-scope.
+  RedefinitionDataHolder(art::StackHandleScope<1>* hs,
+                         art::Runtime* runtime,
+                         art::Thread* self,
+                         std::vector<Redefiner::ClassRedefinition>* redefinitions)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) :
+    arr_(
+      hs->NewHandle(
+        art::mirror::ObjectArray<art::mirror::Object>::Alloc(
+            self,
+            runtime->GetClassLinker()->GetClassRoot(art::ClassLinker::kObjectArrayClass),
+            redefinitions->size() * kNumSlots))),
+    redefinitions_(redefinitions) {}
+
+  bool IsNull() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return arr_.IsNull();
+  }
+
+  art::mirror::ClassLoader* GetSourceClassLoader(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::ClassLoader*>(GetSlot(klass_index, kSlotSourceClassLoader));
+  }
+  art::mirror::Object* GetJavaDexFile(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return GetSlot(klass_index, kSlotJavaDexFile);
+  }
+  art::mirror::LongArray* GetNewDexFileCookie(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::LongArray*>(GetSlot(klass_index, kSlotNewDexFileCookie));
+  }
+  art::mirror::DexCache* GetNewDexCache(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::DexCache*>(GetSlot(klass_index, kSlotNewDexCache));
+  }
+  art::mirror::Class* GetMirrorClass(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::Class*>(GetSlot(klass_index, kSlotMirrorClass));
+  }
+
+  art::mirror::Object* GetOriginalDexFile(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::Object*>(GetSlot(klass_index, kSlotOrigDexFile));
+  }
+
+  art::mirror::PointerArray* GetOldObsoleteMethods(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::PointerArray*>(
+        GetSlot(klass_index, kSlotOldObsoleteMethods));
+  }
+
+  art::mirror::ObjectArray<art::mirror::DexCache>* GetOldDexCaches(jint klass_index) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return art::down_cast<art::mirror::ObjectArray<art::mirror::DexCache>*>(
+        GetSlot(klass_index, kSlotOldDexCaches));
+  }
+
+  void SetSourceClassLoader(jint klass_index, art::mirror::ClassLoader* loader)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotSourceClassLoader, loader);
+  }
+  void SetJavaDexFile(jint klass_index, art::mirror::Object* dexfile)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotJavaDexFile, dexfile);
+  }
+  void SetNewDexFileCookie(jint klass_index, art::mirror::LongArray* cookie)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewDexFileCookie, cookie);
+  }
+  void SetNewDexCache(jint klass_index, art::mirror::DexCache* cache)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotNewDexCache, cache);
+  }
+  void SetMirrorClass(jint klass_index, art::mirror::Class* klass)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotMirrorClass, klass);
+  }
+  void SetOriginalDexFile(jint klass_index, art::mirror::Object* bytes)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotOrigDexFile, bytes);
+  }
+  void SetOldObsoleteMethods(jint klass_index, art::mirror::PointerArray* methods)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotOldObsoleteMethods, methods);
+  }
+  void SetOldDexCaches(jint klass_index, art::mirror::ObjectArray<art::mirror::DexCache>* caches)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    SetSlot(klass_index, kSlotOldDexCaches, caches);
+  }
+
+  int32_t Length() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return arr_->GetLength() / kNumSlots;
+  }
+
+  std::vector<Redefiner::ClassRedefinition>* GetRedefinitions()
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return redefinitions_;
+  }
+
+  bool operator==(const RedefinitionDataHolder& other) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return arr_.Get() == other.arr_.Get();
+  }
+
+  bool operator!=(const RedefinitionDataHolder& other) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return !(*this == other);
+  }
+
+  RedefinitionDataIter begin() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  RedefinitionDataIter end() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+  mutable art::Handle<art::mirror::ObjectArray<art::mirror::Object>> arr_;
+  std::vector<Redefiner::ClassRedefinition>* redefinitions_;
+
+  art::mirror::Object* GetSlot(jint klass_index,
+                               DataSlot slot) const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK_LT(klass_index, Length());
+    return arr_->Get((kNumSlots * klass_index) + slot);
+  }
+
+  void SetSlot(jint klass_index,
+               DataSlot slot,
+               art::ObjPtr<art::mirror::Object> obj) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK(!art::Runtime::Current()->IsActiveTransaction());
+    DCHECK_LT(klass_index, Length());
+    arr_->Set<false>((kNumSlots * klass_index) + slot, obj);
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(RedefinitionDataHolder);
+};
+
+class RedefinitionDataIter {
+ public:
+  RedefinitionDataIter(int32_t idx, RedefinitionDataHolder& holder) : idx_(idx), holder_(holder) {}
+
+  RedefinitionDataIter(const RedefinitionDataIter&) = default;
+  RedefinitionDataIter(RedefinitionDataIter&&) = default;
+  RedefinitionDataIter& operator=(const RedefinitionDataIter&) = default;
+  RedefinitionDataIter& operator=(RedefinitionDataIter&&) = default;
+
+  bool operator==(const RedefinitionDataIter& other) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return idx_ == other.idx_ && holder_ == other.holder_;
+  }
+
+  bool operator!=(const RedefinitionDataIter& other) const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return !(*this == other);
+  }
+
+  RedefinitionDataIter operator++() {  // Value after modification.
+    idx_++;
+    return *this;
+  }
+
+  RedefinitionDataIter operator++(int) {
+    RedefinitionDataIter temp = *this;
+    idx_++;
+    return temp;
+  }
+
+  RedefinitionDataIter operator+(ssize_t delta) const {
+    RedefinitionDataIter temp = *this;
+    temp += delta;
+    return temp;
+  }
+
+  RedefinitionDataIter& operator+=(ssize_t delta) {
+    idx_ += delta;
+    return *this;
+  }
+
+  Redefiner::ClassRedefinition& GetRedefinition() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return (*holder_.GetRedefinitions())[idx_];
+  }
+
+  RedefinitionDataHolder& GetHolder() {
+    return holder_;
+  }
+
+  art::mirror::ClassLoader* GetSourceClassLoader() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetSourceClassLoader(idx_);
+  }
+  art::mirror::Object* GetJavaDexFile() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetJavaDexFile(idx_);
+  }
+  art::mirror::LongArray* GetNewDexFileCookie() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetNewDexFileCookie(idx_);
+  }
+  art::mirror::DexCache* GetNewDexCache() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetNewDexCache(idx_);
+  }
+  art::mirror::Class* GetMirrorClass() const REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetMirrorClass(idx_);
+  }
+  art::mirror::Object* GetOriginalDexFile() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetOriginalDexFile(idx_);
+  }
+  art::mirror::PointerArray* GetOldObsoleteMethods() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetOldObsoleteMethods(idx_);
+  }
+  art::mirror::ObjectArray<art::mirror::DexCache>* GetOldDexCaches() const
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    return holder_.GetOldDexCaches(idx_);
+  }
+
+  int32_t GetIndex() const {
+    return idx_;
+  }
+
+  void SetSourceClassLoader(art::mirror::ClassLoader* loader)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetSourceClassLoader(idx_, loader);
+  }
+  void SetJavaDexFile(art::mirror::Object* dexfile) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetJavaDexFile(idx_, dexfile);
+  }
+  void SetNewDexFileCookie(art::mirror::LongArray* cookie)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetNewDexFileCookie(idx_, cookie);
+  }
+  void SetNewDexCache(art::mirror::DexCache* cache) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetNewDexCache(idx_, cache);
+  }
+  void SetMirrorClass(art::mirror::Class* klass) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetMirrorClass(idx_, klass);
+  }
+  void SetOriginalDexFile(art::mirror::Object* bytes)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetOriginalDexFile(idx_, bytes);
+  }
+  void SetOldObsoleteMethods(art::mirror::PointerArray* methods)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetOldObsoleteMethods(idx_, methods);
+  }
+  void SetOldDexCaches(art::mirror::ObjectArray<art::mirror::DexCache>* caches)
+      REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    holder_.SetOldDexCaches(idx_, caches);
+  }
+
+ private:
+  int32_t idx_;
+  RedefinitionDataHolder& holder_;
+};
+
+RedefinitionDataIter RedefinitionDataHolder::begin() {
+  return RedefinitionDataIter(0, *this);
+}
+
+RedefinitionDataIter RedefinitionDataHolder::end() {
+  return RedefinitionDataIter(Length(), *this);
+}
+
+bool Redefiner::ClassRedefinition::CheckVerification(const RedefinitionDataIter& iter) {
+  DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+  art::StackHandleScope<2> hs(driver_->self_);
+  std::string error;
+  // TODO Make verification log level lower
+  art::verifier::FailureKind failure =
+      art::verifier::MethodVerifier::VerifyClass(driver_->self_,
+                                                 dex_file_.get(),
+                                                 hs.NewHandle(iter.GetNewDexCache()),
+                                                 hs.NewHandle(GetClassLoader()),
+                                                 dex_file_->GetClassDef(0), /*class_def*/
+                                                 nullptr, /*compiler_callbacks*/
+                                                 false, /*allow_soft_failures*/
+                                                 /*log_level*/
+                                                 art::verifier::HardFailLogMode::kLogWarning,
+                                                 &error);
+  bool passes = failure == art::verifier::FailureKind::kNoFailure;
+  if (!passes) {
+    RecordFailure(ERR(FAILS_VERIFICATION), "Failed to verify class. Error was: " + error);
+  }
+  return passes;
+}
+
+// Looks through the previously allocated cookies to see if we need to update them with another new
+// dexfile. This is so that even if multiple classes with the same classloader are redefined at
+// once they are all added to the classloader.
+bool Redefiner::ClassRedefinition::AllocateAndRememberNewDexFileCookie(
+    art::Handle<art::mirror::ClassLoader> source_class_loader,
+    art::Handle<art::mirror::Object> dex_file_obj,
+    /*out*/RedefinitionDataIter* cur_data) {
+  art::StackHandleScope<2> hs(driver_->self_);
+  art::MutableHandle<art::mirror::LongArray> old_cookie(
+      hs.NewHandle<art::mirror::LongArray>(nullptr));
+  bool has_older_cookie = false;
+  // See if we already have a cookie that a previous redefinition got from the same classloader.
+  for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
+    if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+      // Since every instance of this classloader should have the same cookie associated with it we
+      // can stop looking here.
+      has_older_cookie = true;
+      old_cookie.Assign(old_data.GetNewDexFileCookie());
+      break;
+    }
+  }
+  if (old_cookie.IsNull()) {
+    // No older cookie. Get it directly from the dex_file_obj
+    // We should not have seen this classloader elsewhere.
+    CHECK(!has_older_cookie);
+    old_cookie.Assign(ClassLoaderHelper::GetDexFileCookie(dex_file_obj));
+  }
+  // Use the old cookie to generate the new one with the new DexFile* added in.
+  art::Handle<art::mirror::LongArray>
+      new_cookie(hs.NewHandle(ClassLoaderHelper::AllocateNewDexFileCookie(driver_->self_,
+                                                                          old_cookie,
+                                                                          dex_file_.get())));
+  // Make sure the allocation worked.
+  if (new_cookie.IsNull()) {
+    return false;
+  }
+
+  // Save the cookie.
+  cur_data->SetNewDexFileCookie(new_cookie.Get());
+  // If there are other copies of this same classloader we need to make sure that we all have the
+  // same cookie.
+  if (has_older_cookie) {
+    for (auto old_data = cur_data->GetHolder().begin(); old_data != *cur_data; ++old_data) {
+      // We will let the GC take care of the cookie we allocated for this one.
+      if (old_data.GetSourceClassLoader() == source_class_loader.Get()) {
+        old_data.SetNewDexFileCookie(new_cookie.Get());
+      }
+    }
+  }
+
+  return true;
+}
+
+bool Redefiner::ClassRedefinition::FinishRemainingAllocations(
+    /*out*/RedefinitionDataIter* cur_data) {
+  art::ScopedObjectAccessUnchecked soa(driver_->self_);
+  art::StackHandleScope<2> hs(driver_->self_);
+  cur_data->SetMirrorClass(GetMirrorClass());
+  // This shouldn't allocate
+  art::Handle<art::mirror::ClassLoader> loader(hs.NewHandle(GetClassLoader()));
+  // The bootclasspath is handled specially so it doesn't have a j.l.DexFile.
+  if (!art::ClassLinker::IsBootClassLoader(soa, loader.Get())) {
+    cur_data->SetSourceClassLoader(loader.Get());
+    art::Handle<art::mirror::Object> dex_file_obj(hs.NewHandle(
+        ClassLoaderHelper::FindSourceDexFileObject(driver_->self_, loader)));
+    cur_data->SetJavaDexFile(dex_file_obj.Get());
+    if (dex_file_obj == nullptr) {
+      RecordFailure(ERR(INTERNAL), "Unable to find dex file!");
+      return false;
+    }
+    // Allocate the new dex file cookie.
+    if (!AllocateAndRememberNewDexFileCookie(loader, dex_file_obj, cur_data)) {
+      driver_->self_->AssertPendingOOMException();
+      driver_->self_->ClearException();
+      RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate dex file array for class loader");
+      return false;
+    }
+  }
+  cur_data->SetNewDexCache(CreateNewDexCache(loader));
+  if (cur_data->GetNewDexCache() == nullptr) {
+    driver_->self_->AssertPendingException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate DexCache");
+    return false;
+  }
+
+  // We won't always need to set this field.
+  cur_data->SetOriginalDexFile(AllocateOrGetOriginalDexFile());
+  if (cur_data->GetOriginalDexFile() == nullptr) {
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate array for original dex file");
+    return false;
+  }
+  return true;
+}
+
+void Redefiner::ClassRedefinition::UnregisterJvmtiBreakpoints() {
+  BreakpointUtil::RemoveBreakpointsInClass(driver_->env_, GetMirrorClass());
+}
+
+void Redefiner::ClassRedefinition::UnregisterBreakpoints() {
+  DCHECK(art::Dbg::IsDebuggerActive());
+  art::JDWP::JdwpState* state = art::Dbg::GetJdwpState();
+  if (state != nullptr) {
+    state->UnregisterLocationEventsOnClass(GetMirrorClass());
+  }
+}
+
+void Redefiner::UnregisterAllBreakpoints() {
+  if (LIKELY(!art::Dbg::IsDebuggerActive())) {
+    return;
+  }
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    redef.UnregisterBreakpoints();
+  }
+}
+
+bool Redefiner::CheckAllRedefinitionAreValid() {
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    if (!redef.CheckRedefinitionIsValid()) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void Redefiner::RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder) {
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    data.GetRedefinition().RestoreObsoleteMethodMapsIfUnneeded(&data);
+  }
+}
+
+bool Redefiner::EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder) {
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    if (!data.GetRedefinition().EnsureClassAllocationsFinished(&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.
+    if (!data.GetRedefinition().FinishRemainingAllocations(&data)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+void Redefiner::ClassRedefinition::ReleaseDexFile() {
+  dex_file_.release();
+}
+
+void Redefiner::ReleaseAllDexFiles() {
+  for (Redefiner::ClassRedefinition& redef : redefinitions_) {
+    redef.ReleaseDexFile();
+  }
+}
+
+bool Redefiner::CheckAllClassesAreVerified(RedefinitionDataHolder& holder) {
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    if (!data.GetRedefinition().CheckVerification(data)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+class ScopedDisableConcurrentAndMovingGc {
+ public:
+  ScopedDisableConcurrentAndMovingGc(art::gc::Heap* heap, art::Thread* self)
+      : heap_(heap), self_(self) {
+    if (heap_->IsGcConcurrentAndMoving()) {
+      heap_->IncrementDisableMovingGC(self_);
+    }
+  }
+
+  ~ScopedDisableConcurrentAndMovingGc() {
+    if (heap_->IsGcConcurrentAndMoving()) {
+      heap_->DecrementDisableMovingGC(self_);
+    }
+  }
+ private:
+  art::gc::Heap* heap_;
+  art::Thread* self_;
+};
+
+jvmtiError Redefiner::Run() {
+  art::StackHandleScope<1> hs(self_);
+  // Allocate an array to hold onto all java temporary objects associated with this redefinition.
+  // We will let this be collected after the end of this function.
+  RedefinitionDataHolder holder(&hs, runtime_, self_, &redefinitions_);
+  if (holder.IsNull()) {
+    self_->AssertPendingOOMException();
+    self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate storage for temporaries");
+    return result_;
+  }
+
+  // First we just allocate the ClassExt and its fields that we need. These can be updated
+  // atomically without any issues (since we allocate the map arrays as empty) so we don't bother
+  // doing a try loop. The other allocations we need to ensure that nothing has changed in the time
+  // between allocating them and pausing all threads before we can update them so we need to do a
+  // try loop.
+  if (!CheckAllRedefinitionAreValid() ||
+      !EnsureAllClassAllocationsFinished(holder) ||
+      !FinishAllRemainingAllocations(holder) ||
+      !CheckAllClassesAreVerified(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();
+    cl->RegisterExistingDexCache(data.GetNewDexCache(), data.GetSourceClassLoader());
+    if (data.GetSourceClassLoader() == nullptr) {
+      cl->AppendToBootClassPath(self_, data.GetRedefinition().GetDexFile());
+    }
+  }
+  UnregisterAllBreakpoints();
+
+  // Disable GC and wait for it to be done if we are a moving GC.  This is fine since we are done
+  // allocating so no deadlocks.
+  ScopedDisableConcurrentAndMovingGc sdcamgc(runtime_->GetHeap(), self_);
+
+  // Do transition to final suspension
+  // TODO We might want to give this its own suspended state!
+  // TODO This isn't right. We need to change state without any chance of suspend ideally!
+  art::ScopedThreadSuspension sts(self_, art::ThreadState::kNative);
+  art::ScopedSuspendAll ssa("Final installation of redefined Classes!", /*long_suspend*/true);
+  for (RedefinitionDataIter data = holder.begin(); data != holder.end(); ++data) {
+    art::ScopedAssertNoThreadSuspension nts("Updating runtime objects for redefinition");
+    ClassRedefinition& redef = data.GetRedefinition();
+    if (data.GetSourceClassLoader() != nullptr) {
+      ClassLoaderHelper::UpdateJavaDexFile(data.GetJavaDexFile(), data.GetNewDexFileCookie());
+    }
+    art::mirror::Class* klass = data.GetMirrorClass();
+    // TODO Rewrite so we don't do a stack walk for each and every class.
+    redef.FindAndAllocateObsoleteMethods(klass);
+    redef.UpdateClass(klass, data.GetNewDexCache(), data.GetOriginalDexFile());
+    redef.UnregisterJvmtiBreakpoints();
+  }
+  RestoreObsoleteMethodMapsIfUnneeded(holder);
+  // TODO We should check for if any of the redefined methods are intrinsic methods here and, if any
+  // are, force a full-world deoptimization before finishing redefinition. If we don't do this then
+  // methods that have been jitted prior to the current redefinition being applied might continue
+  // to use the old versions of the intrinsics!
+  // TODO Do the dex_file release at a more reasonable place. This works but it muddles who really
+  // owns the DexFile and when ownership is transferred.
+  ReleaseAllDexFiles();
+  return OK;
+}
+
+void Redefiner::ClassRedefinition::UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
+                                                 art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+                                                 const art::DexFile::ClassDef& class_def) {
+  art::ClassLinker* linker = driver_->runtime_->GetClassLinker();
+  art::PointerSize image_pointer_size = linker->GetImagePointerSize();
+  const art::DexFile::TypeId& declaring_class_id = dex_file_->GetTypeId(class_def.class_idx_);
+  const art::DexFile& old_dex_file = mclass->GetDexFile();
+  // Update methods.
+  for (art::ArtMethod& method : mclass->GetDeclaredMethods(image_pointer_size)) {
+    const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(method.GetName());
+    art::dex::TypeIndex method_return_idx =
+        dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(method.GetReturnTypeDescriptor()));
+    const auto* old_type_list = method.GetParameterTypeList();
+    std::vector<art::dex::TypeIndex> new_type_list;
+    for (uint32_t i = 0; old_type_list != nullptr && i < old_type_list->Size(); i++) {
+      new_type_list.push_back(
+          dex_file_->GetIndexForTypeId(
+              *dex_file_->FindTypeId(
+                  old_dex_file.GetTypeDescriptor(
+                      old_dex_file.GetTypeId(
+                          old_type_list->GetTypeItem(i).type_idx_)))));
+    }
+    const art::DexFile::ProtoId* proto_id = dex_file_->FindProtoId(method_return_idx,
+                                                                   new_type_list);
+    CHECK(proto_id != nullptr || old_type_list == nullptr);
+    const art::DexFile::MethodId* method_id = dex_file_->FindMethodId(declaring_class_id,
+                                                                      *new_name_id,
+                                                                      *proto_id);
+    CHECK(method_id != nullptr);
+    uint32_t dex_method_idx = dex_file_->GetIndexForMethodId(*method_id);
+    method.SetDexMethodIndex(dex_method_idx);
+    linker->SetEntryPointsToInterpreter(&method);
+    method.SetCodeItemOffset(dex_file_->FindCodeItemOffset(class_def, dex_method_idx));
+    method.SetDexCacheResolvedMethods(new_dex_cache->GetResolvedMethods(), image_pointer_size);
+    // Clear all the intrinsics related flags.
+    method.ClearAccessFlags(art::kAccIntrinsic | (~art::kAccFlagsNotUsedByIntrinsic));
+    // Notify the jit that this method is redefined.
+    art::jit::Jit* jit = driver_->runtime_->GetJit();
+    if (jit != nullptr) {
+      jit->GetCodeCache()->NotifyMethodRedefined(&method);
+    }
+  }
+}
+
+void Redefiner::ClassRedefinition::UpdateFields(art::ObjPtr<art::mirror::Class> mclass) {
+  // TODO The IFields & SFields pointers should be combined like the methods_ arrays were.
+  for (auto fields_iter : {mclass->GetIFields(), mclass->GetSFields()}) {
+    for (art::ArtField& field : fields_iter) {
+      std::string declaring_class_name;
+      const art::DexFile::TypeId* new_declaring_id =
+          dex_file_->FindTypeId(field.GetDeclaringClass()->GetDescriptor(&declaring_class_name));
+      const art::DexFile::StringId* new_name_id = dex_file_->FindStringId(field.GetName());
+      const art::DexFile::TypeId* new_type_id = dex_file_->FindTypeId(field.GetTypeDescriptor());
+      CHECK(new_name_id != nullptr && new_type_id != nullptr && new_declaring_id != nullptr);
+      const art::DexFile::FieldId* new_field_id =
+          dex_file_->FindFieldId(*new_declaring_id, *new_name_id, *new_type_id);
+      CHECK(new_field_id != nullptr);
+      // We only need to update the index since the other data in the ArtField cannot be updated.
+      field.SetDexFieldIndex(dex_file_->GetIndexForFieldId(*new_field_id));
+    }
+  }
+}
+
+// Performs updates to class that will allow us to verify it.
+void Redefiner::ClassRedefinition::UpdateClass(
+    art::ObjPtr<art::mirror::Class> mclass,
+    art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+    art::ObjPtr<art::mirror::Object> original_dex_file) {
+  DCHECK_EQ(dex_file_->NumClassDefs(), 1u);
+  const art::DexFile::ClassDef& class_def = dex_file_->GetClassDef(0);
+  UpdateMethods(mclass, new_dex_cache, class_def);
+  UpdateFields(mclass);
+
+  // Update the class fields.
+  // Need to update class last since the ArtMethod gets its DexFile from the class (which is needed
+  // to call GetReturnTypeDescriptor and GetParameterTypeList above).
+  mclass->SetDexCache(new_dex_cache.Ptr());
+  mclass->SetDexClassDefIndex(dex_file_->GetIndexForClassDef(class_def));
+  mclass->SetDexTypeIndex(dex_file_->GetIndexForTypeId(*dex_file_->FindTypeId(class_sig_.c_str())));
+  art::ObjPtr<art::mirror::ClassExt> ext(mclass->GetExtData());
+  CHECK(!ext.IsNull());
+  ext->SetOriginalDexFile(original_dex_file);
+}
+
+// Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no new
+// obsolete methods).
+void Redefiner::ClassRedefinition::RestoreObsoleteMethodMapsIfUnneeded(
+    const RedefinitionDataIter* cur_data) {
+  art::mirror::Class* klass = GetMirrorClass();
+  art::mirror::ClassExt* ext = klass->GetExtData();
+  art::mirror::PointerArray* methods = ext->GetObsoleteMethods();
+  art::mirror::PointerArray* old_methods = cur_data->GetOldObsoleteMethods();
+  int32_t old_length = old_methods == nullptr ? 0 : old_methods->GetLength();
+  int32_t expected_length =
+      old_length + klass->NumDirectMethods() + klass->NumDeclaredVirtualMethods();
+  // Check to make sure we are only undoing this one.
+  if (expected_length == methods->GetLength()) {
+    for (int32_t i = 0; i < expected_length; i++) {
+      art::ArtMethod* expected = nullptr;
+      if (i < old_length) {
+        expected = old_methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize);
+      }
+      if (methods->GetElementPtrSize<art::ArtMethod*>(i, art::kRuntimePointerSize) != expected) {
+        // We actually have some new obsolete methods. Just abort since we cannot safely shrink the
+        // obsolete methods array.
+        return;
+      }
+    }
+    // No new obsolete methods! We can get rid of the maps.
+    ext->SetObsoleteArrays(cur_data->GetOldObsoleteMethods(), cur_data->GetOldDexCaches());
+  }
+}
+
+// This function does all (java) allocations we need to do for the Class being redefined.
+// TODO Change this name maybe?
+bool Redefiner::ClassRedefinition::EnsureClassAllocationsFinished(
+    /*out*/RedefinitionDataIter* cur_data) {
+  art::StackHandleScope<2> hs(driver_->self_);
+  art::Handle<art::mirror::Class> klass(hs.NewHandle(
+      driver_->self_->DecodeJObject(klass_)->AsClass()));
+  if (klass == nullptr) {
+    RecordFailure(ERR(INVALID_CLASS), "Unable to decode class argument!");
+    return false;
+  }
+  // Allocate the classExt
+  art::Handle<art::mirror::ClassExt> ext(hs.NewHandle(klass->EnsureExtDataPresent(driver_->self_)));
+  if (ext == nullptr) {
+    // No memory. Clear exception (it's not useful) and return error.
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Could not allocate ClassExt");
+    return false;
+  }
+  // First save the old values of the 2 arrays that make up the obsolete methods maps.  Then
+  // allocate the 2 arrays that make up the obsolete methods map.  Since the contents of the arrays
+  // are only modified when all threads (other than the modifying one) are suspended we don't need
+  // to worry about missing the unsyncronized writes to the array. We do synchronize when setting it
+  // however, since that can happen at any time.
+  cur_data->SetOldObsoleteMethods(ext->GetObsoleteMethods());
+  cur_data->SetOldDexCaches(ext->GetObsoleteDexCaches());
+  if (!ext->ExtendObsoleteArrays(
+        driver_->self_, klass->GetDeclaredMethodsSlice(art::kRuntimePointerSize).size())) {
+    // OOM. Clear exception and return error.
+    driver_->self_->AssertPendingOOMException();
+    driver_->self_->ClearException();
+    RecordFailure(ERR(OUT_OF_MEMORY), "Unable to allocate/extend obsolete methods map");
+    return false;
+  }
+  return true;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_redefine.h b/openjdkjvmti/ti_redefine.h
new file mode 100644
index 0000000..984f922
--- /dev/null
+++ b/openjdkjvmti/ti_redefine.h
@@ -0,0 +1,268 @@
+/* Copyright (C) 2016 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_TI_REDEFINE_H_
+#define ART_OPENJDKJVMTI_TI_REDEFINE_H_
+
+#include <string>
+
+#include <jni.h>
+
+#include "art_jvmti.h"
+#include "art_method.h"
+#include "base/array_ref.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jni_env_ext-inl.h"
+#include "jvmti.h"
+#include "linear_alloc.h"
+#include "mem_map.h"
+#include "mirror/array-inl.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "obj_ptr.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread_list.h"
+#include "ti_class_definition.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+class RedefinitionDataHolder;
+class RedefinitionDataIter;
+
+// Class that can redefine a single class's methods.
+class Redefiner {
+ public:
+  // Redefine the given classes with the given dex data. Note this function does not take ownership
+  // of the dex_data pointers. It is not used after this call however and may be freed if desired.
+  // The caller is responsible for freeing it. The runtime makes its own copy of the data. This
+  // function does not call the transformation events.
+  static jvmtiError RedefineClassesDirect(ArtJvmTiEnv* env,
+                                          art::Runtime* runtime,
+                                          art::Thread* self,
+                                          const std::vector<ArtClassDefinition>& definitions,
+                                          /*out*/std::string* error_msg);
+
+  // Redefine the given classes with the given dex data. Note this function does not take ownership
+  // of the dex_data pointers. It is not used after this call however and may be freed if desired.
+  // The caller is responsible for freeing it. The runtime makes its own copy of the data.
+  static jvmtiError RedefineClasses(ArtJvmTiEnv* env,
+                                    EventHandler* event_handler,
+                                    art::Runtime* runtime,
+                                    art::Thread* self,
+                                    jint class_count,
+                                    const jvmtiClassDefinition* definitions,
+                                    /*out*/std::string* error_msg);
+
+  static jvmtiError IsModifiableClass(jvmtiEnv* env, jclass klass, jboolean* is_redefinable);
+
+  static std::unique_ptr<art::MemMap> MoveDataToMemMap(const std::string& original_location,
+                                                       art::ArrayRef<const unsigned char> data,
+                                                       std::string* error_msg);
+
+ private:
+  class ClassRedefinition {
+   public:
+    ClassRedefinition(Redefiner* driver,
+                      jclass klass,
+                      const art::DexFile* redefined_dex_file,
+                      const char* class_sig,
+                      art::ArrayRef<const unsigned char> orig_dex_file)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // NO_THREAD_SAFETY_ANALYSIS so we can unlock the class in the destructor.
+    ~ClassRedefinition() NO_THREAD_SAFETY_ANALYSIS;
+
+    // Move constructor so we can put these into a vector.
+    ClassRedefinition(ClassRedefinition&& other)
+        : driver_(other.driver_),
+          klass_(other.klass_),
+          dex_file_(std::move(other.dex_file_)),
+          class_sig_(std::move(other.class_sig_)),
+          original_dex_file_(other.original_dex_file_) {
+      other.driver_ = nullptr;
+    }
+
+    art::mirror::Class* GetMirrorClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
+    art::mirror::ClassLoader* GetClassLoader() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    const art::DexFile& GetDexFile() {
+      return *dex_file_;
+    }
+
+    art::mirror::DexCache* CreateNewDexCache(art::Handle<art::mirror::ClassLoader> loader)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // This may return nullptr with a OOME pending if allocation fails.
+    art::mirror::Object* AllocateOrGetOriginalDexFile()
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    void RecordFailure(jvmtiError e, const std::string& err) {
+      driver_->RecordFailure(e, class_sig_, err);
+    }
+
+    bool FinishRemainingAllocations(/*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,
+        /*out*/RedefinitionDataIter* cur_data)
+          REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    void FindAndAllocateObsoleteMethods(art::mirror::Class* art_klass)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    // Checks that the dex file contains only the single expected class and that the top-level class
+    // data has not been modified in an incompatible manner.
+    bool CheckClass() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Checks that the contained class can be successfully verified.
+    bool CheckVerification(const RedefinitionDataIter& holder)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Preallocates all needed allocations in klass so that we can pause execution safely.
+    bool EnsureClassAllocationsFinished(/*out*/RedefinitionDataIter* data)
+        REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // This will check that no constraints are violated (more than 1 class in dex file, any changes
+    // in number/declaration of methods & fields, changes in access flags, etc.)
+    bool CheckRedefinitionIsValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Checks that the class can even be redefined.
+    bool CheckRedefinable() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Checks that the dex file does not add/remove methods, or change their modifiers or types.
+    bool CheckSameMethods() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    // Checks that the dex file does not modify fields types or modifiers.
+    bool CheckSameFields() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    void UpdateJavaDexFile(art::ObjPtr<art::mirror::Object> java_dex_file,
+                           art::ObjPtr<art::mirror::LongArray> new_cookie)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateFields(art::ObjPtr<art::mirror::Class> mclass)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateMethods(art::ObjPtr<art::mirror::Class> mclass,
+                       art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+                       const art::DexFile::ClassDef& class_def)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void UpdateClass(art::ObjPtr<art::mirror::Class> mclass,
+                     art::ObjPtr<art::mirror::DexCache> new_dex_cache,
+                     art::ObjPtr<art::mirror::Object> original_dex_file)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void RestoreObsoleteMethodMapsIfUnneeded(const RedefinitionDataIter* cur_data)
+        REQUIRES(art::Locks::mutator_lock_);
+
+    void ReleaseDexFile() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+    void UnregisterBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
+    // This should be done with all threads suspended.
+    void UnregisterJvmtiBreakpoints() REQUIRES(art::Locks::mutator_lock_);
+
+   private:
+    Redefiner* driver_;
+    jclass klass_;
+    std::unique_ptr<const art::DexFile> dex_file_;
+    std::string class_sig_;
+    art::ArrayRef<const unsigned char> original_dex_file_;
+  };
+
+  ArtJvmTiEnv* env_;
+  jvmtiError result_;
+  art::Runtime* runtime_;
+  art::Thread* self_;
+  std::vector<ClassRedefinition> redefinitions_;
+  // Kept as a jclass since we have weird run-state changes that make keeping it around as a
+  // mirror::Class difficult and confusing.
+  std::string* error_msg_;
+
+  Redefiner(ArtJvmTiEnv* env,
+            art::Runtime* runtime,
+            art::Thread* self,
+            std::string* error_msg)
+      : env_(env),
+        result_(ERR(INTERNAL)),
+        runtime_(runtime),
+        self_(self),
+        redefinitions_(),
+        error_msg_(error_msg) { }
+
+  jvmtiError AddRedefinition(ArtJvmTiEnv* env, const ArtClassDefinition& def)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static jvmtiError GetClassRedefinitionError(art::Handle<art::mirror::Class> klass,
+                                              /*out*/std::string* error_msg)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  jvmtiError Run() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  bool CheckAllRedefinitionAreValid() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool CheckAllClassesAreVerified(RedefinitionDataHolder& holder)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool EnsureAllClassAllocationsFinished(RedefinitionDataHolder& holder)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+  bool FinishAllRemainingAllocations(RedefinitionDataHolder& holder)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void ReleaseAllDexFiles() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void UnregisterAllBreakpoints() REQUIRES_SHARED(art::Locks::mutator_lock_);
+  // Restores the old obsolete methods maps if it turns out they weren't needed (ie there were no
+  // new obsolete methods).
+  void RestoreObsoleteMethodMapsIfUnneeded(RedefinitionDataHolder& holder)
+      REQUIRES(art::Locks::mutator_lock_);
+
+  void RecordFailure(jvmtiError result, const std::string& class_sig, const std::string& error_msg);
+  void RecordFailure(jvmtiError result, const std::string& error_msg) {
+    RecordFailure(result, "NO CLASS", error_msg);
+  }
+
+  friend struct CallbackCtx;
+  friend class RedefinitionDataHolder;
+  friend class RedefinitionDataIter;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_REDEFINE_H_
diff --git a/openjdkjvmti/ti_search.cc b/openjdkjvmti/ti_search.cc
new file mode 100644
index 0000000..25bc5d6
--- /dev/null
+++ b/openjdkjvmti/ti_search.cc
@@ -0,0 +1,296 @@
+/* Copyright (C) 2017 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 "ti_search.h"
+
+#include "jni.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/enums.h"
+#include "base/macros.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object.h"
+#include "mirror/string.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "obj_ptr-inl.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+static std::vector<std::string> gSystemOnloadSegments;
+
+static art::ObjPtr<art::mirror::Object> GetSystemProperties(art::Thread* self,
+                                                            art::ClassLinker* class_linker)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  art::ObjPtr<art::mirror::Class> system_class =
+      class_linker->LookupClass(self, "Ljava/lang/System;", nullptr);
+  DCHECK(system_class != nullptr);
+  DCHECK(system_class->IsInitialized());
+
+  art::ArtField* props_field =
+      system_class->FindDeclaredStaticField("props", "Ljava/util/Properties;");
+  DCHECK(props_field != nullptr);
+
+  art::ObjPtr<art::mirror::Object> props_obj = props_field->GetObject(system_class);
+  DCHECK(props_obj != nullptr);
+
+  return props_obj;
+}
+
+static void Update() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  if (gSystemOnloadSegments.empty()) {
+    return;
+  }
+
+  // In the on-load phase we have to modify java.class.path to influence the system classloader.
+  // As this is an unmodifiable system property, we have to access the "defaults" field.
+  art::ClassLinker* class_linker = art::Runtime::Current()->GetClassLinker();
+  DCHECK(class_linker != nullptr);
+  art::Thread* self = art::Thread::Current();
+
+  // Prepare: collect classes, fields and methods.
+  art::ObjPtr<art::mirror::Class> properties_class =
+      class_linker->LookupClass(self, "Ljava/util/Properties;", nullptr);
+  DCHECK(properties_class != nullptr);
+
+  ScopedLocalRef<jobject> defaults_jobj(self->GetJniEnv(), nullptr);
+  {
+    art::ObjPtr<art::mirror::Object> props_obj = GetSystemProperties(self, class_linker);
+
+    art::ArtField* defaults_field =
+        properties_class->FindDeclaredInstanceField("defaults", "Ljava/util/Properties;");
+    DCHECK(defaults_field != nullptr);
+
+    art::ObjPtr<art::mirror::Object> defaults_obj = defaults_field->GetObject(props_obj);
+    DCHECK(defaults_obj != nullptr);
+    defaults_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(defaults_obj));
+  }
+
+  art::ArtMethod* get_property =
+      properties_class->FindClassMethod(
+          "getProperty",
+          "(Ljava/lang/String;)Ljava/lang/String;",
+          art::kRuntimePointerSize);
+  DCHECK(get_property != nullptr);
+  DCHECK(!get_property->IsDirect());
+  DCHECK(get_property->GetDeclaringClass() == properties_class);
+  art::ArtMethod* set_property =
+      properties_class->FindClassMethod(
+          "setProperty",
+          "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;",
+          art::kRuntimePointerSize);
+  DCHECK(set_property != nullptr);
+  DCHECK(!set_property->IsDirect());
+  DCHECK(set_property->GetDeclaringClass() == properties_class);
+
+  // This is an allocation. Do this late to avoid the need for handles.
+  ScopedLocalRef<jobject> cp_jobj(self->GetJniEnv(), nullptr);
+  {
+    art::ObjPtr<art::mirror::Object> cp_key =
+        art::mirror::String::AllocFromModifiedUtf8(self, "java.class.path");
+    if (cp_key == nullptr) {
+      self->AssertPendingOOMException();
+      self->ClearException();
+      return;
+    }
+    cp_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(cp_key));
+  }
+
+  // OK, now get the current value.
+  std::string str_value;
+  {
+    ScopedLocalRef<jobject> old_value(self->GetJniEnv(),
+                                      self->GetJniEnv()->CallObjectMethod(
+                                          defaults_jobj.get(),
+                                          art::jni::EncodeArtMethod(get_property),
+                                          cp_jobj.get()));
+    DCHECK(old_value.get() != nullptr);
+
+    str_value = self->DecodeJObject(old_value.get())->AsString()->ToModifiedUtf8();
+    self->GetJniEnv()->DeleteLocalRef(old_value.release());
+  }
+
+  // Update the value by appending the new segments.
+  for (const std::string& segment : gSystemOnloadSegments) {
+    if (!str_value.empty()) {
+      str_value += ":";
+    }
+    str_value += segment;
+  }
+  gSystemOnloadSegments.clear();
+
+  // Create the new value object.
+  ScopedLocalRef<jobject> new_val_jobj(self->GetJniEnv(), nullptr);
+  {
+    art::ObjPtr<art::mirror::Object> new_value =
+        art::mirror::String::AllocFromModifiedUtf8(self, str_value.c_str());
+    if (new_value == nullptr) {
+      self->AssertPendingOOMException();
+      self->ClearException();
+      return;
+    }
+
+    new_val_jobj.reset(self->GetJniEnv()->AddLocalReference<jobject>(new_value));
+  }
+
+  // Write to the defaults.
+  ScopedLocalRef<jobject> res_obj(self->GetJniEnv(),
+                                  self->GetJniEnv()->CallObjectMethod(defaults_jobj.get(),
+                                      art::jni::EncodeArtMethod(set_property),
+                                      cp_jobj.get(),
+                                      new_val_jobj.get()));
+  if (self->IsExceptionPending()) {
+    self->ClearException();
+    return;
+  }
+}
+
+struct SearchCallback : public art::RuntimePhaseCallback {
+  void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (phase == RuntimePhase::kStart) {
+      // It's time to update the system properties.
+      Update();
+    }
+  }
+};
+
+static SearchCallback gSearchCallback;
+
+void SearchUtil::Register() {
+  art::Runtime* runtime = art::Runtime::Current();
+
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add search callback");
+  runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gSearchCallback);
+}
+
+void SearchUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove search callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gSearchCallback);
+}
+
+jvmtiError SearchUtil::AddToBootstrapClassLoaderSearch(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                                       const char* segment) {
+  art::Runtime* current = art::Runtime::Current();
+  if (current == nullptr) {
+    return ERR(WRONG_PHASE);
+  }
+  if (current->GetClassLinker() == nullptr) {
+    return ERR(WRONG_PHASE);
+  }
+  if (segment == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  std::string error_msg;
+  std::vector<std::unique_ptr<const art::DexFile>> dex_files;
+  if (!art::DexFile::Open(segment, segment, true, &error_msg, &dex_files)) {
+    LOG(WARNING) << "Could not open " << segment << " for boot classpath extension: " << error_msg;
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  for (std::unique_ptr<const art::DexFile>& dex_file : dex_files) {
+    current->GetClassLinker()->AppendToBootClassPath(art::Thread::Current(), *dex_file.release());
+  }
+
+  return ERR(NONE);
+}
+
+jvmtiError SearchUtil::AddToSystemClassLoaderSearch(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED,
+                                                    const char* segment) {
+  if (segment == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  jvmtiPhase phase = PhaseUtil::GetPhaseUnchecked();
+
+  if (phase == jvmtiPhase::JVMTI_PHASE_ONLOAD) {
+    // We could try and see whether it is a valid path. We could also try to allocate Java
+    // objects to avoid later OOME.
+    gSystemOnloadSegments.push_back(segment);
+    return ERR(NONE);
+  } else if (phase != jvmtiPhase::JVMTI_PHASE_LIVE) {
+    return ERR(WRONG_PHASE);
+  }
+
+  jobject sys_class_loader = art::Runtime::Current()->GetSystemClassLoader();
+  if (sys_class_loader == nullptr) {
+    // This is unexpected.
+    return ERR(INTERNAL);
+  }
+
+  // We'll use BaseDexClassLoader.addDexPath, as it takes care of array resizing etc. As a downside,
+  // exceptions are swallowed.
+
+  art::Thread* self = art::Thread::Current();
+  JNIEnv* env = self->GetJniEnv();
+  if (!env->IsInstanceOf(sys_class_loader,
+                         art::WellKnownClasses::dalvik_system_BaseDexClassLoader)) {
+    return ERR(INTERNAL);
+  }
+
+  jmethodID add_dex_path_id = env->GetMethodID(
+      art::WellKnownClasses::dalvik_system_BaseDexClassLoader,
+      "addDexPath",
+      "(Ljava/lang/String;)V");
+  if (add_dex_path_id == nullptr) {
+    return ERR(INTERNAL);
+  }
+
+  ScopedLocalRef<jstring> dex_path(env, env->NewStringUTF(segment));
+  if (dex_path.get() == nullptr) {
+    return ERR(INTERNAL);
+  }
+  env->CallVoidMethod(sys_class_loader, add_dex_path_id, dex_path.get());
+
+  if (env->ExceptionCheck()) {
+    env->ExceptionClear();
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_search.h b/openjdkjvmti/ti_search.h
new file mode 100644
index 0000000..81a28cc
--- /dev/null
+++ b/openjdkjvmti/ti_search.h
@@ -0,0 +1,53 @@
+/* Copyright (C) 2017 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_TI_SEARCH_H_
+#define ART_OPENJDKJVMTI_TI_SEARCH_H_
+
+#include <vector>
+
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class SearchUtil {
+ public:
+  static void Register();
+  static void Unregister();
+
+  static jvmtiError AddToBootstrapClassLoaderSearch(jvmtiEnv* env, const char* segment);
+
+  static jvmtiError AddToSystemClassLoaderSearch(jvmtiEnv* env, const char* segment);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_SEARCH_H_
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
new file mode 100644
index 0000000..ff2de8d
--- /dev/null
+++ b/openjdkjvmti/ti_stack.cc
@@ -0,0 +1,827 @@
+/* Copyright (C) 2016 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 "ti_stack.h"
+
+#include <algorithm>
+#include <list>
+#include <unordered_map>
+#include <vector>
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "barrier.h"
+#include "base/bit_utils.h"
+#include "base/enums.h"
+#include "base/mutex.h"
+#include "dex_file.h"
+#include "dex_file_annotations.h"
+#include "handle_scope-inl.h"
+#include "jni_env_ext.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/dex_cache.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "thread_pool.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+template <typename FrameFn>
+struct GetStackTraceVisitor : public art::StackVisitor {
+  GetStackTraceVisitor(art::Thread* thread_in,
+                       size_t start_,
+                       size_t stop_,
+                       FrameFn fn_)
+      : StackVisitor(thread_in, nullptr, StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        fn(fn_),
+        start(start_),
+        stop(stop_) {}
+  GetStackTraceVisitor(const GetStackTraceVisitor&) = default;
+  GetStackTraceVisitor(GetStackTraceVisitor&&) = default;
+
+  bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ArtMethod* m = GetMethod();
+    if (m->IsRuntimeMethod()) {
+      return true;
+    }
+
+    if (start == 0) {
+      m = m->GetInterfaceMethodIfProxy(art::kRuntimePointerSize);
+      jmethodID id = art::jni::EncodeArtMethod(m);
+
+      uint32_t dex_pc = GetDexPc(false);
+      jlong dex_location = (dex_pc == art::DexFile::kDexNoIndex) ? -1 : static_cast<jlong>(dex_pc);
+
+      jvmtiFrameInfo info = { id, dex_location };
+      fn(info);
+
+      if (stop == 1) {
+        return false;  // We're done.
+      } else if (stop > 0) {
+        stop--;
+      }
+    } else {
+      start--;
+    }
+
+    return true;
+  }
+
+  FrameFn fn;
+  size_t start;
+  size_t stop;
+};
+
+template <typename FrameFn>
+GetStackTraceVisitor<FrameFn> MakeStackTraceVisitor(art::Thread* thread_in,
+                                                    size_t start,
+                                                    size_t stop,
+                                                    FrameFn fn) {
+  return GetStackTraceVisitor<FrameFn>(thread_in, start, stop, fn);
+}
+
+struct GetStackTraceVectorClosure : public art::Closure {
+ public:
+  GetStackTraceVectorClosure(size_t start, size_t stop)
+      : start_input(start),
+        stop_input(stop),
+        start_result(0),
+        stop_result(0) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    auto frames_fn = [&](jvmtiFrameInfo info) {
+      frames.push_back(info);
+    };
+    auto visitor = MakeStackTraceVisitor(self, start_input, stop_input, frames_fn);
+    visitor.WalkStack(/* include_transitions */ false);
+
+    start_result = visitor.start;
+    stop_result = visitor.stop;
+  }
+
+  const size_t start_input;
+  const size_t stop_input;
+
+  std::vector<jvmtiFrameInfo> frames;
+  size_t start_result;
+  size_t stop_result;
+};
+
+static jvmtiError TranslateFrameVector(const std::vector<jvmtiFrameInfo>& frames,
+                                       jint start_depth,
+                                       size_t start_result,
+                                       jint max_frame_count,
+                                       jvmtiFrameInfo* frame_buffer,
+                                       jint* count_ptr) {
+  size_t collected_frames = frames.size();
+
+  // Assume we're here having collected something.
+  DCHECK_GT(max_frame_count, 0);
+
+  // Frames from the top.
+  if (start_depth >= 0) {
+    if (start_result != 0) {
+      // Not enough frames.
+      return ERR(ILLEGAL_ARGUMENT);
+    }
+    DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count));
+    if (frames.size() > 0) {
+      memcpy(frame_buffer, frames.data(), collected_frames * sizeof(jvmtiFrameInfo));
+    }
+    *count_ptr = static_cast<jint>(frames.size());
+    return ERR(NONE);
+  }
+
+  // Frames from the bottom.
+  if (collected_frames < static_cast<size_t>(-start_depth)) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+
+  size_t count = std::min(static_cast<size_t>(-start_depth), static_cast<size_t>(max_frame_count));
+  memcpy(frame_buffer,
+         &frames.data()[collected_frames + start_depth],
+         count * sizeof(jvmtiFrameInfo));
+  *count_ptr = static_cast<jint>(count);
+  return ERR(NONE);
+}
+
+struct GetStackTraceDirectClosure : public art::Closure {
+ public:
+  GetStackTraceDirectClosure(jvmtiFrameInfo* frame_buffer_, size_t start, size_t stop)
+      : frame_buffer(frame_buffer_),
+        start_input(start),
+        stop_input(stop),
+        index(0) {
+    DCHECK_GE(start_input, 0u);
+  }
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    auto frames_fn = [&](jvmtiFrameInfo info) {
+      frame_buffer[index] = info;
+      ++index;
+    };
+    auto visitor = MakeStackTraceVisitor(self, start_input, stop_input, frames_fn);
+    visitor.WalkStack(/* include_transitions */ false);
+  }
+
+  jvmtiFrameInfo* frame_buffer;
+
+  const size_t start_input;
+  const size_t stop_input;
+
+  size_t index = 0;
+};
+
+static jvmtiError GetThread(JNIEnv* env,
+                            art::ScopedObjectAccessAlreadyRunnable& soa,
+                            jthread java_thread,
+                            art::Thread** thread)
+    REQUIRES_SHARED(art::Locks::mutator_lock_)  // Needed for FromManagedThread.
+    REQUIRES(art::Locks::thread_list_lock_) {   // Needed for FromManagedThread.
+  if (java_thread == nullptr) {
+    *thread = art::Thread::Current();
+    if (*thread == nullptr) {
+      // GetStackTrace can only be run during the live phase, so the current thread should be
+      // attached and thus available. Getting a null for current means we're starting up or
+      // dying.
+      return ERR(WRONG_PHASE);
+    }
+  } else {
+    if (!env->IsInstanceOf(java_thread, art::WellKnownClasses::java_lang_Thread)) {
+      return ERR(INVALID_THREAD);
+    }
+
+    // TODO: Need non-aborting call here, to return JVMTI_ERROR_INVALID_THREAD.
+    *thread = art::Thread::FromManagedThread(soa, java_thread);
+    if (*thread == nullptr) {
+      return ERR(THREAD_NOT_ALIVE);
+    }
+  }
+  return ERR(NONE);
+}
+
+jvmtiError StackUtil::GetStackTrace(jvmtiEnv* jvmti_env ATTRIBUTE_UNUSED,
+                                    jthread java_thread,
+                                    jint start_depth,
+                                    jint max_frame_count,
+                                    jvmtiFrameInfo* frame_buffer,
+                                    jint* count_ptr) {
+  // It is not great that we have to hold these locks for so long, but it is necessary to ensure
+  // that the thread isn't dying on us.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+
+  art::Thread* thread;
+  jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(),
+                                      soa,
+                                      java_thread,
+                                      &thread);
+  if (thread_error != ERR(NONE)) {
+    return thread_error;
+  }
+  DCHECK(thread != nullptr);
+
+  art::ThreadState state = thread->GetState();
+  if (state == art::ThreadState::kStarting ||
+      state == art::ThreadState::kTerminated ||
+      thread->IsStillStarting()) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+
+  if (max_frame_count < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (frame_buffer == nullptr || count_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  if (max_frame_count == 0) {
+    *count_ptr = 0;
+    return ERR(NONE);
+  }
+
+  if (start_depth >= 0) {
+    // Fast path: Regular order of stack trace. Fill into the frame_buffer directly.
+    GetStackTraceDirectClosure closure(frame_buffer,
+                                       static_cast<size_t>(start_depth),
+                                       static_cast<size_t>(max_frame_count));
+    thread->RequestSynchronousCheckpoint(&closure);
+    *count_ptr = static_cast<jint>(closure.index);
+    if (closure.index < static_cast<size_t>(start_depth)) {
+      return ERR(ILLEGAL_ARGUMENT);
+    }
+    return ERR(NONE);
+  }
+
+  GetStackTraceVectorClosure closure(0, 0);
+  thread->RequestSynchronousCheckpoint(&closure);
+
+  return TranslateFrameVector(closure.frames,
+                              start_depth,
+                              closure.start_result,
+                              max_frame_count,
+                              frame_buffer,
+                              count_ptr);
+}
+
+template <typename Data>
+struct GetAllStackTracesVectorClosure : public art::Closure {
+  GetAllStackTracesVectorClosure(size_t stop, Data* data_)
+      : barrier(0), stop_input(stop), data(data_) {}
+
+  void Run(art::Thread* thread) OVERRIDE
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!data->mutex) {
+    art::Thread* self = art::Thread::Current();
+    Work(thread, self);
+    barrier.Pass(self);
+  }
+
+  void Work(art::Thread* thread, art::Thread* self)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(!data->mutex) {
+    // Skip threads that are still starting.
+    if (thread->IsStillStarting()) {
+      return;
+    }
+
+    std::vector<jvmtiFrameInfo>* thread_frames = data->GetFrameStorageFor(self, thread);
+    if (thread_frames == nullptr) {
+      return;
+    }
+
+    // Now collect the data.
+    auto frames_fn = [&](jvmtiFrameInfo info) {
+      thread_frames->push_back(info);
+    };
+    auto visitor = MakeStackTraceVisitor(thread, 0u, stop_input, frames_fn);
+    visitor.WalkStack(/* include_transitions */ false);
+  }
+
+  art::Barrier barrier;
+  const size_t stop_input;
+  Data* data;
+};
+
+template <typename Data>
+static void RunCheckpointAndWait(Data* data, size_t max_frame_count) {
+  GetAllStackTracesVectorClosure<Data> closure(max_frame_count, data);
+  size_t barrier_count = art::Runtime::Current()->GetThreadList()->RunCheckpoint(&closure, nullptr);
+  if (barrier_count == 0) {
+    return;
+  }
+  art::Thread* self = art::Thread::Current();
+  art::ScopedThreadStateChange tsc(self, art::ThreadState::kWaitingForCheckPointsToRun);
+  closure.barrier.Increment(self, barrier_count);
+}
+
+jvmtiError StackUtil::GetAllStackTraces(jvmtiEnv* env,
+                                        jint max_frame_count,
+                                        jvmtiStackInfo** stack_info_ptr,
+                                        jint* thread_count_ptr) {
+  if (max_frame_count < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (stack_info_ptr == nullptr || thread_count_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  struct AllStackTracesData {
+    AllStackTracesData() : mutex("GetAllStackTraces", art::LockLevel::kAbortLock) {}
+    ~AllStackTracesData() {
+      JNIEnv* jni_env = art::Thread::Current()->GetJniEnv();
+      for (jthread global_thread_ref : thread_peers) {
+        jni_env->DeleteGlobalRef(global_thread_ref);
+      }
+    }
+
+    std::vector<jvmtiFrameInfo>* GetFrameStorageFor(art::Thread* self, art::Thread* thread)
+        REQUIRES_SHARED(art::Locks::mutator_lock_)
+        REQUIRES(!mutex) {
+      art::MutexLock mu(self, mutex);
+
+      threads.push_back(thread);
+
+      jthread peer = art::Runtime::Current()->GetJavaVM()->AddGlobalRef(
+          self, thread->GetPeerFromOtherThread());
+      thread_peers.push_back(peer);
+
+      frames.emplace_back(new std::vector<jvmtiFrameInfo>());
+      return frames.back().get();
+    }
+
+    art::Mutex mutex;
+
+    // Storage. Only access directly after completion.
+
+    std::vector<art::Thread*> threads;
+    // "thread_peers" contains global references to their peers.
+    std::vector<jthread> thread_peers;
+
+    std::vector<std::unique_ptr<std::vector<jvmtiFrameInfo>>> frames;
+  };
+
+  AllStackTracesData data;
+  RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
+
+  art::Thread* current = art::Thread::Current();
+
+  // Convert the data into our output format.
+
+  // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to
+  //       allocate one big chunk for this and the actual frames, which means we need
+  //       to either be conservative or rearrange things later (the latter is implemented).
+  std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[data.frames.size()]);
+  std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos;
+  frame_infos.reserve(data.frames.size());
+
+  // Now run through and add data for each thread.
+  size_t sum_frames = 0;
+  for (size_t index = 0; index < data.frames.size(); ++index) {
+    jvmtiStackInfo& stack_info = stack_info_array.get()[index];
+    memset(&stack_info, 0, sizeof(jvmtiStackInfo));
+
+    const std::vector<jvmtiFrameInfo>& thread_frames = *data.frames[index].get();
+
+    // For the time being, set the thread to null. We'll fix it up in the second stage.
+    stack_info.thread = nullptr;
+    stack_info.state = JVMTI_THREAD_STATE_SUSPENDED;
+
+    size_t collected_frames = thread_frames.size();
+    if (max_frame_count == 0 || collected_frames == 0) {
+      stack_info.frame_count = 0;
+      stack_info.frame_buffer = nullptr;
+      continue;
+    }
+    DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count));
+
+    jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames];
+    frame_infos.emplace_back(frame_info);
+
+    jint count;
+    jvmtiError translate_result = TranslateFrameVector(thread_frames,
+                                                       0,
+                                                       0,
+                                                       static_cast<jint>(collected_frames),
+                                                       frame_info,
+                                                       &count);
+    DCHECK(translate_result == JVMTI_ERROR_NONE);
+    stack_info.frame_count = static_cast<jint>(collected_frames);
+    stack_info.frame_buffer = frame_info;
+    sum_frames += static_cast<size_t>(count);
+  }
+
+  // No errors, yet. Now put it all into an output buffer.
+  size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * data.frames.size(),
+                                                alignof(jvmtiFrameInfo));
+  size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo);
+  unsigned char* chunk_data;
+  jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data);
+  if (alloc_result != ERR(NONE)) {
+    return alloc_result;
+  }
+
+  jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data);
+  // First copy in all the basic data.
+  memcpy(stack_info, stack_info_array.get(), sizeof(jvmtiStackInfo) * data.frames.size());
+
+  // Now copy the frames and fix up the pointers.
+  jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>(
+      chunk_data + rounded_stack_info_size);
+  for (size_t i = 0; i < data.frames.size(); ++i) {
+    jvmtiStackInfo& old_stack_info = stack_info_array.get()[i];
+    jvmtiStackInfo& new_stack_info = stack_info[i];
+
+    // Translate the global ref into a local ref.
+    new_stack_info.thread =
+        static_cast<JNIEnv*>(current->GetJniEnv())->NewLocalRef(data.thread_peers[i]);
+
+    if (old_stack_info.frame_count > 0) {
+      // Only copy when there's data - leave the nullptr alone.
+      size_t frames_size = static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo);
+      memcpy(frame_info, old_stack_info.frame_buffer, frames_size);
+      new_stack_info.frame_buffer = frame_info;
+      frame_info += old_stack_info.frame_count;
+    }
+  }
+
+  *stack_info_ptr = stack_info;
+  *thread_count_ptr = static_cast<jint>(data.frames.size());
+
+  return ERR(NONE);
+}
+
+jvmtiError StackUtil::GetThreadListStackTraces(jvmtiEnv* env,
+                                               jint thread_count,
+                                               const jthread* thread_list,
+                                               jint max_frame_count,
+                                               jvmtiStackInfo** stack_info_ptr) {
+  if (max_frame_count < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (thread_count < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (thread_count == 0) {
+    *stack_info_ptr = nullptr;
+    return ERR(NONE);
+  }
+  if (stack_info_ptr == nullptr || stack_info_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* current = art::Thread::Current();
+  art::ScopedObjectAccess soa(current);      // Now we know we have the shared lock.
+
+  struct SelectStackTracesData {
+    SelectStackTracesData() : mutex("GetSelectStackTraces", art::LockLevel::kAbortLock) {}
+
+    std::vector<jvmtiFrameInfo>* GetFrameStorageFor(art::Thread* self, art::Thread* thread)
+              REQUIRES_SHARED(art::Locks::mutator_lock_)
+              REQUIRES(!mutex) {
+      art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread();
+      for (size_t index = 0; index != handles.size(); ++index) {
+        if (peer == handles[index].Get()) {
+          // Found the thread.
+          art::MutexLock mu(self, mutex);
+
+          threads.push_back(thread);
+          thread_list_indices.push_back(index);
+
+          frames.emplace_back(new std::vector<jvmtiFrameInfo>());
+          return frames.back().get();
+        }
+      }
+      return nullptr;
+    }
+
+    art::Mutex mutex;
+
+    // Selection data.
+
+    std::vector<art::Handle<art::mirror::Object>> handles;
+
+    // Storage. Only access directly after completion.
+
+    std::vector<art::Thread*> threads;
+    std::vector<size_t> thread_list_indices;
+
+    std::vector<std::unique_ptr<std::vector<jvmtiFrameInfo>>> frames;
+  };
+
+  SelectStackTracesData data;
+
+  // Decode all threads to raw pointers. Put them into a handle scope to avoid any moving GC bugs.
+  art::VariableSizedHandleScope hs(current);
+  for (jint i = 0; i != thread_count; ++i) {
+    if (thread_list[i] == nullptr) {
+      return ERR(INVALID_THREAD);
+    }
+    if (!soa.Env()->IsInstanceOf(thread_list[i], art::WellKnownClasses::java_lang_Thread)) {
+      return ERR(INVALID_THREAD);
+    }
+    data.handles.push_back(hs.NewHandle(soa.Decode<art::mirror::Object>(thread_list[i])));
+  }
+
+  RunCheckpointAndWait(&data, static_cast<size_t>(max_frame_count));
+
+  // Convert the data into our output format.
+
+  // Note: we use an array of jvmtiStackInfo for convenience. The spec says we need to
+  //       allocate one big chunk for this and the actual frames, which means we need
+  //       to either be conservative or rearrange things later (the latter is implemented).
+  std::unique_ptr<jvmtiStackInfo[]> stack_info_array(new jvmtiStackInfo[data.frames.size()]);
+  std::vector<std::unique_ptr<jvmtiFrameInfo[]>> frame_infos;
+  frame_infos.reserve(data.frames.size());
+
+  // Now run through and add data for each thread.
+  size_t sum_frames = 0;
+  for (size_t index = 0; index < data.frames.size(); ++index) {
+    jvmtiStackInfo& stack_info = stack_info_array.get()[index];
+    memset(&stack_info, 0, sizeof(jvmtiStackInfo));
+
+    art::Thread* self = data.threads[index];
+    const std::vector<jvmtiFrameInfo>& thread_frames = *data.frames[index].get();
+
+    // For the time being, set the thread to null. We don't have good ScopedLocalRef
+    // infrastructure.
+    DCHECK(self->GetPeerFromOtherThread() != nullptr);
+    stack_info.thread = nullptr;
+    stack_info.state = JVMTI_THREAD_STATE_SUSPENDED;
+
+    size_t collected_frames = thread_frames.size();
+    if (max_frame_count == 0 || collected_frames == 0) {
+      stack_info.frame_count = 0;
+      stack_info.frame_buffer = nullptr;
+      continue;
+    }
+    DCHECK_LE(collected_frames, static_cast<size_t>(max_frame_count));
+
+    jvmtiFrameInfo* frame_info = new jvmtiFrameInfo[collected_frames];
+    frame_infos.emplace_back(frame_info);
+
+    jint count;
+    jvmtiError translate_result = TranslateFrameVector(thread_frames,
+                                                       0,
+                                                       0,
+                                                       static_cast<jint>(collected_frames),
+                                                       frame_info,
+                                                       &count);
+    DCHECK(translate_result == JVMTI_ERROR_NONE);
+    stack_info.frame_count = static_cast<jint>(collected_frames);
+    stack_info.frame_buffer = frame_info;
+    sum_frames += static_cast<size_t>(count);
+  }
+
+  // No errors, yet. Now put it all into an output buffer. Note that this is not frames.size(),
+  // potentially.
+  size_t rounded_stack_info_size = art::RoundUp(sizeof(jvmtiStackInfo) * thread_count,
+                                                alignof(jvmtiFrameInfo));
+  size_t chunk_size = rounded_stack_info_size + sum_frames * sizeof(jvmtiFrameInfo);
+  unsigned char* chunk_data;
+  jvmtiError alloc_result = env->Allocate(chunk_size, &chunk_data);
+  if (alloc_result != ERR(NONE)) {
+    return alloc_result;
+  }
+
+  jvmtiStackInfo* stack_info = reinterpret_cast<jvmtiStackInfo*>(chunk_data);
+  jvmtiFrameInfo* frame_info = reinterpret_cast<jvmtiFrameInfo*>(
+      chunk_data + rounded_stack_info_size);
+
+  for (size_t i = 0; i < static_cast<size_t>(thread_count); ++i) {
+    // Check whether we found a running thread for this.
+    // Note: For simplicity, and with the expectation that the list is usually small, use a simple
+    //       search. (The list is *not* sorted!)
+    auto it = std::find(data.thread_list_indices.begin(), data.thread_list_indices.end(), i);
+    if (it == data.thread_list_indices.end()) {
+      // No native thread. Must be new or dead. We need to fill out the stack info now.
+      // (Need to read the Java "started" field to know whether this is starting or terminated.)
+      art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread_list[i]);
+      art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
+      art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z");
+      CHECK(started_field != nullptr);
+      bool started = started_field->GetBoolean(peer) != 0;
+      constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+      constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED |
+          JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+      stack_info[i].thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]);
+      stack_info[i].state = started ? kTerminatedState : kStartedState;
+      stack_info[i].frame_count = 0;
+      stack_info[i].frame_buffer = nullptr;
+    } else {
+      // Had a native thread and frames.
+      size_t f_index = it - data.thread_list_indices.begin();
+
+      jvmtiStackInfo& old_stack_info = stack_info_array.get()[f_index];
+      jvmtiStackInfo& new_stack_info = stack_info[i];
+
+      memcpy(&new_stack_info, &old_stack_info, sizeof(jvmtiStackInfo));
+      new_stack_info.thread = reinterpret_cast<JNIEnv*>(soa.Env())->NewLocalRef(thread_list[i]);
+      if (old_stack_info.frame_count > 0) {
+        // Only copy when there's data - leave the nullptr alone.
+        size_t frames_size =
+            static_cast<size_t>(old_stack_info.frame_count) * sizeof(jvmtiFrameInfo);
+        memcpy(frame_info, old_stack_info.frame_buffer, frames_size);
+        new_stack_info.frame_buffer = frame_info;
+        frame_info += old_stack_info.frame_count;
+      }
+    }
+  }
+
+  *stack_info_ptr = stack_info;
+
+  return ERR(NONE);
+}
+
+// Walks up the stack counting Java frames. This is not StackVisitor::ComputeNumFrames, as
+// runtime methods and transitions must not be counted.
+struct GetFrameCountVisitor : public art::StackVisitor {
+  explicit GetFrameCountVisitor(art::Thread* thread)
+      : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        count(0) {}
+
+  bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ArtMethod* m = GetMethod();
+    const bool do_count = !(m == nullptr || m->IsRuntimeMethod());
+    if (do_count) {
+      count++;
+    }
+    return true;
+  }
+
+  size_t count;
+};
+
+struct GetFrameCountClosure : public art::Closure {
+ public:
+  GetFrameCountClosure() : count(0) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    GetFrameCountVisitor visitor(self);
+    visitor.WalkStack(false);
+
+    count = visitor.count;
+  }
+
+  size_t count;
+};
+
+jvmtiError StackUtil::GetFrameCount(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                    jthread java_thread,
+                                    jint* count_ptr) {
+  // It is not great that we have to hold these locks for so long, but it is necessary to ensure
+  // that the thread isn't dying on us.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+
+  art::Thread* thread;
+  jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(),
+                                      soa,
+                                      java_thread,
+                                      &thread);
+
+  if (thread_error != ERR(NONE)) {
+    return thread_error;
+  }
+  DCHECK(thread != nullptr);
+
+  if (count_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  GetFrameCountClosure closure;
+  thread->RequestSynchronousCheckpoint(&closure);
+
+  *count_ptr = closure.count;
+  return ERR(NONE);
+}
+
+// Walks up the stack 'n' callers, when used with Thread::WalkStack.
+struct GetLocationVisitor : public art::StackVisitor {
+  GetLocationVisitor(art::Thread* thread, size_t n_in)
+      : art::StackVisitor(thread, nullptr, art::StackVisitor::StackWalkKind::kIncludeInlinedFrames),
+        n(n_in),
+        count(0),
+        caller(nullptr),
+        caller_dex_pc(0) {}
+
+  bool VisitFrame() REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    art::ArtMethod* m = GetMethod();
+    const bool do_count = !(m == nullptr || m->IsRuntimeMethod());
+    if (do_count) {
+      DCHECK(caller == nullptr);
+      if (count == n) {
+        caller = m;
+        caller_dex_pc = GetDexPc(false);
+        return false;
+      }
+      count++;
+    }
+    return true;
+  }
+
+  const size_t n;
+  size_t count;
+  art::ArtMethod* caller;
+  uint32_t caller_dex_pc;
+};
+
+struct GetLocationClosure : public art::Closure {
+ public:
+  explicit GetLocationClosure(size_t n_in) : n(n_in), method(nullptr), dex_pc(0) {}
+
+  void Run(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    GetLocationVisitor visitor(self, n);
+    visitor.WalkStack(false);
+
+    method = visitor.caller;
+    dex_pc = visitor.caller_dex_pc;
+  }
+
+  const size_t n;
+  art::ArtMethod* method;
+  uint32_t dex_pc;
+};
+
+jvmtiError StackUtil::GetFrameLocation(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                       jthread java_thread,
+                                       jint depth,
+                                       jmethodID* method_ptr,
+                                       jlocation* location_ptr) {
+  // It is not great that we have to hold these locks for so long, but it is necessary to ensure
+  // that the thread isn't dying on us.
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
+
+  art::Thread* thread;
+  jvmtiError thread_error = GetThread(art::Thread::Current()->GetJniEnv(),
+                                      soa,
+                                      java_thread,
+                                      &thread);
+  if (thread_error != ERR(NONE)) {
+    return thread_error;
+  }
+  DCHECK(thread != nullptr);
+
+  if (depth < 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  }
+  if (method_ptr == nullptr || location_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  GetLocationClosure closure(static_cast<size_t>(depth));
+  thread->RequestSynchronousCheckpoint(&closure);
+
+  if (closure.method == nullptr) {
+    return ERR(NO_MORE_FRAMES);
+  }
+
+  *method_ptr = art::jni::EncodeArtMethod(closure.method);
+  if (closure.method->IsNative()) {
+    *location_ptr = -1;
+  } else {
+    if (closure.dex_pc == art::DexFile::kDexNoIndex) {
+      return ERR(INTERNAL);
+    }
+    *location_ptr = static_cast<jlocation>(closure.dex_pc);
+  }
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_stack.h b/openjdkjvmti/ti_stack.h
new file mode 100644
index 0000000..2e96b82
--- /dev/null
+++ b/openjdkjvmti/ti_stack.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2016 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_TI_STACK_H_
+#define ART_OPENJDKJVMTI_TI_STACK_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+
+namespace openjdkjvmti {
+
+class StackUtil {
+ public:
+  static jvmtiError GetAllStackTraces(jvmtiEnv* env,
+                                      jint max_frame_count,
+                                      jvmtiStackInfo** stack_info_ptr,
+                                      jint* thread_count_ptr)
+      REQUIRES(!art::Locks::thread_list_lock_);
+
+  static jvmtiError GetFrameCount(jvmtiEnv* env, jthread thread, jint* count_ptr);
+
+  static jvmtiError GetFrameLocation(jvmtiEnv* env,
+                                     jthread thread,
+                                     jint depth,
+                                     jmethodID* method_ptr,
+                                     jlocation* location_ptr);
+
+  static jvmtiError GetStackTrace(jvmtiEnv* env,
+                                  jthread thread,
+                                  jint start_depth,
+                                  jint max_frame_count,
+                                  jvmtiFrameInfo* frame_buffer,
+                                  jint* count_ptr);
+
+  static jvmtiError GetThreadListStackTraces(jvmtiEnv* env,
+                                             jint thread_count,
+                                             const jthread* thread_list,
+                                             jint max_frame_count,
+                                             jvmtiStackInfo** stack_info_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_STACK_H_
diff --git a/openjdkjvmti/ti_thread.cc b/openjdkjvmti/ti_thread.cc
new file mode 100644
index 0000000..6fa73f8
--- /dev/null
+++ b/openjdkjvmti/ti_thread.cc
@@ -0,0 +1,909 @@
+/* Copyright (C) 2017 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 "ti_thread.h"
+
+#include "android-base/strings.h"
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "base/mutex.h"
+#include "events-inl.h"
+#include "gc/system_weak.h"
+#include "gc_root-inl.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/string.h"
+#include "nativehelper/ScopedLocalRef.h"
+#include "obj_ptr.h"
+#include "runtime.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+art::ArtField* ThreadUtil::context_class_loader_ = nullptr;
+
+struct ThreadCallback : public art::ThreadLifecycleCallback, public art::RuntimePhaseCallback {
+  jthread GetThreadObject(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (self->GetPeer() == nullptr) {
+      return nullptr;
+    }
+    return self->GetJniEnv()->AddLocalReference<jthread>(self->GetPeer());
+  }
+  template <ArtJvmtiEvent kEvent>
+  void Post(art::Thread* self) REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    DCHECK_EQ(self, art::Thread::Current());
+    ScopedLocalRef<jthread> thread(self->GetJniEnv(), GetThreadObject(self));
+    art::ScopedThreadSuspension sts(self, art::ThreadState::kNative);
+    event_handler->DispatchEvent<kEvent>(self,
+                                         reinterpret_cast<JNIEnv*>(self->GetJniEnv()),
+                                         thread.get());
+  }
+
+  void ThreadStart(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (!started) {
+      // Runtime isn't started. We only expect at most the signal handler or JIT threads to be
+      // started here.
+      if (art::kIsDebugBuild) {
+        std::string name;
+        self->GetThreadName(name);
+        if (name != "JDWP" &&
+            name != "Signal Catcher" &&
+            !android::base::StartsWith(name, "Jit thread pool")) {
+          LOG(FATAL) << "Unexpected thread before start: " << name;
+        }
+      }
+      return;
+    }
+    Post<ArtJvmtiEvent::kThreadStart>(self);
+  }
+
+  void ThreadDeath(art::Thread* self) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    Post<ArtJvmtiEvent::kThreadEnd>(self);
+  }
+
+  void NextRuntimePhase(RuntimePhase phase) OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
+    if (phase == RuntimePhase::kInit) {
+      // We moved to VMInit. Report the main thread as started (it was attached early, and must
+      // not be reported until Init.
+      started = true;
+      Post<ArtJvmtiEvent::kThreadStart>(art::Thread::Current());
+    }
+  }
+
+  EventHandler* event_handler = nullptr;
+  bool started = false;
+};
+
+ThreadCallback gThreadCallback;
+
+void ThreadUtil::Register(EventHandler* handler) {
+  art::Runtime* runtime = art::Runtime::Current();
+
+  gThreadCallback.started = runtime->IsStarted();
+  gThreadCallback.event_handler = handler;
+
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add thread callback");
+  runtime->GetRuntimeCallbacks()->AddThreadLifecycleCallback(&gThreadCallback);
+  runtime->GetRuntimeCallbacks()->AddRuntimePhaseCallback(&gThreadCallback);
+}
+
+void ThreadUtil::CacheData() {
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  art::ObjPtr<art::mirror::Class> thread_class =
+      soa.Decode<art::mirror::Class>(art::WellKnownClasses::java_lang_Thread);
+  CHECK(thread_class != nullptr);
+  context_class_loader_ = thread_class->FindDeclaredInstanceField("contextClassLoader",
+                                                                  "Ljava/lang/ClassLoader;");
+  CHECK(context_class_loader_ != nullptr);
+}
+
+void ThreadUtil::Unregister() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Remove thread callback");
+  art::Runtime* runtime = art::Runtime::Current();
+  runtime->GetRuntimeCallbacks()->RemoveThreadLifecycleCallback(&gThreadCallback);
+  runtime->GetRuntimeCallbacks()->RemoveRuntimePhaseCallback(&gThreadCallback);
+}
+
+jvmtiError ThreadUtil::GetCurrentThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread* thread_ptr) {
+  art::Thread* self = art::Thread::Current();
+
+  art::ScopedObjectAccess soa(self);
+
+  jthread thread_peer;
+  if (self->IsStillStarting()) {
+    thread_peer = nullptr;
+  } else {
+    thread_peer = soa.AddLocalReference<jthread>(self->GetPeer());
+  }
+
+  *thread_ptr = thread_peer;
+  return ERR(NONE);
+}
+
+// Get the native thread. The spec says a null object denotes the current thread.
+art::Thread* ThreadUtil::GetNativeThread(jthread thread,
+                                         const art::ScopedObjectAccessAlreadyRunnable& soa) {
+  if (thread == nullptr) {
+    return art::Thread::Current();
+  }
+
+  return art::Thread::FromManagedThread(soa, thread);
+}
+
+jvmtiError ThreadUtil::GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr) {
+  if (info_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  if (!PhaseUtil::IsLivePhase()) {
+    return JVMTI_ERROR_WRONG_PHASE;
+  }
+
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+
+  art::Thread* target = GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+
+  JvmtiUniquePtr<char[]> name_uptr;
+  if (target != nullptr) {
+    // Have a native thread object, this thread is alive.
+    std::string name;
+    target->GetThreadName(name);
+    jvmtiError name_result;
+    name_uptr = CopyString(env, name.c_str(), &name_result);
+    if (name_uptr == nullptr) {
+      return name_result;
+    }
+    info_ptr->name = name_uptr.get();
+
+    info_ptr->priority = target->GetNativePriority();
+
+    info_ptr->is_daemon = target->IsDaemon();
+
+    art::ObjPtr<art::mirror::Object> peer = target->GetPeerFromOtherThread();
+
+    // ThreadGroup.
+    if (peer != nullptr) {
+      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+      CHECK(f != nullptr);
+      art::ObjPtr<art::mirror::Object> group = f->GetObject(peer);
+      info_ptr->thread_group = group == nullptr
+                                   ? nullptr
+                                   : soa.AddLocalReference<jthreadGroup>(group);
+    } else {
+      info_ptr->thread_group = nullptr;
+    }
+
+    // Context classloader.
+    DCHECK(context_class_loader_ != nullptr);
+    art::ObjPtr<art::mirror::Object> ccl = peer != nullptr
+        ? context_class_loader_->GetObject(peer)
+        : nullptr;
+    info_ptr->context_class_loader = ccl == nullptr
+                                         ? nullptr
+                                         : soa.AddLocalReference<jobject>(ccl);
+  } else {
+    // Only the peer. This thread has either not been started, or is dead. Read things from
+    // the Java side.
+    art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
+
+    // Name.
+    {
+      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_name);
+      CHECK(f != nullptr);
+      art::ObjPtr<art::mirror::Object> name = f->GetObject(peer);
+      std::string name_cpp;
+      const char* name_cstr;
+      if (name != nullptr) {
+        name_cpp = name->AsString()->ToModifiedUtf8();
+        name_cstr = name_cpp.c_str();
+      } else {
+        name_cstr = "";
+      }
+      jvmtiError name_result;
+      name_uptr = CopyString(env, name_cstr, &name_result);
+      if (name_uptr == nullptr) {
+        return name_result;
+      }
+      info_ptr->name = name_uptr.get();
+    }
+
+    // Priority.
+    {
+      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_priority);
+      CHECK(f != nullptr);
+      info_ptr->priority = static_cast<jint>(f->GetInt(peer));
+    }
+
+    // Daemon.
+    {
+      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_daemon);
+      CHECK(f != nullptr);
+      info_ptr->is_daemon = f->GetBoolean(peer) == 0 ? JNI_FALSE : JNI_TRUE;
+    }
+
+    // ThreadGroup.
+    {
+      art::ArtField* f = art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+      CHECK(f != nullptr);
+      art::ObjPtr<art::mirror::Object> group = f->GetObject(peer);
+      info_ptr->thread_group = group == nullptr
+                                   ? nullptr
+                                   : soa.AddLocalReference<jthreadGroup>(group);
+    }
+
+    // Context classloader.
+    DCHECK(context_class_loader_ != nullptr);
+    art::ObjPtr<art::mirror::Object> ccl = peer != nullptr
+        ? context_class_loader_->GetObject(peer)
+        : nullptr;
+    info_ptr->context_class_loader = ccl == nullptr
+                                         ? nullptr
+                                         : soa.AddLocalReference<jobject>(ccl);
+  }
+
+  name_uptr.release();
+
+  return ERR(NONE);
+}
+
+struct InternalThreadState {
+  art::Thread* native_thread;
+  art::ThreadState art_state;
+  int thread_user_code_suspend_count;
+};
+
+// Return the thread's (or current thread, if null) thread state.
+static InternalThreadState GetNativeThreadState(jthread thread,
+                                                const art::ScopedObjectAccessAlreadyRunnable& soa)
+    REQUIRES_SHARED(art::Locks::mutator_lock_)
+    REQUIRES(art::Locks::thread_list_lock_, art::Locks::user_code_suspension_lock_) {
+  art::Thread* self = nullptr;
+  if (thread == nullptr) {
+    self = art::Thread::Current();
+  } else {
+    self = art::Thread::FromManagedThread(soa, thread);
+  }
+  InternalThreadState thread_state = {};
+  art::MutexLock tscl_mu(soa.Self(), *art::Locks::thread_suspend_count_lock_);
+  thread_state.native_thread = self;
+  if (self == nullptr || self->IsStillStarting()) {
+    thread_state.art_state = art::ThreadState::kStarting;
+    thread_state.thread_user_code_suspend_count = 0;
+  } else {
+    thread_state.art_state = self->GetState();
+    thread_state.thread_user_code_suspend_count = self->GetUserCodeSuspendCount();
+  }
+  return thread_state;
+}
+
+static jint GetJvmtiThreadStateFromInternal(const InternalThreadState& state) {
+  art::ThreadState internal_thread_state = state.art_state;
+  jint jvmti_state = JVMTI_THREAD_STATE_ALIVE;
+
+  if (state.thread_user_code_suspend_count != 0) {
+    jvmti_state |= JVMTI_THREAD_STATE_SUSPENDED;
+    // Note: We do not have data about the previous state. Otherwise we should load the previous
+    //       state here.
+  }
+
+  if (state.native_thread->IsInterrupted()) {
+    jvmti_state |= JVMTI_THREAD_STATE_INTERRUPTED;
+  }
+
+  if (internal_thread_state == art::ThreadState::kNative) {
+    jvmti_state |= JVMTI_THREAD_STATE_IN_NATIVE;
+  }
+
+  if (internal_thread_state == art::ThreadState::kRunnable ||
+      internal_thread_state == art::ThreadState::kWaitingWeakGcRootRead ||
+      internal_thread_state == art::ThreadState::kSuspended) {
+    jvmti_state |= JVMTI_THREAD_STATE_RUNNABLE;
+  } else if (internal_thread_state == art::ThreadState::kBlocked) {
+    jvmti_state |= JVMTI_THREAD_STATE_BLOCKED_ON_MONITOR_ENTER;
+  } else {
+    // Should be in waiting state.
+    jvmti_state |= JVMTI_THREAD_STATE_WAITING;
+
+    if (internal_thread_state == art::ThreadState::kTimedWaiting ||
+        internal_thread_state == art::ThreadState::kSleeping) {
+      jvmti_state |= JVMTI_THREAD_STATE_WAITING_WITH_TIMEOUT;
+    } else {
+      jvmti_state |= JVMTI_THREAD_STATE_WAITING_INDEFINITELY;
+    }
+
+    if (internal_thread_state == art::ThreadState::kSleeping) {
+      jvmti_state |= JVMTI_THREAD_STATE_SLEEPING;
+    }
+
+    if (internal_thread_state == art::ThreadState::kTimedWaiting ||
+        internal_thread_state == art::ThreadState::kWaiting) {
+      jvmti_state |= JVMTI_THREAD_STATE_IN_OBJECT_WAIT;
+    }
+
+    // TODO: PARKED. We'll have to inspect the stack.
+  }
+
+  return jvmti_state;
+}
+
+static jint GetJavaStateFromInternal(const InternalThreadState& state) {
+  switch (state.art_state) {
+    case art::ThreadState::kTerminated:
+      return JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+
+    case art::ThreadState::kRunnable:
+    case art::ThreadState::kNative:
+    case art::ThreadState::kWaitingWeakGcRootRead:
+    case art::ThreadState::kSuspended:
+      return JVMTI_JAVA_LANG_THREAD_STATE_RUNNABLE;
+
+    case art::ThreadState::kTimedWaiting:
+    case art::ThreadState::kSleeping:
+      return JVMTI_JAVA_LANG_THREAD_STATE_TIMED_WAITING;
+
+    case art::ThreadState::kBlocked:
+      return JVMTI_JAVA_LANG_THREAD_STATE_BLOCKED;
+
+    case art::ThreadState::kStarting:
+      return JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+
+    case art::ThreadState::kWaiting:
+    case art::ThreadState::kWaitingForGcToComplete:
+    case art::ThreadState::kWaitingPerformingGc:
+    case art::ThreadState::kWaitingForCheckPointsToRun:
+    case art::ThreadState::kWaitingForDebuggerSend:
+    case art::ThreadState::kWaitingForDebuggerToAttach:
+    case art::ThreadState::kWaitingInMainDebuggerLoop:
+    case art::ThreadState::kWaitingForDebuggerSuspension:
+    case art::ThreadState::kWaitingForDeoptimization:
+    case art::ThreadState::kWaitingForGetObjectsAllocated:
+    case art::ThreadState::kWaitingForJniOnLoad:
+    case art::ThreadState::kWaitingForSignalCatcherOutput:
+    case art::ThreadState::kWaitingInMainSignalCatcherLoop:
+    case art::ThreadState::kWaitingForMethodTracingStart:
+    case art::ThreadState::kWaitingForVisitObjects:
+    case art::ThreadState::kWaitingForGcThreadFlip:
+      return JVMTI_JAVA_LANG_THREAD_STATE_WAITING;
+  }
+  LOG(FATAL) << "Unreachable";
+  UNREACHABLE();
+}
+
+// Suspends the current thread if it has any suspend requests on it.
+static void SuspendCheck(art::Thread* self)
+    REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_) {
+  art::ScopedObjectAccess soa(self);
+  // Really this is only needed if we are in FastJNI and actually have the mutator_lock_ already.
+  self->FullSuspendCheck();
+}
+
+jvmtiError ThreadUtil::GetThreadState(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                      jthread thread,
+                                      jint* thread_state_ptr) {
+  if (thread_state_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* self = art::Thread::Current();
+  InternalThreadState state = {};
+  // Loop since we need to bail out and try again if we would end up getting suspended while holding
+  // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
+  // release the lock, wait to get resumed and try again.
+  do {
+    SuspendCheck(self);
+    art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+    {
+      art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+      if (self->GetUserCodeSuspendCount() != 0) {
+        // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_
+        // by a user-code suspension. We retry and do another SuspendCheck to clear this.
+        continue;
+      }
+    }
+    art::ScopedObjectAccess soa(self);
+    art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+    state = GetNativeThreadState(thread, soa);
+    if (state.art_state == art::ThreadState::kStarting) {
+      break;
+    }
+    DCHECK(state.native_thread != nullptr);
+
+    // Translate internal thread state to JVMTI and Java state.
+    jint jvmti_state = GetJvmtiThreadStateFromInternal(state);
+
+    // Java state is derived from nativeGetState.
+    // TODO: Our implementation assigns "runnable" to suspended. As such, we will have slightly
+    //       different mask if a thread got suspended due to user-code. However, this is for
+    //       consistency with the Java view.
+    jint java_state = GetJavaStateFromInternal(state);
+
+    *thread_state_ptr = jvmti_state | java_state;
+
+    return ERR(NONE);
+  } while (true);
+
+  DCHECK_EQ(state.art_state, art::ThreadState::kStarting);
+
+  if (thread == nullptr) {
+    // No native thread, and no Java thread? We must be starting up. Report as wrong phase.
+    return ERR(WRONG_PHASE);
+  }
+
+  art::ScopedObjectAccess soa(self);
+
+  // Need to read the Java "started" field to know whether this is starting or terminated.
+  art::ObjPtr<art::mirror::Object> peer = soa.Decode<art::mirror::Object>(thread);
+  art::ObjPtr<art::mirror::Class> klass = peer->GetClass();
+  art::ArtField* started_field = klass->FindDeclaredInstanceField("started", "Z");
+  CHECK(started_field != nullptr);
+  bool started = started_field->GetBoolean(peer) != 0;
+  constexpr jint kStartedState = JVMTI_JAVA_LANG_THREAD_STATE_NEW;
+  constexpr jint kTerminatedState = JVMTI_THREAD_STATE_TERMINATED |
+                                    JVMTI_JAVA_LANG_THREAD_STATE_TERMINATED;
+  *thread_state_ptr = started ? kTerminatedState : kStartedState;
+  return ERR(NONE);
+}
+
+jvmtiError ThreadUtil::GetAllThreads(jvmtiEnv* env,
+                                     jint* threads_count_ptr,
+                                     jthread** threads_ptr) {
+  if (threads_count_ptr == nullptr || threads_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* current = art::Thread::Current();
+
+  art::ScopedObjectAccess soa(current);
+
+  art::MutexLock mu(current, *art::Locks::thread_list_lock_);
+  std::list<art::Thread*> thread_list = art::Runtime::Current()->GetThreadList()->GetList();
+
+  std::vector<art::ObjPtr<art::mirror::Object>> peers;
+
+  for (art::Thread* thread : thread_list) {
+    // Skip threads that are still starting.
+    if (thread->IsStillStarting()) {
+      continue;
+    }
+
+    art::ObjPtr<art::mirror::Object> peer = thread->GetPeerFromOtherThread();
+    if (peer != nullptr) {
+      peers.push_back(peer);
+    }
+  }
+
+  if (peers.empty()) {
+    *threads_count_ptr = 0;
+    *threads_ptr = nullptr;
+  } else {
+    unsigned char* data;
+    jvmtiError data_result = env->Allocate(peers.size() * sizeof(jthread), &data);
+    if (data_result != ERR(NONE)) {
+      return data_result;
+    }
+    jthread* threads = reinterpret_cast<jthread*>(data);
+    for (size_t i = 0; i != peers.size(); ++i) {
+      threads[i] = soa.AddLocalReference<jthread>(peers[i]);
+    }
+
+    *threads_count_ptr = static_cast<jint>(peers.size());
+    *threads_ptr = threads;
+  }
+  return ERR(NONE);
+}
+
+// The struct that we store in the art::Thread::custom_tls_ that maps the jvmtiEnvs to the data
+// stored with that thread. This is needed since different jvmtiEnvs are not supposed to share TLS
+// data but we only have a single slot in Thread objects to store data.
+struct JvmtiGlobalTLSData {
+  std::unordered_map<jvmtiEnv*, const void*> data GUARDED_BY(art::Locks::thread_list_lock_);
+};
+
+static void RemoveTLSData(art::Thread* target, void* ctx) REQUIRES(art::Locks::thread_list_lock_) {
+  jvmtiEnv* env = reinterpret_cast<jvmtiEnv*>(ctx);
+  art::Locks::thread_list_lock_->AssertHeld(art::Thread::Current());
+  JvmtiGlobalTLSData* global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+  if (global_tls != nullptr) {
+    global_tls->data.erase(env);
+  }
+}
+
+void ThreadUtil::RemoveEnvironment(jvmtiEnv* env) {
+  art::Thread* self = art::Thread::Current();
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::ThreadList* list = art::Runtime::Current()->GetThreadList();
+  list->ForEach(RemoveTLSData, env);
+}
+
+jvmtiError ThreadUtil::SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data) {
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+
+  JvmtiGlobalTLSData* global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+  if (global_tls == nullptr) {
+    target->SetCustomTLS(new JvmtiGlobalTLSData);
+    global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+  }
+
+  global_tls->data[env] = data;
+
+  return ERR(NONE);
+}
+
+jvmtiError ThreadUtil::GetThreadLocalStorage(jvmtiEnv* env,
+                                             jthread thread,
+                                             void** data_ptr) {
+  if (data_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Thread* self = art::Thread::Current();
+  art::ScopedObjectAccess soa(self);
+  art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+  art::Thread* target = GetNativeThread(thread, soa);
+  if (target == nullptr && thread == nullptr) {
+    return ERR(INVALID_THREAD);
+  }
+  if (target == nullptr) {
+    return ERR(THREAD_NOT_ALIVE);
+  }
+
+  JvmtiGlobalTLSData* global_tls = reinterpret_cast<JvmtiGlobalTLSData*>(target->GetCustomTLS());
+  if (global_tls == nullptr) {
+    *data_ptr = nullptr;
+    return OK;
+  }
+  auto it = global_tls->data.find(env);
+  if (it != global_tls->data.end()) {
+    *data_ptr = const_cast<void*>(it->second);
+  } else {
+    *data_ptr = nullptr;
+  }
+
+  return ERR(NONE);
+}
+
+struct AgentData {
+  const void* arg;
+  jvmtiStartFunction proc;
+  jthread thread;
+  JavaVM* java_vm;
+  jvmtiEnv* jvmti_env;
+  jint priority;
+};
+
+static void* AgentCallback(void* arg) {
+  std::unique_ptr<AgentData> data(reinterpret_cast<AgentData*>(arg));
+  CHECK(data->thread != nullptr);
+
+  // We already have a peer. So call our special Attach function.
+  art::Thread* self = art::Thread::Attach("JVMTI Agent thread", true, data->thread);
+  CHECK(self != nullptr);
+  // The name in Attach() is only for logging. Set the thread name. This is important so
+  // that the thread is no longer seen as starting up.
+  {
+    art::ScopedObjectAccess soa(self);
+    self->SetThreadName("JVMTI Agent thread");
+  }
+
+  // Release the peer.
+  JNIEnv* env = self->GetJniEnv();
+  env->DeleteGlobalRef(data->thread);
+  data->thread = nullptr;
+
+  // Run the agent code.
+  data->proc(data->jvmti_env, env, const_cast<void*>(data->arg));
+
+  // Detach the thread.
+  int detach_result = data->java_vm->DetachCurrentThread();
+  CHECK_EQ(detach_result, 0);
+
+  return nullptr;
+}
+
+jvmtiError ThreadUtil::RunAgentThread(jvmtiEnv* jvmti_env,
+                                      jthread thread,
+                                      jvmtiStartFunction proc,
+                                      const void* arg,
+                                      jint priority) {
+  if (priority < JVMTI_THREAD_MIN_PRIORITY || priority > JVMTI_THREAD_MAX_PRIORITY) {
+    return ERR(INVALID_PRIORITY);
+  }
+  JNIEnv* env = art::Thread::Current()->GetJniEnv();
+  if (thread == nullptr || !env->IsInstanceOf(thread, art::WellKnownClasses::java_lang_Thread)) {
+    return ERR(INVALID_THREAD);
+  }
+  if (proc == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  std::unique_ptr<AgentData> data(new AgentData);
+  data->arg = arg;
+  data->proc = proc;
+  // We need a global ref for Java objects, as local refs will be invalid.
+  data->thread = env->NewGlobalRef(thread);
+  data->java_vm = art::Runtime::Current()->GetJavaVM();
+  data->jvmti_env = jvmti_env;
+  data->priority = priority;
+
+  pthread_t pthread;
+  int pthread_create_result = pthread_create(&pthread,
+                                             nullptr,
+                                             &AgentCallback,
+                                             reinterpret_cast<void*>(data.get()));
+  if (pthread_create_result != 0) {
+    return ERR(INTERNAL);
+  }
+  data.release();
+
+  return ERR(NONE);
+}
+
+jvmtiError ThreadUtil::SuspendOther(art::Thread* self,
+                                    jthread target_jthread) {
+  // Loop since we need to bail out and try again if we would end up getting suspended while holding
+  // the user_code_suspension_lock_ due to a SuspendReason::kForUserCode. In this situation we
+  // release the lock, wait to get resumed and try again.
+  do {
+    // Suspend ourself if we have any outstanding suspends. This is so we won't suspend due to
+    // another SuspendThread in the middle of suspending something else potentially causing a
+    // deadlock. We need to do this in the loop because if we ended up back here then we had
+    // outstanding SuspendReason::kForUserCode suspensions and we should wait for them to be cleared
+    // before continuing.
+    SuspendCheck(self);
+    art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+    {
+      art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+      // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
+      // a user-code suspension. We retry and do another SuspendCheck to clear this.
+      if (self->GetUserCodeSuspendCount() != 0) {
+        continue;
+      }
+      // We are not going to be suspended by user code from now on.
+    }
+    {
+      art::ScopedObjectAccess soa(self);
+      art::MutexLock thread_list_mu(self, *art::Locks::thread_list_lock_);
+      art::Thread* target = GetNativeThread(target_jthread, soa);
+      art::ThreadState state = target->GetState();
+      if (state == art::ThreadState::kTerminated || state == art::ThreadState::kStarting) {
+        return ERR(THREAD_NOT_ALIVE);
+      } else {
+        art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+        if (target->GetUserCodeSuspendCount() != 0) {
+          return ERR(THREAD_SUSPENDED);
+        }
+      }
+    }
+    bool timeout = true;
+    art::Thread* ret_target = art::Runtime::Current()->GetThreadList()->SuspendThreadByPeer(
+        target_jthread,
+        /* request_suspension */ true,
+        art::SuspendReason::kForUserCode,
+        &timeout);
+    if (ret_target == nullptr && !timeout) {
+      // TODO It would be good to get more information about why exactly the thread failed to
+      // suspend.
+      return ERR(INTERNAL);
+    } else if (!timeout) {
+      // we didn't time out and got a result.
+      return OK;
+    }
+    // We timed out. Just go around and try again.
+  } while (true);
+  UNREACHABLE();
+}
+
+jvmtiError ThreadUtil::SuspendSelf(art::Thread* self) {
+  CHECK(self == art::Thread::Current());
+  {
+    art::MutexLock mu(self, *art::Locks::user_code_suspension_lock_);
+    art::MutexLock thread_list_mu(self, *art::Locks::thread_suspend_count_lock_);
+    if (self->GetUserCodeSuspendCount() != 0) {
+      // This can only happen if we race with another thread to suspend 'self' and we lose.
+      return ERR(THREAD_SUSPENDED);
+    }
+    // We shouldn't be able to fail this.
+    if (!self->ModifySuspendCount(self, +1, nullptr, art::SuspendReason::kForUserCode)) {
+      // TODO More specific error would be nice.
+      return ERR(INTERNAL);
+    }
+  }
+  // Once we have requested the suspend we actually go to sleep. We need to do this after releasing
+  // the suspend_lock to make sure we can be woken up. This call gains the mutator lock causing us
+  // to go to sleep until we are resumed.
+  SuspendCheck(self);
+  return OK;
+}
+
+jvmtiError ThreadUtil::SuspendThread(jvmtiEnv* env ATTRIBUTE_UNUSED, jthread thread) {
+  art::Thread* self = art::Thread::Current();
+  bool target_is_self = false;
+  {
+    art::ScopedObjectAccess soa(self);
+    art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+    art::Thread* target = GetNativeThread(thread, soa);
+    if (target == nullptr) {
+      return ERR(INVALID_THREAD);
+    } else if (target == self) {
+      target_is_self = true;
+    }
+  }
+  if (target_is_self) {
+    return SuspendSelf(self);
+  } else {
+    return SuspendOther(self, thread);
+  }
+}
+
+jvmtiError ThreadUtil::ResumeThread(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                    jthread thread) {
+  if (thread == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  art::Thread* self = art::Thread::Current();
+  art::Thread* target;
+  // Retry until we know we won't get suspended by user code while resuming something.
+  do {
+    SuspendCheck(self);
+    art::MutexLock ucsl_mu(self, *art::Locks::user_code_suspension_lock_);
+    {
+      art::MutexLock tscl_mu(self, *art::Locks::thread_suspend_count_lock_);
+      // Make sure we won't be suspended in the middle of holding the thread_suspend_count_lock_ by
+      // a user-code suspension. We retry and do another SuspendCheck to clear this.
+      if (self->GetUserCodeSuspendCount() != 0) {
+        continue;
+      }
+    }
+    // From now on we know we cannot get suspended by user-code.
+    {
+      // NB This does a SuspendCheck (during thread state change) so we need to make sure we don't
+      // have the 'suspend_lock' locked here.
+      art::ScopedObjectAccess soa(self);
+      art::MutexLock tll_mu(self, *art::Locks::thread_list_lock_);
+      target = GetNativeThread(thread, soa);
+      if (target == nullptr) {
+        return ERR(INVALID_THREAD);
+      } else if (target == self) {
+        // We would have paused until we aren't suspended anymore due to the ScopedObjectAccess so
+        // we can just return THREAD_NOT_SUSPENDED. Unfortunately we cannot do any real DCHECKs
+        // about current state since it's all concurrent.
+        return ERR(THREAD_NOT_SUSPENDED);
+      } else if (target->GetState() == art::ThreadState::kTerminated) {
+        return ERR(THREAD_NOT_ALIVE);
+      }
+      // The JVMTI spec requires us to return THREAD_NOT_SUSPENDED if it is alive but we really
+      // cannot tell why resume failed.
+      {
+        art::MutexLock thread_suspend_count_mu(self, *art::Locks::thread_suspend_count_lock_);
+        if (target->GetUserCodeSuspendCount() == 0) {
+          return ERR(THREAD_NOT_SUSPENDED);
+        }
+      }
+    }
+    // It is okay that we don't have a thread_list_lock here since we know that the thread cannot
+    // die since it is currently held suspended by a SuspendReason::kForUserCode suspend.
+    DCHECK(target != self);
+    if (!art::Runtime::Current()->GetThreadList()->Resume(target,
+                                                          art::SuspendReason::kForUserCode)) {
+      // TODO Give a better error.
+      // This is most likely THREAD_NOT_SUSPENDED but we cannot really be sure.
+      return ERR(INTERNAL);
+    } else {
+      return OK;
+    }
+  } while (true);
+}
+
+// Suspends all the threads in the list at the same time. Getting this behavior is a little tricky
+// since we can have threads in the list multiple times. This generally doesn't matter unless the
+// current thread is present multiple times. In that case we need to suspend only once and either
+// return the same error code in all the other slots if it failed or return ERR(THREAD_SUSPENDED) if
+// it didn't. We also want to handle the current thread last to make the behavior of the code
+// simpler to understand.
+jvmtiError ThreadUtil::SuspendThreadList(jvmtiEnv* env,
+                                         jint request_count,
+                                         const jthread* threads,
+                                         jvmtiError* results) {
+  if (request_count == 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (results == nullptr || threads == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  // This is the list of the indexes in 'threads' and 'results' that correspond to the currently
+  // running thread. These indexes we need to handle specially since we need to only actually
+  // suspend a single time.
+  std::vector<jint> current_thread_indexes;
+  art::Thread* self = art::Thread::Current();
+  for (jint i = 0; i < request_count; i++) {
+    {
+      art::ScopedObjectAccess soa(self);
+      art::MutexLock mu(self, *art::Locks::thread_list_lock_);
+      if (threads[i] == nullptr || GetNativeThread(threads[i], soa) == self) {
+        current_thread_indexes.push_back(i);
+        continue;
+      }
+    }
+    results[i] = env->SuspendThread(threads[i]);
+  }
+  if (!current_thread_indexes.empty()) {
+    jint first_current_thread_index = current_thread_indexes[0];
+    // Suspend self.
+    jvmtiError res = env->SuspendThread(threads[first_current_thread_index]);
+    results[first_current_thread_index] = res;
+    // Fill in the rest of the error values as appropriate.
+    jvmtiError other_results = (res != OK) ? res : ERR(THREAD_SUSPENDED);
+    for (auto it = ++current_thread_indexes.begin(); it != current_thread_indexes.end(); ++it) {
+      results[*it] = other_results;
+    }
+  }
+  return OK;
+}
+
+jvmtiError ThreadUtil::ResumeThreadList(jvmtiEnv* env,
+                                        jint request_count,
+                                        const jthread* threads,
+                                        jvmtiError* results) {
+  if (request_count == 0) {
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (results == nullptr || threads == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+  for (jint i = 0; i < request_count; i++) {
+    results[i] = env->ResumeThread(threads[i]);
+  }
+  return OK;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_thread.h b/openjdkjvmti/ti_thread.h
new file mode 100644
index 0000000..03c49d7
--- /dev/null
+++ b/openjdkjvmti/ti_thread.h
@@ -0,0 +1,116 @@
+/* Copyright (C) 2017 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_TI_THREAD_H_
+#define ART_OPENJDKJVMTI_TI_THREAD_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/macros.h"
+#include "base/mutex.h"
+
+namespace art {
+class ArtField;
+class ScopedObjectAccessAlreadyRunnable;
+class Thread;
+}  // namespace art
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+class ThreadUtil {
+ public:
+  static void Register(EventHandler* event_handler);
+  static void Unregister();
+
+  // To be called when it is safe to cache data.
+  static void CacheData();
+
+  // Handle a jvmtiEnv going away.
+  static void RemoveEnvironment(jvmtiEnv* env);
+
+  static jvmtiError GetAllThreads(jvmtiEnv* env, jint* threads_count_ptr, jthread** threads_ptr);
+
+  static jvmtiError GetCurrentThread(jvmtiEnv* env, jthread* thread_ptr);
+
+  static jvmtiError GetThreadInfo(jvmtiEnv* env, jthread thread, jvmtiThreadInfo* info_ptr);
+
+  static jvmtiError GetThreadState(jvmtiEnv* env, jthread thread, jint* thread_state_ptr);
+
+  static jvmtiError SetThreadLocalStorage(jvmtiEnv* env, jthread thread, const void* data);
+  static jvmtiError GetThreadLocalStorage(jvmtiEnv* env, jthread thread, void** data_ptr);
+
+  static jvmtiError RunAgentThread(jvmtiEnv* env,
+                                   jthread thread,
+                                   jvmtiStartFunction proc,
+                                   const void* arg,
+                                   jint priority);
+
+  static jvmtiError SuspendThread(jvmtiEnv* env, jthread thread);
+  static jvmtiError ResumeThread(jvmtiEnv* env, jthread thread);
+
+  static jvmtiError SuspendThreadList(jvmtiEnv* env,
+                                      jint request_count,
+                                      const jthread* threads,
+                                      jvmtiError* results);
+  static jvmtiError ResumeThreadList(jvmtiEnv* env,
+                                     jint request_count,
+                                     const jthread* threads,
+                                     jvmtiError* results);
+
+  static art::Thread* GetNativeThread(jthread thread,
+                                      const art::ScopedObjectAccessAlreadyRunnable& soa)
+      REQUIRES_SHARED(art::Locks::mutator_lock_)
+      REQUIRES(art::Locks::thread_list_lock_);
+
+ private:
+  // We need to make sure only one thread tries to suspend threads at a time so we can get the
+  // 'suspend-only-once' behavior the spec requires. Internally, ART considers suspension to be a
+  // counted state, allowing a single thread to be suspended multiple times by different users. This
+  // makes mapping into the JVMTI idea of thread suspension difficult. We have decided to split the
+  // difference and ensure that JVMTI tries to treat suspension as the boolean flag as much as
+  // possible with the suspend/resume methods but only do best effort. On the other hand
+  // GetThreadState will be totally accurate as much as possible. This means that calling
+  // ResumeThread on a thread that has state JVMTI_THREAD_STATE_SUSPENDED will not necessarily
+  // cause the thread to wake up if the thread is suspended for the debugger or gc or something.
+  static jvmtiError SuspendSelf(art::Thread* self)
+      REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
+  static jvmtiError SuspendOther(art::Thread* self, jthread target_jthread)
+      REQUIRES(!art::Locks::mutator_lock_, !art::Locks::user_code_suspension_lock_);
+
+  static art::ArtField* context_class_loader_;
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_THREAD_H_
diff --git a/openjdkjvmti/ti_threadgroup.cc b/openjdkjvmti/ti_threadgroup.cc
new file mode 100644
index 0000000..c0597ad
--- /dev/null
+++ b/openjdkjvmti/ti_threadgroup.cc
@@ -0,0 +1,279 @@
+/* Copyright (C) 2017 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 "ti_threadgroup.h"
+
+#include "art_field-inl.h"
+#include "art_jvmti.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "handle_scope-inl.h"
+#include "jni_internal.h"
+#include "mirror/class.h"
+#include "mirror/object-inl.h"
+#include "mirror/string.h"
+#include "obj_ptr.h"
+#include "object_lock.h"
+#include "runtime.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "well_known_classes.h"
+
+namespace openjdkjvmti {
+
+
+jvmtiError ThreadGroupUtil::GetTopThreadGroups(jvmtiEnv* env,
+                                               jint* group_count_ptr,
+                                               jthreadGroup** groups_ptr) {
+  // We only have a single top group. So we can take the current thread and move upwards.
+  if (group_count_ptr == nullptr || groups_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  art::Runtime* runtime = art::Runtime::Current();
+  if (runtime == nullptr) {
+    // Must be starting the runtime, or dying.
+    return ERR(WRONG_PHASE);
+  }
+
+  jobject sys_thread_group = runtime->GetSystemThreadGroup();
+  if (sys_thread_group == nullptr) {
+    // Seems we're still starting up.
+    return ERR(WRONG_PHASE);
+  }
+
+  unsigned char* data;
+  jvmtiError result = env->Allocate(sizeof(jthreadGroup), &data);
+  if (result != ERR(NONE)) {
+    return result;
+  }
+
+  jthreadGroup* groups = reinterpret_cast<jthreadGroup*>(data);
+  *groups =
+      reinterpret_cast<JNIEnv*>(art::Thread::Current()->GetJniEnv())->NewLocalRef(sys_thread_group);
+  *groups_ptr = groups;
+  *group_count_ptr = 1;
+
+  return ERR(NONE);
+}
+
+jvmtiError ThreadGroupUtil::GetThreadGroupInfo(jvmtiEnv* env,
+                                               jthreadGroup group,
+                                               jvmtiThreadGroupInfo* info_ptr) {
+  if (group == nullptr) {
+    return ERR(INVALID_THREAD_GROUP);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+  if (soa.Env()->IsInstanceOf(group, art::WellKnownClasses::java_lang_ThreadGroup) == JNI_FALSE) {
+    return ERR(INVALID_THREAD_GROUP);
+  }
+
+  art::ObjPtr<art::mirror::Object> obj = soa.Decode<art::mirror::Object>(group);
+
+  // Do the name first. It's the only thing that can fail.
+  {
+    art::ArtField* name_field =
+        art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_name);
+    CHECK(name_field != nullptr);
+    art::ObjPtr<art::mirror::String> name_obj =
+        art::ObjPtr<art::mirror::String>::DownCast(name_field->GetObject(obj));
+    std::string tmp_str;
+    const char* tmp_cstr;
+    if (name_obj == nullptr) {
+      tmp_cstr = "";
+    } else {
+      tmp_str = name_obj->ToModifiedUtf8();
+      tmp_cstr = tmp_str.c_str();
+    }
+    jvmtiError result;
+    JvmtiUniquePtr<char[]> copy = CopyString(env, tmp_cstr, &result);
+    if (copy == nullptr) {
+      return result;
+    }
+    info_ptr->name = copy.release();
+  }
+
+  // Parent.
+  {
+    art::ArtField* parent_field =
+        art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_parent);
+    CHECK(parent_field != nullptr);
+    art::ObjPtr<art::mirror::Object> parent_group = parent_field->GetObject(obj);
+    info_ptr->parent = parent_group == nullptr
+                           ? nullptr
+                           : soa.AddLocalReference<jthreadGroup>(parent_group);
+  }
+
+  // Max priority.
+  {
+    art::ArtField* prio_field = obj->GetClass()->FindDeclaredInstanceField("maxPriority", "I");
+    CHECK(prio_field != nullptr);
+    info_ptr->max_priority = static_cast<jint>(prio_field->GetInt(obj));
+  }
+
+  // Daemon.
+  {
+    art::ArtField* daemon_field = obj->GetClass()->FindDeclaredInstanceField("daemon", "Z");
+    CHECK(daemon_field != nullptr);
+    info_ptr->is_daemon = daemon_field->GetBoolean(obj) == 0 ? JNI_FALSE : JNI_TRUE;
+  }
+
+  return ERR(NONE);
+}
+
+
+static bool IsInDesiredThreadGroup(art::Handle<art::mirror::Object> desired_thread_group,
+                                   art::ObjPtr<art::mirror::Object> peer)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  CHECK(desired_thread_group != nullptr);
+
+  art::ArtField* thread_group_field =
+      art::jni::DecodeArtField(art::WellKnownClasses::java_lang_Thread_group);
+  DCHECK(thread_group_field != nullptr);
+  art::ObjPtr<art::mirror::Object> group = thread_group_field->GetObject(peer);
+  return (group == desired_thread_group.Get());
+}
+
+static void GetThreads(art::Handle<art::mirror::Object> thread_group,
+                       std::vector<art::ObjPtr<art::mirror::Object>>* thread_peers)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) REQUIRES(!art::Locks::thread_list_lock_) {
+  CHECK(thread_group != nullptr);
+
+  art::MutexLock mu(art::Thread::Current(), *art::Locks::thread_list_lock_);
+  for (art::Thread* t : art::Runtime::Current()->GetThreadList()->GetList()) {
+    if (t->IsStillStarting()) {
+      continue;
+    }
+    art::ObjPtr<art::mirror::Object> peer = t->GetPeerFromOtherThread();
+    if (peer == nullptr) {
+      continue;
+    }
+    if (IsInDesiredThreadGroup(thread_group, peer)) {
+      thread_peers->push_back(peer);
+    }
+  }
+}
+
+static void GetChildThreadGroups(art::Handle<art::mirror::Object> thread_group,
+                                 std::vector<art::ObjPtr<art::mirror::Object>>* thread_groups)
+    REQUIRES_SHARED(art::Locks::mutator_lock_) {
+  CHECK(thread_group != nullptr);
+
+  // Get the ThreadGroup[] "groups" out of this thread group...
+  art::ArtField* groups_field =
+      art::jni::DecodeArtField(art::WellKnownClasses::java_lang_ThreadGroup_groups);
+  art::ObjPtr<art::mirror::Object> groups_array = groups_field->GetObject(thread_group.Get());
+
+  if (groups_array == nullptr) {
+    return;
+  }
+  CHECK(groups_array->IsObjectArray());
+
+  art::ObjPtr<art::mirror::ObjectArray<art::mirror::Object>> groups_array_as_array =
+      groups_array->AsObjectArray<art::mirror::Object>();
+
+  // Copy all non-null elements.
+  for (int32_t i = 0; i < groups_array_as_array->GetLength(); ++i) {
+    art::ObjPtr<art::mirror::Object> entry = groups_array_as_array->Get(i);
+    if (entry != nullptr) {
+      thread_groups->push_back(entry);
+    }
+  }
+}
+
+jvmtiError ThreadGroupUtil::GetThreadGroupChildren(jvmtiEnv* env,
+                                                   jthreadGroup group,
+                                                   jint* thread_count_ptr,
+                                                   jthread** threads_ptr,
+                                                   jint* group_count_ptr,
+                                                   jthreadGroup** groups_ptr) {
+  if (group == nullptr) {
+    return ERR(INVALID_THREAD_GROUP);
+  }
+
+  art::ScopedObjectAccess soa(art::Thread::Current());
+
+  if (!soa.Env()->IsInstanceOf(group, art::WellKnownClasses::java_lang_ThreadGroup)) {
+    return ERR(INVALID_THREAD_GROUP);
+  }
+
+  art::StackHandleScope<1> hs(soa.Self());
+  art::Handle<art::mirror::Object> thread_group = hs.NewHandle(
+      soa.Decode<art::mirror::Object>(group));
+
+  art::ObjectLock<art::mirror::Object> thread_group_lock(soa.Self(), thread_group);
+
+  std::vector<art::ObjPtr<art::mirror::Object>> thread_peers;
+  GetThreads(thread_group, &thread_peers);
+
+  std::vector<art::ObjPtr<art::mirror::Object>> thread_groups;
+  GetChildThreadGroups(thread_group, &thread_groups);
+
+  JvmtiUniquePtr<jthread[]> peers_uptr;
+  if (!thread_peers.empty()) {
+    jvmtiError res;
+    peers_uptr = AllocJvmtiUniquePtr<jthread[]>(env, thread_peers.size(), &res);
+    if (peers_uptr == nullptr) {
+      return res;
+    }
+  }
+
+  JvmtiUniquePtr<jthreadGroup[]> group_uptr;
+  if (!thread_groups.empty()) {
+    jvmtiError res;
+    group_uptr = AllocJvmtiUniquePtr<jthreadGroup[]>(env, thread_groups.size(), &res);
+    if (group_uptr == nullptr) {
+      return res;
+    }
+  }
+
+  // Can't fail anymore from here on.
+
+  // Copy data into out buffers.
+  for (size_t i = 0; i != thread_peers.size(); ++i) {
+    peers_uptr[i] = soa.AddLocalReference<jthread>(thread_peers[i]);
+  }
+  for (size_t i = 0; i != thread_groups.size(); ++i) {
+    group_uptr[i] = soa.AddLocalReference<jthreadGroup>(thread_groups[i]);
+  }
+
+  *thread_count_ptr = static_cast<jint>(thread_peers.size());
+  *threads_ptr = peers_uptr.release();
+  *group_count_ptr = static_cast<jint>(thread_groups.size());
+  *groups_ptr = group_uptr.release();
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_threadgroup.h b/openjdkjvmti/ti_threadgroup.h
new file mode 100644
index 0000000..4911566
--- /dev/null
+++ b/openjdkjvmti/ti_threadgroup.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2017 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_TI_THREADGROUP_H_
+#define ART_OPENJDKJVMTI_TI_THREADGROUP_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class ThreadGroupUtil {
+ public:
+  static jvmtiError GetTopThreadGroups(jvmtiEnv* env,
+                                       jint* group_count_ptr,
+                                       jthreadGroup** groups_ptr);
+
+  static jvmtiError GetThreadGroupInfo(jvmtiEnv* env,
+                                       jthreadGroup group,
+                                       jvmtiThreadGroupInfo* info_ptr);
+
+  static jvmtiError GetThreadGroupChildren(jvmtiEnv* env,
+                                           jthreadGroup group,
+                                           jint* thread_count_ptr,
+                                           jthread** threads_ptr,
+                                           jint* group_count_ptr,
+                                           jthreadGroup** groups_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_THREADGROUP_H_
diff --git a/openjdkjvmti/ti_timers.cc b/openjdkjvmti/ti_timers.cc
new file mode 100644
index 0000000..24fb041
--- /dev/null
+++ b/openjdkjvmti/ti_timers.cc
@@ -0,0 +1,93 @@
+/* Copyright (C) 2017 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 "ti_timers.h"
+
+#include <limits>
+
+#ifndef __APPLE__
+#include <time.h>
+#else
+#include <sys/time.h>
+#endif
+#include <unistd.h>
+
+#include "art_jvmti.h"
+#include "base/macros.h"
+
+namespace openjdkjvmti {
+
+jvmtiError TimerUtil::GetAvailableProcessors(jvmtiEnv* env ATTRIBUTE_UNUSED,
+                                             jint* processor_count_ptr) {
+  if (processor_count_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  *processor_count_ptr = static_cast<jint>(sysconf(_SC_NPROCESSORS_CONF));
+
+  return ERR(NONE);
+}
+
+jvmtiError TimerUtil::GetTimerInfo(jvmtiEnv* env ATTRIBUTE_UNUSED, jvmtiTimerInfo* info_ptr) {
+  if (info_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+  info_ptr->max_value = static_cast<jlong>(std::numeric_limits<uint64_t>::max());
+  info_ptr->may_skip_forward = JNI_TRUE;
+  info_ptr->may_skip_backward = JNI_TRUE;
+  info_ptr->kind = jvmtiTimerKind::JVMTI_TIMER_ELAPSED;
+
+  return ERR(NONE);
+}
+
+jvmtiError TimerUtil::GetTime(jvmtiEnv* env ATTRIBUTE_UNUSED, jlong* nanos_ptr) {
+  if (nanos_ptr == nullptr) {
+    return ERR(NULL_POINTER);
+  }
+
+#ifndef __APPLE__
+  // Use the same implementation as System.nanoTime.
+  struct timespec now;
+  clock_gettime(CLOCK_MONOTONIC, &now);
+  *nanos_ptr = now.tv_sec * 1000000000LL + now.tv_nsec;
+#else
+  // No CLOCK_MONOTONIC support on older Mac OS.
+  struct timeval t;
+  t.tv_sec = t.tv_usec = 0;
+  gettimeofday(&t, NULL);
+  *nanos_ptr = static_cast<jlong>(t.tv_sec)*1000000000LL + static_cast<jlong>(t.tv_usec)*1000LL;
+#endif
+
+  return ERR(NONE);
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/ti_timers.h b/openjdkjvmti/ti_timers.h
new file mode 100644
index 0000000..892205a
--- /dev/null
+++ b/openjdkjvmti/ti_timers.h
@@ -0,0 +1,51 @@
+/* Copyright (C) 2017 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_TI_TIMERS_H_
+#define ART_OPENJDKJVMTI_TI_TIMERS_H_
+
+#include "jni.h"
+#include "jvmti.h"
+
+namespace openjdkjvmti {
+
+class TimerUtil {
+ public:
+  static jvmtiError GetAvailableProcessors(jvmtiEnv* env, jint* processor_count_ptr);
+
+  static jvmtiError GetTimerInfo(jvmtiEnv* env, jvmtiTimerInfo* info_ptr);
+
+  static jvmtiError GetTime(jvmtiEnv* env, jlong* nanos_ptr);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TI_TIMERS_H_
diff --git a/openjdkjvmti/transform.cc b/openjdkjvmti/transform.cc
new file mode 100644
index 0000000..1d7f137
--- /dev/null
+++ b/openjdkjvmti/transform.cc
@@ -0,0 +1,152 @@
+/* Copyright (C) 2016 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 <unordered_map>
+#include <unordered_set>
+
+#include "transform.h"
+
+#include "art_method.h"
+#include "base/array_ref.h"
+#include "class_linker.h"
+#include "dex_file.h"
+#include "dex_file_types.h"
+#include "events-inl.h"
+#include "gc_root-inl.h"
+#include "globals.h"
+#include "jni_env_ext-inl.h"
+#include "jvalue.h"
+#include "jvmti.h"
+#include "linear_alloc.h"
+#include "mem_map.h"
+#include "mirror/array.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_ext.h"
+#include "mirror/class_loader-inl.h"
+#include "mirror/string-inl.h"
+#include "oat_file.h"
+#include "scoped_thread_state_change-inl.h"
+#include "stack.h"
+#include "thread_list.h"
+#include "ti_redefine.h"
+#include "transform.h"
+#include "utf.h"
+#include "utils/dex_cache_arrays_layout-inl.h"
+
+namespace openjdkjvmti {
+
+jvmtiError Transformer::RetransformClassesDirect(
+      ArtJvmTiEnv* env,
+      EventHandler* event_handler,
+      art::Thread* self,
+      /*in-out*/std::vector<ArtClassDefinition>* definitions) {
+  for (ArtClassDefinition& def : *definitions) {
+    jint new_len = -1;
+    unsigned char* new_data = nullptr;
+    art::ArrayRef<const unsigned char> dex_data = def.GetDexData();
+    event_handler->DispatchEvent<ArtJvmtiEvent::kClassFileLoadHookRetransformable>(
+        self,
+        GetJniEnv(env),
+        def.GetClass(),
+        def.GetLoader(),
+        def.GetName().c_str(),
+        def.GetProtectionDomain(),
+        static_cast<jint>(dex_data.size()),
+        dex_data.data(),
+        /*out*/&new_len,
+        /*out*/&new_data);
+    def.SetNewDexData(env, new_len, new_data);
+  }
+  return OK;
+}
+
+jvmtiError Transformer::RetransformClasses(ArtJvmTiEnv* env,
+                                           EventHandler* event_handler,
+                                           art::Runtime* runtime,
+                                           art::Thread* self,
+                                           jint class_count,
+                                           const jclass* classes,
+                                           /*out*/std::string* error_msg) {
+  if (env == nullptr) {
+    *error_msg = "env was null!";
+    return ERR(INVALID_ENVIRONMENT);
+  } else if (class_count < 0) {
+    *error_msg = "class_count was less then 0";
+    return ERR(ILLEGAL_ARGUMENT);
+  } else if (class_count == 0) {
+    // We don't actually need to do anything. Just return OK.
+    return OK;
+  } else if (classes == nullptr) {
+    *error_msg = "null classes!";
+    return ERR(NULL_POINTER);
+  }
+  // A holder that will Deallocate all the class bytes buffers on destruction.
+  std::vector<ArtClassDefinition> definitions;
+  jvmtiError res = OK;
+  for (jint i = 0; i < class_count; i++) {
+    jboolean is_modifiable = JNI_FALSE;
+    res = env->IsModifiableClass(classes[i], &is_modifiable);
+    if (res != OK) {
+      return res;
+    } else if (!is_modifiable) {
+      return ERR(UNMODIFIABLE_CLASS);
+    }
+    ArtClassDefinition def;
+    res = def.Init(env, classes[i]);
+    if (res != OK) {
+      return res;
+    }
+    definitions.push_back(std::move(def));
+  }
+  res = RetransformClassesDirect(env, event_handler, self, &definitions);
+  if (res != OK) {
+    return res;
+  }
+  return Redefiner::RedefineClassesDirect(env, runtime, self, definitions, error_msg);
+}
+
+// TODO Move this somewhere else, ti_class?
+jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location) {
+  JNIEnv* jni_env = nullptr;
+  jint ret = env->art_vm->GetEnv(reinterpret_cast<void**>(&jni_env), JNI_VERSION_1_1);
+  if (ret != JNI_OK) {
+    // TODO Different error might be better?
+    return ERR(INTERNAL);
+  }
+  art::ScopedObjectAccess soa(jni_env);
+  art::StackHandleScope<1> hs(art::Thread::Current());
+  art::Handle<art::mirror::Class> hs_klass(hs.NewHandle(soa.Decode<art::mirror::Class>(klass)));
+  const art::DexFile& dex = hs_klass->GetDexFile();
+  *location = dex.GetLocation();
+  return OK;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/transform.h b/openjdkjvmti/transform.h
new file mode 100644
index 0000000..6bbe60a
--- /dev/null
+++ b/openjdkjvmti/transform.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 2016 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_TRANSFORM_H_
+#define ART_OPENJDKJVMTI_TRANSFORM_H_
+
+#include <string>
+
+#include <jni.h>
+#include "jvmti.h"
+
+#include "art_jvmti.h"
+#include "ti_class_definition.h"
+
+namespace openjdkjvmti {
+
+class EventHandler;
+
+jvmtiError GetClassLocation(ArtJvmTiEnv* env, jclass klass, /*out*/std::string* location);
+
+class Transformer {
+ public:
+  static jvmtiError RetransformClassesDirect(
+      ArtJvmTiEnv* env,
+      EventHandler* event_handler,
+      art::Thread* self,
+      /*in-out*/std::vector<ArtClassDefinition>* definitions);
+
+  static jvmtiError RetransformClasses(ArtJvmTiEnv* env,
+                                       EventHandler* event_handler,
+                                       art::Runtime* runtime,
+                                       art::Thread* self,
+                                       jint class_count,
+                                       const jclass* classes,
+                                       /*out*/std::string* error_msg);
+};
+
+}  // namespace openjdkjvmti
+
+#endif  // ART_OPENJDKJVMTI_TRANSFORM_H_
+