Wire up check JNI force copy mode.

Increase check JNI checks.
Break apart jni_internal.h in to jni_env_ext.h and java_vm_ext.h.
Fix the abuse of ScopedObjectAccess/annotalysis by ScopedCheck in the case
of VM routines.
Make class loader override and shared library class loader JNI global
references rather than mirror pointers.
Clean-ups to native bridge.

Change-Id: If7c6110b5aade7a402bfb67534af86a7b2cdeb55
diff --git a/runtime/Android.mk b/runtime/Android.mk
index 0b13a3f..1e037f5 100644
--- a/runtime/Android.mk
+++ b/runtime/Android.mk
@@ -81,6 +81,7 @@
   interpreter/interpreter.cc \
   interpreter/interpreter_common.cc \
   interpreter/interpreter_switch_impl.cc \
+  java_vm_ext.cc \
   jdwp/jdwp_event.cc \
   jdwp/jdwp_expand_buf.cc \
   jdwp/jdwp_handler.cc \
@@ -88,6 +89,7 @@
   jdwp/jdwp_request.cc \
   jdwp/jdwp_socket.cc \
   jdwp/object_registry.cc \
+  jni_env_ext.cc \
   jni_internal.cc \
   jobject_comparator.cc \
   mem_map.cc \
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index c0a865f..abe0aa0 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -35,6 +35,7 @@
 Mutex* Locks::breakpoint_lock_ = nullptr;
 ReaderWriterMutex* Locks::classlinker_classes_lock_ = nullptr;
 ReaderWriterMutex* Locks::heap_bitmap_lock_ = nullptr;
+Mutex* Locks::jni_libraries_lock_ = nullptr;
 Mutex* Locks::logging_lock_ = nullptr;
 Mutex* Locks::mem_maps_lock_ = nullptr;
 Mutex* Locks::modify_ldt_lock_ = nullptr;
@@ -834,6 +835,7 @@
     DCHECK(breakpoint_lock_ != nullptr);
     DCHECK(classlinker_classes_lock_ != nullptr);
     DCHECK(heap_bitmap_lock_ != nullptr);
+    DCHECK(jni_libraries_lock_ != nullptr);
     DCHECK(logging_lock_ != nullptr);
     DCHECK(mutator_lock_ != nullptr);
     DCHECK(thread_list_lock_ != nullptr);
@@ -878,6 +880,10 @@
     DCHECK(thread_list_lock_ == nullptr);
     thread_list_lock_ = new Mutex("thread list lock", current_lock_level);
 
+    UPDATE_CURRENT_LOCK_LEVEL(kJniLoadLibraryLock);
+    DCHECK(jni_libraries_lock_ == nullptr);
+    jni_libraries_lock_ = new Mutex("JNI shared libraries map lock", current_lock_level);
+
     UPDATE_CURRENT_LOCK_LEVEL(kBreakpointLock);
     DCHECK(breakpoint_lock_ == nullptr);
     breakpoint_lock_ = new Mutex("breakpoint lock", current_lock_level);
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index d898e49..fd76629 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -74,7 +74,6 @@
   kDefaultMutexLevel,
   kMarkSweepLargeObjectLock,
   kPinTableLock,
-  kLoadLibraryLock,
   kJdwpObjectRegistryLock,
   kModifyLdtLock,
   kAllocatedThreadIdsLock,
@@ -83,6 +82,7 @@
   kBreakpointLock,
   kMonitorLock,
   kMonitorListLock,
+  kJniLoadLibraryLock,
   kThreadListLock,
   kBreakpointInvokeLock,
   kDeoptimizationLock,
@@ -561,8 +561,11 @@
   // attaching and detaching.
   static Mutex* thread_list_lock_ ACQUIRED_AFTER(trace_lock_);
 
+  // Guards maintaining loading library data structures.
+  static Mutex* jni_libraries_lock_ ACQUIRED_AFTER(thread_list_lock_);
+
   // Guards breakpoints.
-  static Mutex* breakpoint_lock_ ACQUIRED_AFTER(thread_list_lock_);
+  static Mutex* breakpoint_lock_ ACQUIRED_AFTER(jni_libraries_lock_);
 
   // Guards lists of classes within the class linker.
   static ReaderWriterMutex* classlinker_classes_lock_ ACQUIRED_AFTER(breakpoint_lock_);
diff --git a/runtime/check_jni.cc b/runtime/check_jni.cc
index a530594..99277a0 100644
--- a/runtime/check_jni.cc
+++ b/runtime/check_jni.cc
@@ -25,6 +25,7 @@
 #include "dex_file-inl.h"
 #include "field_helper.h"
 #include "gc/space/space.h"
+#include "java_vm_ext.h"
 #include "mirror/art_field-inl.h"
 #include "mirror/art_method-inl.h"
 #include "mirror/class-inl.h"
@@ -35,62 +36,16 @@
 #include "runtime.h"
 #include "scoped_thread_state_change.h"
 #include "thread.h"
+#include "well_known_classes.h"
 
 namespace art {
 
-static void JniAbort(const char* jni_function_name, const char* msg) {
-  Thread* self = Thread::Current();
-  ScopedObjectAccess soa(self);
-  mirror::ArtMethod* current_method = self->GetCurrentMethod(nullptr);
-
-  std::ostringstream os;
-  os << "JNI DETECTED ERROR IN APPLICATION: " << msg;
-
-  if (jni_function_name != nullptr) {
-    os << "\n    in call to " << jni_function_name;
-  }
-  // TODO: is this useful given that we're about to dump the calling thread's stack?
-  if (current_method != nullptr) {
-    os << "\n    from " << PrettyMethod(current_method);
-  }
-  os << "\n";
-  self->Dump(os);
-
-  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-  if (vm->check_jni_abort_hook != nullptr) {
-    vm->check_jni_abort_hook(vm->check_jni_abort_hook_data, os.str());
-  } else {
-    // Ensure that we get a native stack trace for this thread.
-    self->TransitionFromRunnableToSuspended(kNative);
-    LOG(FATAL) << os.str();
-    self->TransitionFromSuspendedToRunnable();  // Unreachable, keep annotalysis happy.
-  }
-}
-
-static void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) {
-  std::string msg;
-  StringAppendV(&msg, fmt, ap);
-  JniAbort(jni_function_name, msg.c_str());
-}
-
-void JniAbortF(const char* jni_function_name, const char* fmt, ...) {
-  va_list args;
-  va_start(args, fmt);
-  JniAbortV(jni_function_name, fmt, args);
-  va_end(args);
-}
-
 /*
  * ===========================================================================
  *      JNI function helpers
  * ===========================================================================
  */
 
-static bool IsHandleScopeLocalRef(JNIEnv* env, jobject localRef) {
-  return GetIndirectRefKind(localRef) == kHandleScopeOrInvalid &&
-      reinterpret_cast<JNIEnvExt*>(env)->self->HandleScopeContains(localRef);
-}
-
 // Flags passed into ScopedCheck.
 #define kFlag_Default       0x0000
 
@@ -109,134 +64,88 @@
 #define kFlag_Invocation    0x8000      // Part of the invocation interface (JavaVM*).
 
 #define kFlag_ForceTrace    0x80000000  // Add this to a JNI function's flags if you want to trace every call.
-
-static const char* gBuiltInPrefixes[] = {
-  "Landroid/",
-  "Lcom/android/",
-  "Lcom/google/android/",
-  "Ldalvik/",
-  "Ljava/",
-  "Ljavax/",
-  "Llibcore/",
-  "Lorg/apache/harmony/",
-  nullptr
+/*
+ * Java primitive types:
+ * B - jbyte
+ * C - jchar
+ * D - jdouble
+ * F - jfloat
+ * I - jint
+ * J - jlong
+ * S - jshort
+ * Z - jboolean (shown as true and false)
+ * V - void
+ *
+ * Java reference types:
+ * L - jobject
+ * a - jarray
+ * c - jclass
+ * s - jstring
+ * t - jthrowable
+ *
+ * JNI types:
+ * b - jboolean (shown as JNI_TRUE and JNI_FALSE)
+ * f - jfieldID
+ * i - JNI error value (JNI_OK, JNI_ERR, JNI_EDETACHED, JNI_EVERSION)
+ * m - jmethodID
+ * p - void*
+ * r - jint (for release mode arguments)
+ * u - const char* (Modified UTF-8)
+ * z - jsize (for lengths; use i if negative values are okay)
+ * v - JavaVM*
+ * w - jobjectRefType
+ * E - JNIEnv*
+ * . - no argument; just print "..." (used for varargs JNI calls)
+ *
+ */
+union JniValueType {
+  jarray a;
+  jboolean b;
+  jclass c;
+  jfieldID f;
+  jint i;
+  jmethodID m;
+  const void* p;  // Pointer.
+  jint r;  // Release mode.
+  jstring s;
+  jthrowable t;
+  const char* u;  // Modified UTF-8.
+  JavaVM* v;
+  jobjectRefType w;
+  jsize z;
+  jbyte B;
+  jchar C;
+  jdouble D;
+  JNIEnv* E;
+  jfloat F;
+  jint I;
+  jlong J;
+  jobject L;
+  jshort S;
+  const void* V;  // void
+  jboolean Z;
 };
 
-static bool ShouldTrace(JavaVMExt* vm, mirror::ArtMethod* method)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  // If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages
-  // when a native method that matches the -Xjnitrace argument calls a JNI function
-  // such as NewByteArray.
-  // If -verbose:third-party-jni is on, we want to log any JNI function calls
-  // made by a third-party native method.
-  std::string class_name(method->GetDeclaringClassDescriptor());
-  if (!vm->trace.empty() && class_name.find(vm->trace) != std::string::npos) {
-    return true;
-  }
-  if (VLOG_IS_ON(third_party_jni)) {
-    // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look
-    // like part of Android.
-    for (size_t i = 0; gBuiltInPrefixes[i] != nullptr; ++i) {
-      if (StartsWith(class_name, gBuiltInPrefixes[i])) {
-        return false;
-      }
-    }
-    return true;
-  }
-  return false;
-}
-
 class ScopedCheck {
  public:
-  // For JNIEnv* functions.
-  explicit ScopedCheck(JNIEnv* env, int flags, const char* functionName)
-      SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
-      : soa_(env) {
-    Init(flags, functionName, true);
-    CheckThread(flags);
+  explicit ScopedCheck(int flags, const char* functionName, bool has_method = true)
+      : function_name_(functionName), flags_(flags), indent_(0), has_method_(has_method) {
   }
 
-  // For JavaVM* functions.
-  // TODO: it's not correct that this is a lock function, but making it so aids annotalysis.
-  explicit ScopedCheck(JavaVM* vm, bool has_method, const char* functionName)
-      SHARED_LOCK_FUNCTION(Locks::mutator_lock_)
-      : soa_(vm) {
-    Init(kFlag_Invocation, functionName, has_method);
-  }
-
-  ~ScopedCheck() UNLOCK_FUNCTION(Locks::mutator_lock_) {}
-
-  const ScopedObjectAccess& soa() {
-    return soa_;
-  }
-
-  bool ForceCopy() {
-    return Runtime::Current()->GetJavaVM()->force_copy;
-  }
+  ~ScopedCheck() {}
 
   // Checks that 'class_name' is a valid "fully-qualified" JNI class name, like "java/lang/Thread"
   // or "[Ljava/lang/Object;". A ClassLoader can actually normalize class names a couple of
   // times, so using "java.lang.Thread" instead of "java/lang/Thread" might work in some
   // circumstances, but this is incorrect.
-  void CheckClassName(const char* class_name) {
+  bool CheckClassName(const char* class_name) {
     if ((class_name == nullptr) || !IsValidJniClassName(class_name)) {
-      JniAbortF(function_name_,
-                "illegal class name '%s'\n"
-                "    (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')",
-                class_name);
+      AbortF("illegal class name '%s'\n"
+             "    (should be of the form 'package/Class', [Lpackage/Class;' or '[[B')",
+             class_name);
+      return false;
     }
-  }
-
-  /*
-   * Verify that the field is of the appropriate type.  If the field has an
-   * object type, "java_object" is the object we're trying to assign into it.
-   *
-   * Works for both static and instance fields.
-   */
-  void CheckFieldType(jvalue value, jfieldID fid, char prim, bool isStatic)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    StackHandleScope<1> hs(Thread::Current());
-    Handle<mirror::ArtField> f(hs.NewHandle(CheckFieldID(fid)));
-    if (f.Get() == nullptr) {
-      return;
-    }
-    mirror::Class* field_type = FieldHelper(f).GetType();
-    if (!field_type->IsPrimitive()) {
-      jobject java_object = value.l;
-      if (java_object != nullptr) {
-        mirror::Object* obj = soa_.Decode<mirror::Object*>(java_object);
-        // If java_object is a weak global ref whose referent has been cleared,
-        // obj will be NULL.  Otherwise, obj should always be non-NULL
-        // and valid.
-        if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj)) {
-          Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-          JniAbortF(function_name_, "field operation on invalid %s: %p",
-                    ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
-          return;
-        } else {
-          if (!obj->InstanceOf(field_type)) {
-            JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %s",
-                      PrettyField(f.Get()).c_str(), PrettyTypeOf(obj).c_str());
-            return;
-          }
-        }
-      }
-    } else if (field_type != Runtime::Current()->GetClassLinker()->FindPrimitiveClass(prim)) {
-      JniAbortF(function_name_, "attempt to set field %s with value of wrong type: %c",
-                PrettyField(f.Get()).c_str(), prim);
-      return;
-    }
-
-    if (isStatic != f.Get()->IsStatic()) {
-      if (isStatic) {
-        JniAbortF(function_name_, "accessing non-static field %s as static",
-                  PrettyField(f.Get()).c_str());
-      } else {
-        JniAbortF(function_name_, "accessing static field %s as non-static",
-                  PrettyField(f.Get()).c_str());
-      }
-      return;
-    }
+    return true;
   }
 
   /*
@@ -244,59 +153,87 @@
    *
    * Assumes "jobj" has already been validated.
    */
-  void CheckInstanceFieldID(jobject java_object, jfieldID fid)
+  bool CheckInstanceFieldID(ScopedObjectAccess& soa, jobject java_object, jfieldID fid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::Object* o = soa_.Decode<mirror::Object*>(java_object);
-    if (o == nullptr || !Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) {
+    mirror::Object* o = soa.Decode<mirror::Object*>(java_object);
+    if (o == nullptr) {
+      AbortF("field operation on NULL object: %p", java_object);
+      return false;
+    }
+    if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "field operation on invalid %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
-      return;
+      AbortF("field operation on invalid %s: %p",
+             ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(),
+             java_object);
+      return false;
     }
 
-    mirror::ArtField* f = CheckFieldID(fid);
+    mirror::ArtField* f = CheckFieldID(soa, fid);
     if (f == nullptr) {
-      return;
+      return false;
     }
     mirror::Class* c = o->GetClass();
     if (c->FindInstanceField(f->GetName(), f->GetTypeDescriptor()) == nullptr) {
-      JniAbortF(function_name_, "jfieldID %s not valid for an object of class %s",
-                PrettyField(f).c_str(), PrettyTypeOf(o).c_str());
+      AbortF("jfieldID %s not valid for an object of class %s",
+             PrettyField(f).c_str(), PrettyTypeOf(o).c_str());
+      return false;
     }
+    return true;
   }
 
   /*
    * Verify that the pointer value is non-NULL.
    */
-  void CheckNonNull(const void* ptr) {
-    if (ptr == nullptr) {
-      JniAbortF(function_name_, "non-nullable argument was NULL");
+  bool CheckNonNull(const void* ptr) {
+    if (UNLIKELY(ptr == nullptr)) {
+      AbortF("non-nullable argument was NULL");
+      return false;
     }
+    return true;
   }
 
   /*
    * Verify that the method's return type matches the type of call.
    * 'expectedType' will be "L" for all objects, including arrays.
    */
-  void CheckSig(jmethodID mid, const char* expectedType, bool isStatic)
+  bool CheckMethodAndSig(ScopedObjectAccess& soa, jobject jobj, jclass jc,
+                         jmethodID mid, Primitive::Type type, InvokeType invoke)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::ArtMethod* m = CheckMethodID(mid);
+    mirror::ArtMethod* m = CheckMethodID(soa, mid);
     if (m == nullptr) {
-      return;
+      return false;
     }
-    if (*expectedType != m->GetShorty()[0]) {
-      JniAbortF(function_name_, "the return type of %s does not match %s",
-                function_name_, PrettyMethod(m).c_str());
+    if (type != Primitive::GetType(m->GetShorty()[0])) {
+      AbortF("the return type of %s does not match %s", function_name_, PrettyMethod(m).c_str());
+      return false;
     }
-    if (isStatic != m->IsStatic()) {
-      if (isStatic) {
-        JniAbortF(function_name_, "calling non-static method %s with %s",
-                  PrettyMethod(m).c_str(), function_name_);
+    bool is_static = (invoke == kStatic);
+    if (is_static != m->IsStatic()) {
+      if (is_static) {
+        AbortF("calling non-static method %s with %s",
+               PrettyMethod(m).c_str(), function_name_);
       } else {
-        JniAbortF(function_name_, "calling static method %s with %s",
-                  PrettyMethod(m).c_str(), function_name_);
+        AbortF("calling static method %s with %s",
+               PrettyMethod(m).c_str(), function_name_);
+      }
+      return false;
+    }
+    if (invoke != kVirtual) {
+      mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+      if (!m->GetDeclaringClass()->IsAssignableFrom(c)) {
+        AbortF("can't call %s %s with class %s", invoke == kStatic ? "static" : "nonvirtual",
+            PrettyMethod(m).c_str(), PrettyClass(c).c_str());
+        return false;
       }
     }
+    if (invoke != kStatic) {
+      mirror::Object* o = soa.Decode<mirror::Object*>(jobj);
+      if (!o->InstanceOf(m->GetDeclaringClass())) {
+        AbortF("can't call %s on instance of %s", PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
+        return false;
+      }
+    }
+    return true;
   }
 
   /*
@@ -304,17 +241,18 @@
    *
    * Assumes "java_class" has already been validated.
    */
-  void CheckStaticFieldID(jclass java_class, jfieldID fid)
+  bool CheckStaticFieldID(ScopedObjectAccess& soa, jclass java_class, jfieldID fid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::Class* c = soa_.Decode<mirror::Class*>(java_class);
-    mirror::ArtField* f = CheckFieldID(fid);
+    mirror::Class* c = soa.Decode<mirror::Class*>(java_class);
+    mirror::ArtField* f = CheckFieldID(soa, fid);
     if (f == nullptr) {
-      return;
+      return false;
     }
     if (f->GetDeclaringClass() != c) {
-      JniAbortF(function_name_, "static jfieldID %p not valid for class %s",
-                fid, PrettyClass(c).c_str());
+      AbortF("static jfieldID %p not valid for class %s", fid, PrettyClass(c).c_str());
+      return false;
     }
+    return true;
   }
 
   /*
@@ -326,17 +264,18 @@
    *
    * Instances of "java_class" must be instances of the method's declaring class.
    */
-  void CheckStaticMethod(jclass java_class, jmethodID mid)
+  bool CheckStaticMethod(ScopedObjectAccess& soa, jclass java_class, jmethodID mid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::ArtMethod* m = CheckMethodID(mid);
+    mirror::ArtMethod* m = CheckMethodID(soa, mid);
     if (m == nullptr) {
-      return;
+      return false;
     }
-    mirror::Class* c = soa_.Decode<mirror::Class*>(java_class);
+    mirror::Class* c = soa.Decode<mirror::Class*>(java_class);
     if (!m->GetDeclaringClass()->IsAssignableFrom(c)) {
-      JniAbortF(function_name_, "can't call static %s on class %s",
-                PrettyMethod(m).c_str(), PrettyClass(c).c_str());
+      AbortF("can't call static %s on class %s", PrettyMethod(m).c_str(), PrettyClass(c).c_str());
+      return false;
     }
+    return true;
   }
 
   /*
@@ -346,17 +285,18 @@
    * (Note the mid might point to a declaration in an interface; this
    * will be handled automatically by the instanceof check.)
    */
-  void CheckVirtualMethod(jobject java_object, jmethodID mid)
+  bool CheckVirtualMethod(ScopedObjectAccess& soa, jobject java_object, jmethodID mid)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    mirror::ArtMethod* m = CheckMethodID(mid);
+    mirror::ArtMethod* m = CheckMethodID(soa, mid);
     if (m == nullptr) {
-      return;
+      return false;
     }
-    mirror::Object* o = soa_.Decode<mirror::Object*>(java_object);
+    mirror::Object* o = soa.Decode<mirror::Object*>(java_object);
     if (!o->InstanceOf(m->GetDeclaringClass())) {
-      JniAbortF(function_name_, "can't call %s on instance of %s",
-                PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
+      AbortF("can't call %s on instance of %s", PrettyMethod(m).c_str(), PrettyTypeOf(o).c_str());
+      return false;
     }
+    return true;
   }
 
   /**
@@ -395,11 +335,10 @@
    *
    * Use the kFlag_NullableUtf flag where 'u' field(s) are nullable.
    */
-  void Check(bool entry, const char* fmt0, ...) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    va_list ap;
-
+  bool Check(ScopedObjectAccess& soa, bool entry, const char* fmt, JniValueType* args)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     mirror::ArtMethod* traceMethod = nullptr;
-    if (has_method_ && (!soa_.Vm()->trace.empty() || VLOG_IS_ON(third_party_jni))) {
+    if (has_method_ && soa.Vm()->IsTracingEnabled()) {
       // We need to guard some of the invocation interface's calls: a bad caller might
       // use DetachCurrentThread or GetEnv on a thread that's not yet attached.
       Thread* self = Thread::Current();
@@ -409,124 +348,14 @@
     }
 
     if (((flags_ & kFlag_ForceTrace) != 0) ||
-        (traceMethod != nullptr && ShouldTrace(soa_.Vm(), traceMethod))) {
-      va_start(ap, fmt0);
+        (traceMethod != nullptr && soa.Vm()->ShouldTrace(traceMethod))) {
       std::string msg;
-      for (const char* fmt = fmt0; *fmt;) {
-        char ch = *fmt++;
-        if (ch == 'B') {  // jbyte
-          jbyte b = va_arg(ap, int);
-          if (b >= 0 && b < 10) {
-            StringAppendF(&msg, "%d", b);
-          } else {
-            StringAppendF(&msg, "%#x (%d)", b, b);
-          }
-        } else if (ch == 'C') {  // jchar
-          jchar c = va_arg(ap, int);
-          if (c < 0x7f && c >= ' ') {
-            StringAppendF(&msg, "U+%x ('%c')", c, c);
-          } else {
-            StringAppendF(&msg, "U+%x", c);
-          }
-        } else if (ch == 'F' || ch == 'D') {  // jfloat, jdouble
-          StringAppendF(&msg, "%g", va_arg(ap, double));
-        } else if (ch == 'I' || ch == 'S') {  // jint, jshort
-          StringAppendF(&msg, "%d", va_arg(ap, int));
-        } else if (ch == 'J') {  // jlong
-          StringAppendF(&msg, "%" PRId64, va_arg(ap, jlong));
-        } else if (ch == 'Z') {  // jboolean
-          StringAppendF(&msg, "%s", va_arg(ap, int) ? "true" : "false");
-        } else if (ch == 'V') {  // void
-          msg += "void";
-        } else if (ch == 'v') {  // JavaVM*
-          JavaVM* vm = va_arg(ap, JavaVM*);
-          StringAppendF(&msg, "(JavaVM*)%p", vm);
-        } else if (ch == 'E') {  // JNIEnv*
-          JNIEnv* env = va_arg(ap, JNIEnv*);
-          StringAppendF(&msg, "(JNIEnv*)%p", env);
-        } else if (ch == 'L' || ch == 'a' || ch == 's') {  // jobject, jarray, jstring
-          // For logging purposes, these are identical.
-          jobject o = va_arg(ap, jobject);
-          if (o == nullptr) {
-            msg += "NULL";
-          } else {
-            StringAppendF(&msg, "%p", o);
-          }
-        } else if (ch == 'b') {  // jboolean (JNI-style)
-          jboolean b = va_arg(ap, int);
-          msg += (b ? "JNI_TRUE" : "JNI_FALSE");
-        } else if (ch == 'c') {  // jclass
-          jclass jc = va_arg(ap, jclass);
-          mirror::Class* c = reinterpret_cast<mirror::Class*>(Thread::Current()->DecodeJObject(jc));
-          if (c == nullptr) {
-            msg += "NULL";
-          } else if (c == kInvalidIndirectRefObject ||
-              !Runtime::Current()->GetHeap()->IsValidObjectAddress(c)) {
-            StringAppendF(&msg, "INVALID POINTER:%p", jc);
-          } else if (!c->IsClass()) {
-            msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c);
-          } else {
-            msg += PrettyClass(c);
-            if (!entry) {
-              StringAppendF(&msg, " (%p)", jc);
-            }
-          }
-        } else if (ch == 'f') {  // jfieldID
-          jfieldID fid = va_arg(ap, jfieldID);
-          mirror::ArtField* f = reinterpret_cast<mirror::ArtField*>(fid);
-          msg += PrettyField(f);
-          if (!entry) {
-            StringAppendF(&msg, " (%p)", fid);
-          }
-        } else if (ch == 'z') {  // non-negative jsize
-          // You might expect jsize to be size_t, but it's not; it's the same as jint.
-          // We only treat this specially so we can do the non-negative check.
-          // TODO: maybe this wasn't worth it?
-          jint i = va_arg(ap, jint);
-          StringAppendF(&msg, "%d", i);
-        } else if (ch == 'm') {  // jmethodID
-          jmethodID mid = va_arg(ap, jmethodID);
-          mirror::ArtMethod* m = reinterpret_cast<mirror::ArtMethod*>(mid);
-          msg += PrettyMethod(m);
-          if (!entry) {
-            StringAppendF(&msg, " (%p)", mid);
-          }
-        } else if (ch == 'p') {  // void* ("pointer")
-          void* p = va_arg(ap, void*);
-          if (p == nullptr) {
-            msg += "NULL";
-          } else {
-            StringAppendF(&msg, "(void*) %p", p);
-          }
-        } else if (ch == 'r') {  // jint (release mode)
-          jint releaseMode = va_arg(ap, jint);
-          if (releaseMode == 0) {
-            msg += "0";
-          } else if (releaseMode == JNI_ABORT) {
-            msg += "JNI_ABORT";
-          } else if (releaseMode == JNI_COMMIT) {
-            msg += "JNI_COMMIT";
-          } else {
-            StringAppendF(&msg, "invalid release mode %d", releaseMode);
-          }
-        } else if (ch == 'u') {  // const char* (Modified UTF-8)
-          const char* utf = va_arg(ap, const char*);
-          if (utf == nullptr) {
-            msg += "NULL";
-          } else {
-            StringAppendF(&msg, "\"%s\"", utf);
-          }
-        } else if (ch == '.') {
-          msg += "...";
-        } else {
-          JniAbortF(function_name_, "unknown trace format specifier: %c", ch);
-          return;
-        }
-        if (*fmt) {
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        TracePossibleHeapValue(soa, entry, fmt[i], args[i], &msg);
+        if (fmt[i + 1] != '\0') {
           StringAppendF(&msg, ", ");
         }
       }
-      va_end(ap);
 
       if ((flags_ & kFlag_ForceTrace) != 0) {
         LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")";
@@ -546,43 +375,227 @@
 
     // We always do the thorough checks on entry, and never on exit...
     if (entry) {
-      va_start(ap, fmt0);
-      for (const char* fmt = fmt0; *fmt; ++fmt) {
-        char ch = *fmt;
-        if (ch == 'a') {
-          CheckArray(va_arg(ap, jarray));
-        } else if (ch == 'c') {
-          CheckInstance(kClass, va_arg(ap, jclass));
-        } else if (ch == 'L') {
-          CheckObject(va_arg(ap, jobject));
-        } else if (ch == 'r') {
-          CheckReleaseMode(va_arg(ap, jint));
-        } else if (ch == 's') {
-          CheckInstance(kString, va_arg(ap, jstring));
-        } else if (ch == 'u') {
-          if ((flags_ & kFlag_Release) != 0) {
-            CheckNonNull(va_arg(ap, const char*));
-          } else {
-            bool nullable = ((flags_ & kFlag_NullableUtf) != 0);
-            CheckUtfString(va_arg(ap, const char*), nullable);
-          }
-        } else if (ch == 'z') {
-          CheckLengthPositive(va_arg(ap, jsize));
-        } else if (strchr("BCISZbfmpEv", ch) != nullptr) {
-          va_arg(ap, uint32_t);  // Skip this argument.
-        } else if (ch == 'D' || ch == 'F') {
-          va_arg(ap, double);  // Skip this argument.
-        } else if (ch == 'J') {
-          va_arg(ap, uint64_t);  // Skip this argument.
-        } else if (ch == '.') {
-        } else {
-          LOG(FATAL) << "Unknown check format specifier: " << ch;
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        if (!CheckPossibleHeapValue(soa, fmt[i], args[i])) {
+          return false;
         }
       }
-      va_end(ap);
     }
+    return true;
   }
 
+  bool CheckNonHeap(JavaVMExt* vm, bool entry, const char* fmt, JniValueType* args) {
+    bool should_trace = (flags_ & kFlag_ForceTrace) != 0;
+    if (!should_trace && vm->IsTracingEnabled()) {
+      // We need to guard some of the invocation interface's calls: a bad caller might
+      // use DetachCurrentThread or GetEnv on a thread that's not yet attached.
+      Thread* self = Thread::Current();
+      if ((flags_ & kFlag_Invocation) == 0 || self != nullptr) {
+        ScopedObjectAccess soa(self);
+        mirror::ArtMethod* traceMethod = self->GetCurrentMethod(nullptr);
+        should_trace = (traceMethod != nullptr && vm->ShouldTrace(traceMethod));
+      }
+    }
+    if (should_trace) {
+      std::string msg;
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        TraceNonHeapValue(fmt[i], args[i], &msg);
+        if (fmt[i + 1] != '\0') {
+          StringAppendF(&msg, ", ");
+        }
+      }
+
+      if ((flags_ & kFlag_ForceTrace) != 0) {
+        LOG(INFO) << "JNI: call to " << function_name_ << "(" << msg << ")";
+      } else if (entry) {
+        if (has_method_) {
+          Thread* self = Thread::Current();
+          ScopedObjectAccess soa(self);
+          mirror::ArtMethod* traceMethod = self->GetCurrentMethod(nullptr);
+          std::string methodName(PrettyMethod(traceMethod, false));
+          LOG(INFO) << "JNI: " << methodName << " -> " << function_name_ << "(" << msg << ")";
+          indent_ = methodName.size() + 1;
+        } else {
+          LOG(INFO) << "JNI: -> " << function_name_ << "(" << msg << ")";
+          indent_ = 0;
+        }
+      } else {
+        LOG(INFO) << StringPrintf("JNI: %*s<- %s returned %s", indent_, "", function_name_, msg.c_str());
+      }
+    }
+
+    // We always do the thorough checks on entry, and never on exit...
+    if (entry) {
+      for (size_t i = 0; fmt[i] != '\0'; ++i) {
+        if (!CheckNonHeapValue(fmt[i], args[i])) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  bool CheckReflectedMethod(ScopedObjectAccess& soa, jobject jmethod)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Object* method = soa.Decode<mirror::Object*>(jmethod);
+    if (method == nullptr) {
+      AbortF("expected non-null method");
+      return false;
+    }
+    mirror::Class* c = method->GetClass();
+    if (soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Method) != c &&
+        soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Constructor) != c) {
+      AbortF("expected java.lang.reflect.Method or "
+          "java.lang.reflect.Constructor but got object of type %s: %p",
+          PrettyTypeOf(method).c_str(), jmethod);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckConstructor(ScopedObjectAccess& soa, jmethodID mid)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::ArtMethod* method = soa.DecodeMethod(mid);
+    if (method == nullptr) {
+      AbortF("expected non-null constructor");
+      return false;
+    }
+    if (!method->IsConstructor() || method->IsStatic()) {
+      AbortF("expected a constructor but %s: %p", PrettyTypeOf(method).c_str(), mid);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckReflectedField(ScopedObjectAccess& soa, jobject jfield)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Object* field = soa.Decode<mirror::Object*>(jfield);
+    if (field == nullptr) {
+      AbortF("expected non-null java.lang.reflect.Field");
+      return false;
+    }
+    mirror::Class* c = field->GetClass();
+    if (soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_reflect_Field) != c) {
+      AbortF("expected java.lang.reflect.Field but got object of type %s: %p",
+             PrettyTypeOf(field).c_str(), jfield);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckThrowable(ScopedObjectAccess& soa, jthrowable jobj)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Object* obj = soa.Decode<mirror::Object*>(jobj);
+    if (!obj->GetClass()->IsThrowableClass()) {
+      AbortF("expected java.lang.Throwable but got object of type "
+             "%s: %p", PrettyTypeOf(obj).c_str(), obj);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckThrowableClass(ScopedObjectAccess& soa, jclass jc)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+    if (!c->IsThrowableClass()) {
+      AbortF("expected java.lang.Throwable class but got object of "
+             "type %s: %p", PrettyDescriptor(c).c_str(), c);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckReferenceKind(IndirectRefKind expected_kind, JavaVMExt* vm, Thread* self, jobject obj) {
+    IndirectRefKind found_kind;
+    if (expected_kind == kLocal) {
+      found_kind = GetIndirectRefKind(obj);
+      if (found_kind == kHandleScopeOrInvalid && self->HandleScopeContains(obj)) {
+        found_kind = kLocal;
+      }
+    } else {
+      found_kind = GetIndirectRefKind(obj);
+    }
+    if (obj != nullptr && found_kind != expected_kind) {
+      AbortF("expected reference of kind %s but found %s: %p",
+             ToStr<IndirectRefKind>(expected_kind).c_str(),
+             ToStr<IndirectRefKind>(GetIndirectRefKind(obj)).c_str(),
+             obj);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckInstantiableNonArray(ScopedObjectAccess& soa, jclass jc)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+    if (!c->IsInstantiableNonArray()) {
+      AbortF("can't make objects of type %s: %p", PrettyDescriptor(c).c_str(), c);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckPrimitiveArrayType(ScopedObjectAccess& soa, jarray array, Primitive::Type type)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (!CheckArray(soa, array)) {
+      return false;
+    }
+    mirror::Array* a = soa.Decode<mirror::Array*>(array);
+    if (a->GetClass()->GetComponentType()->GetPrimitiveType() != type) {
+      AbortF("incompatible array type %s expected %s[]: %p",
+             PrettyDescriptor(a->GetClass()).c_str(), PrettyDescriptor(type).c_str(), array);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckFieldAccess(ScopedObjectAccess& soa, jobject obj, jfieldID fid, bool is_static,
+                        Primitive::Type type)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (is_static && !CheckStaticFieldID(soa, down_cast<jclass>(obj), fid)) {
+      return false;
+    }
+    if (!is_static && !CheckInstanceFieldID(soa, obj, fid)) {
+      return false;
+    }
+    mirror::ArtField* field = soa.DecodeField(fid);
+    DCHECK(field != nullptr);  // Already checked by Check.
+    if (is_static != field->IsStatic()) {
+      AbortF("attempt to access %s field %s: %p",
+             field->IsStatic() ? "static" : "non-static", PrettyField(field).c_str(), fid);
+      return false;
+    }
+    if (type != field->GetTypeAsPrimitiveType()) {
+      AbortF("attempt to access field %s of type %s with the wrong type %s: %p",
+             PrettyField(field).c_str(), PrettyDescriptor(field->GetTypeDescriptor()).c_str(),
+             PrettyDescriptor(type).c_str(), fid);
+      return false;
+    }
+    if (is_static) {
+      mirror::Object* o = soa.Decode<mirror::Object*>(obj);
+      if (o == nullptr || !o->IsClass()) {
+        AbortF("attempt to access static field %s with a class argument of type %s: %p",
+               PrettyField(field).c_str(), PrettyTypeOf(o).c_str(), fid);
+        return false;
+      }
+      mirror::Class* c = o->AsClass();
+      if (field->GetDeclaringClass() != c) {
+        AbortF("attempt to access static field %s with an incompatible class argument of %s: %p",
+               PrettyField(field).c_str(), PrettyDescriptor(c).c_str(), fid);
+        return false;
+      }
+    } else {
+      mirror::Object* o = soa.Decode<mirror::Object*>(obj);
+      if (o == nullptr || !field->GetDeclaringClass()->IsAssignableFrom(o->GetClass())) {
+        AbortF("attempt to access field %s from an object argument of type %s: %p",
+               PrettyField(field).c_str(), PrettyTypeOf(o).c_str(), fid);
+        return false;
+      }
+    }
+    return true;
+  }
+
+ private:
   enum InstanceKind {
     kClass,
     kDirectByteBuffer,
@@ -598,7 +611,7 @@
    * Because we're looking at an object on the GC heap, we have to switch
    * to "running" mode before doing the checks.
    */
-  bool CheckInstance(InstanceKind kind, jobject java_object)
+  bool CheckInstance(ScopedObjectAccess& soa, InstanceKind kind, jobject java_object, bool null_ok)
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     const char* what = nullptr;
     switch (kind) {
@@ -622,15 +635,20 @@
     }
 
     if (java_object == nullptr) {
-      JniAbortF(function_name_, "%s received null %s", function_name_, what);
-      return false;
+      if (null_ok) {
+        return true;
+      } else {
+        AbortF("%s received NULL %s", function_name_, what);
+        return false;
+      }
     }
 
-    mirror::Object* obj = soa_.Decode<mirror::Object*>(java_object);
+    mirror::Object* obj = soa.Decode<mirror::Object*>(java_object);
     if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(obj)) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "%s is an invalid %s: %p (%p)",
-                what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object, obj);
+      AbortF("%s is an invalid %s: %p (%p)",
+             what, ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(),
+             java_object, obj);
       return false;
     }
 
@@ -652,114 +670,333 @@
       break;
     }
     if (!okay) {
-      JniAbortF(function_name_, "%s has wrong type: %s", what, PrettyTypeOf(obj).c_str());
+      AbortF("%s has wrong type: %s", what, PrettyTypeOf(obj).c_str());
       return false;
     }
 
     return true;
   }
 
- private:
-  // Set "has_method" to true if we have a valid thread with a method pointer.
-  // We won't have one before attaching a thread, after detaching a thread, or
-  // when shutting down the runtime.
-  void Init(int flags, const char* functionName, bool has_method) {
-    flags_ = flags;
-    function_name_ = functionName;
-    has_method_ = has_method;
+  /*
+   * Verify that the "mode" argument passed to a primitive array Release
+   * function is one of the valid values.
+   */
+  bool CheckReleaseMode(jint mode) {
+    if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) {
+      AbortF("unknown value for release mode: %d", mode);
+      return false;
+    }
+    return true;
   }
 
+  bool CheckPossibleHeapValue(ScopedObjectAccess& soa, char fmt, JniValueType arg)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    switch (fmt) {
+      case 'a':  // jarray
+        return CheckArray(soa, arg.a);
+      case 'c':  // jclass
+        return CheckInstance(soa, kClass, arg.c, false);
+      case 'f':  // jfieldID
+        return CheckFieldID(soa, arg.f) != nullptr;
+      case 'm':  // jmethodID
+        return CheckMethodID(soa, arg.m) != nullptr;
+      case 'r':  // release int
+        return CheckReleaseMode(arg.r);
+      case 's':  // jstring
+        return CheckInstance(soa, kString, arg.s, false);
+      case 't':  // jthrowable
+        return CheckInstance(soa, kThrowable, arg.t, false);
+      case 'E':  // JNIEnv*
+        return CheckThread(arg.E);
+      case 'L':  // jobject
+        return CheckInstance(soa, kObject, arg.L, true);
+      default:
+        return CheckNonHeapValue(fmt, arg);
+    }
+  }
+
+  bool CheckNonHeapValue(char fmt, JniValueType arg) {
+    switch (fmt) {
+      case '.':  // ...
+      case 'p':  // TODO: pointer - null or readable?
+      case 'v':  // JavaVM*
+      case 'B':  // jbyte
+      case 'C':  // jchar
+      case 'D':  // jdouble
+      case 'F':  // jfloat
+      case 'I':  // jint
+      case 'J':  // jlong
+      case 'S':  // jshort
+        break;  // Ignored.
+      case 'b':  // jboolean, why two? Fall-through.
+      case 'Z':
+        return CheckBoolean(arg.Z);
+      case 'u':  // utf8
+        if ((flags_ & kFlag_Release) != 0) {
+          return CheckNonNull(arg.u);
+        } else {
+          bool nullable = ((flags_ & kFlag_NullableUtf) != 0);
+          return CheckUtfString(arg.u, nullable);
+        }
+      case 'w':  // jobjectRefType
+        switch (arg.w) {
+          case JNIInvalidRefType:
+          case JNILocalRefType:
+          case JNIGlobalRefType:
+          case JNIWeakGlobalRefType:
+            break;
+          default:
+            AbortF("Unknown reference type");
+            return false;
+        }
+        break;
+      case 'z':  // jsize
+        return CheckLengthPositive(arg.z);
+      default:
+        AbortF("unknown format specifier: '%c'", fmt);
+        return false;
+    }
+    return true;
+  }
+
+  void TracePossibleHeapValue(ScopedObjectAccess& soa, bool entry, char fmt, JniValueType arg,
+                              std::string* msg)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    switch (fmt) {
+      case 'L':  // jobject fall-through.
+      case 'a':  // jarray fall-through.
+      case 's':  // jstring fall-through.
+      case 't':  // jthrowable fall-through.
+        if (arg.L == nullptr) {
+          *msg += "NULL";
+        } else {
+          StringAppendF(msg, "%p", arg.L);
+        }
+        break;
+      case 'c': {  // jclass
+        jclass jc = arg.c;
+        mirror::Class* c = soa.Decode<mirror::Class*>(jc);
+        if (c == nullptr) {
+          *msg += "NULL";
+        } else if (c == kInvalidIndirectRefObject ||
+            !Runtime::Current()->GetHeap()->IsValidObjectAddress(c)) {
+          StringAppendF(msg, "INVALID POINTER:%p", jc);
+        } else if (!c->IsClass()) {
+          *msg += "INVALID NON-CLASS OBJECT OF TYPE:" + PrettyTypeOf(c);
+        } else {
+          *msg += PrettyClass(c);
+          if (!entry) {
+            StringAppendF(msg, " (%p)", jc);
+          }
+        }
+        break;
+      }
+      case 'f': {  // jfieldID
+        jfieldID fid = arg.f;
+        mirror::ArtField* f = soa.DecodeField(fid);
+        *msg += PrettyField(f);
+        if (!entry) {
+          StringAppendF(msg, " (%p)", fid);
+        }
+        break;
+      }
+      case 'm': {  // jmethodID
+        jmethodID mid = arg.m;
+        mirror::ArtMethod* m = soa.DecodeMethod(mid);
+        *msg += PrettyMethod(m);
+        if (!entry) {
+          StringAppendF(msg, " (%p)", mid);
+        }
+        break;
+      }
+      default:
+        TraceNonHeapValue(fmt, arg, msg);
+        break;
+    }
+  }
+
+  void TraceNonHeapValue(char fmt, JniValueType arg, std::string* msg) {
+    switch (fmt) {
+      case 'B':  // jbyte
+        if (arg.B >= 0 && arg.B < 10) {
+          StringAppendF(msg, "%d", arg.B);
+        } else {
+          StringAppendF(msg, "%#x (%d)", arg.B, arg.B);
+        }
+        break;
+      case 'C':  // jchar
+        if (arg.C < 0x7f && arg.C >= ' ') {
+          StringAppendF(msg, "U+%x ('%c')", arg.C, arg.C);
+        } else {
+          StringAppendF(msg, "U+%x", arg.C);
+        }
+        break;
+      case 'F':  // jfloat
+        StringAppendF(msg, "%g", arg.F);
+        break;
+      case 'D':  // jdouble
+        StringAppendF(msg, "%g", arg.D);
+        break;
+      case 'S':  // jshort
+        StringAppendF(msg, "%d", arg.S);
+        break;
+      case 'i':  // jint - fall-through.
+      case 'I':  // jint
+        StringAppendF(msg, "%d", arg.I);
+        break;
+      case 'J':  // jlong
+        StringAppendF(msg, "%" PRId64, arg.J);
+        break;
+      case 'Z':  // jboolean
+      case 'b':  // jboolean (JNI-style)
+        *msg += arg.b == JNI_TRUE ? "true" : "false";
+        break;
+      case 'V':  // void
+        DCHECK(arg.V == nullptr);
+        *msg += "void";
+        break;
+      case 'v':  // JavaVM*
+        StringAppendF(msg, "(JavaVM*)%p", arg.v);
+        break;
+      case 'E':
+        StringAppendF(msg, "(JNIEnv*)%p", arg.E);
+        break;
+      case 'z':  // non-negative jsize
+        // You might expect jsize to be size_t, but it's not; it's the same as jint.
+        // We only treat this specially so we can do the non-negative check.
+        // TODO: maybe this wasn't worth it?
+        StringAppendF(msg, "%d", arg.z);
+        break;
+      case 'p':  // void* ("pointer")
+        if (arg.p == nullptr) {
+          *msg += "NULL";
+        } else {
+          StringAppendF(msg, "(void*) %p", arg.p);
+        }
+        break;
+      case 'r': {  // jint (release mode)
+        jint releaseMode = arg.r;
+        if (releaseMode == 0) {
+          *msg += "0";
+        } else if (releaseMode == JNI_ABORT) {
+          *msg += "JNI_ABORT";
+        } else if (releaseMode == JNI_COMMIT) {
+          *msg += "JNI_COMMIT";
+        } else {
+          StringAppendF(msg, "invalid release mode %d", releaseMode);
+        }
+        break;
+      }
+      case 'u':  // const char* (Modified UTF-8)
+        if (arg.u == nullptr) {
+          *msg += "NULL";
+        } else {
+          StringAppendF(msg, "\"%s\"", arg.u);
+        }
+        break;
+      case 'w':  // jobjectRefType
+        switch (arg.w) {
+          case JNIInvalidRefType:
+            *msg += "invalid reference type";
+            break;
+          case JNILocalRefType:
+            *msg += "local ref type";
+            break;
+          case JNIGlobalRefType:
+            *msg += "global ref type";
+            break;
+          case JNIWeakGlobalRefType:
+            *msg += "weak global ref type";
+            break;
+          default:
+            *msg += "unknown ref type";
+            break;
+        }
+        break;
+      case '.':
+        *msg += "...";
+        break;
+      default:
+        LOG(FATAL) << function_name_ << ": unknown trace format specifier: '" << fmt << "'";
+    }
+  }
   /*
    * Verify that "array" is non-NULL and points to an Array object.
    *
    * Since we're dealing with objects, switch to "running" mode.
    */
-  void CheckArray(jarray java_array) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    if (java_array == nullptr) {
-      JniAbortF(function_name_, "jarray was NULL");
-      return;
+  bool CheckArray(ScopedObjectAccess& soa, jarray java_array)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    if (UNLIKELY(java_array == nullptr)) {
+      AbortF("jarray was NULL");
+      return false;
     }
 
-    mirror::Array* a = soa_.Decode<mirror::Array*>(java_array);
-    if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(a)) {
+    mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
+    if (UNLIKELY(!Runtime::Current()->GetHeap()->IsValidObjectAddress(a))) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "jarray is an invalid %s: %p (%p)",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(), java_array, a);
+      AbortF("jarray is an invalid %s: %p (%p)",
+             ToStr<IndirectRefKind>(GetIndirectRefKind(java_array)).c_str(),
+             java_array, a);
+      return false;
     } else if (!a->IsArrayInstance()) {
-      JniAbortF(function_name_, "jarray argument has non-array type: %s", PrettyTypeOf(a).c_str());
+      AbortF("jarray argument has non-array type: %s", PrettyTypeOf(a).c_str());
+      return false;
     }
+    return true;
   }
 
-  void CheckLengthPositive(jsize length) {
+  bool CheckBoolean(jboolean z) {
+    if (z != JNI_TRUE && z != JNI_FALSE) {
+      AbortF("unexpected jboolean value: %d", z);
+      return false;
+    }
+    return true;
+  }
+
+  bool CheckLengthPositive(jsize length) {
     if (length < 0) {
-      JniAbortF(function_name_, "negative jsize: %d", length);
+      AbortF("negative jsize: %d", length);
+      return false;
     }
+    return true;
   }
 
-  mirror::ArtField* CheckFieldID(jfieldID fid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  mirror::ArtField* CheckFieldID(ScopedObjectAccess& soa, jfieldID fid)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     if (fid == nullptr) {
-      JniAbortF(function_name_, "jfieldID was NULL");
+      AbortF("jfieldID was NULL");
       return nullptr;
     }
-    mirror::ArtField* f = soa_.DecodeField(fid);
+    mirror::ArtField* f = soa.DecodeField(fid);
     if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(f) || !f->IsArtField()) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "invalid jfieldID: %p", fid);
+      AbortF("invalid jfieldID: %p", fid);
       return nullptr;
     }
     return f;
   }
 
-  mirror::ArtMethod* CheckMethodID(jmethodID mid) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  mirror::ArtMethod* CheckMethodID(ScopedObjectAccess& soa, jmethodID mid)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     if (mid == nullptr) {
-      JniAbortF(function_name_, "jmethodID was NULL");
+      AbortF("jmethodID was NULL");
       return nullptr;
     }
-    mirror::ArtMethod* m = soa_.DecodeMethod(mid);
+    mirror::ArtMethod* m = soa.DecodeMethod(mid);
     if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(m) || !m->IsArtMethod()) {
       Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      JniAbortF(function_name_, "invalid jmethodID: %p", mid);
+      AbortF("invalid jmethodID: %p", mid);
       return nullptr;
     }
     return m;
   }
 
-  /*
-   * Verify that "jobj" is a valid object, and that it's an object that JNI
-   * is allowed to know about.  We allow NULL references.
-   *
-   * Switches to "running" mode before performing checks.
-   */
-  void CheckObject(jobject java_object)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    if (java_object == nullptr) {
-      return;
-    }
-
-    mirror::Object* o = soa_.Decode<mirror::Object*>(java_object);
-    if (!Runtime::Current()->GetHeap()->IsValidObjectAddress(o)) {
-      Runtime::Current()->GetHeap()->DumpSpaces(LOG(ERROR));
-      // TODO: when we remove work_around_app_jni_bugs, this should be impossible.
-      JniAbortF(function_name_, "native code passing in reference to invalid %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(java_object)).c_str(), java_object);
-    }
-  }
-
-  /*
-   * Verify that the "mode" argument passed to a primitive array Release
-   * function is one of the valid values.
-   */
-  void CheckReleaseMode(jint mode) {
-    if (mode != 0 && mode != JNI_COMMIT && mode != JNI_ABORT) {
-      JniAbortF(function_name_, "unknown value for release mode: %d", mode);
-    }
-  }
-
-  void CheckThread(int flags) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  bool CheckThread(JNIEnv* env) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     Thread* self = Thread::Current();
     if (self == nullptr) {
-      JniAbortF(function_name_, "a thread (tid %d) is making JNI calls without being attached", GetTid());
-      return;
+      AbortF("a thread (tid %d) is making JNI calls without being attached", GetTid());
+      return false;
     }
 
     // Get the *correct* JNIEnv by going through our TLS pointer.
@@ -767,21 +1004,22 @@
 
     // Verify that the current thread is (a) attached and (b) associated with
     // this particular instance of JNIEnv.
-    if (soa_.Env() != threadEnv) {
-      JniAbortF(function_name_, "thread %s using JNIEnv* from thread %s",
-                ToStr<Thread>(*self).c_str(), ToStr<Thread>(*soa_.Self()).c_str());
-      return;
+    if (env != threadEnv) {
+      AbortF("thread %s using JNIEnv* from thread %s",
+             ToStr<Thread>(*self).c_str(), ToStr<Thread>(*self).c_str());
+      return false;
     }
 
     // Verify that, if this thread previously made a critical "get" call, we
     // do the corresponding "release" call before we try anything else.
-    switch (flags & kFlag_CritMask) {
+    switch (flags_ & kFlag_CritMask) {
     case kFlag_CritOkay:    // okay to call this method
       break;
     case kFlag_CritBad:     // not okay to call
       if (threadEnv->critical) {
-        JniAbortF(function_name_, "thread %s using JNI after critical get", ToStr<Thread>(*self).c_str());
-        return;
+        AbortF("thread %s using JNI after critical get",
+               ToStr<Thread>(*self).c_str());
+        return false;
       }
       break;
     case kFlag_CritGet:     // this is a "get" call
@@ -791,44 +1029,46 @@
     case kFlag_CritRelease:  // this is a "release" call
       threadEnv->critical--;
       if (threadEnv->critical < 0) {
-        JniAbortF(function_name_, "thread %s called too many critical releases", ToStr<Thread>(*self).c_str());
-        return;
+        AbortF("thread %s called too many critical releases",
+               ToStr<Thread>(*self).c_str());
+        return false;
       }
       break;
     default:
-      LOG(FATAL) << "Bad flags (internal error): " << flags;
+      LOG(FATAL) << "Bad flags (internal error): " << flags_;
     }
 
     // Verify that, if an exception has been raised, the native code doesn't
     // make any JNI calls other than the Exception* methods.
-    if ((flags & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) {
+    if ((flags_ & kFlag_ExcepOkay) == 0 && self->IsExceptionPending()) {
       ThrowLocation throw_location;
       mirror::Throwable* exception = self->GetException(&throw_location);
       std::string type(PrettyTypeOf(exception));
-      JniAbortF(function_name_, "JNI %s called with pending exception '%s' thrown in %s",
-                function_name_, type.c_str(), throw_location.Dump().c_str());
-      return;
+      AbortF("JNI %s called with pending exception '%s' thrown in %s",
+             function_name_, type.c_str(), throw_location.Dump().c_str());
+      return false;
     }
+    return true;
   }
 
   // Verifies that "bytes" points to valid Modified UTF-8 data.
-  void CheckUtfString(const char* bytes, bool nullable) {
+  bool CheckUtfString(const char* bytes, bool nullable) {
     if (bytes == nullptr) {
       if (!nullable) {
-        JniAbortF(function_name_, "non-nullable const char* was NULL");
-        return;
+        AbortF("non-nullable const char* was NULL");
+        return false;
       }
-      return;
+      return true;
     }
 
     const char* errorKind = nullptr;
     uint8_t utf8 = CheckUtfBytes(bytes, &errorKind);
     if (errorKind != nullptr) {
-      JniAbortF(function_name_,
-                "input is not valid Modified UTF-8: illegal %s byte %#x\n"
-                "    string: '%s'", errorKind, utf8, bytes);
-      return;
+      AbortF("input is not valid Modified UTF-8: illegal %s byte %#x\n"
+          "    string: '%s'", errorKind, utf8, bytes);
+      return false;
     }
+    return true;
   }
 
   static uint8_t CheckUtfBytes(const char* bytes, const char** errorKind) {
@@ -880,92 +1120,120 @@
     return 0;
   }
 
-  const ScopedObjectAccess soa_;
-  const char* function_name_;
-  int flags_;
-  bool has_method_;
+  void AbortF(const char* fmt, ...) __attribute__((__format__(__printf__, 2, 3))) {
+    va_list args;
+    va_start(args, fmt);
+    Runtime::Current()->GetJavaVM()->JniAbortV(function_name_, fmt, args);
+    va_end(args);
+  }
+
+  // The name of the JNI function being checked.
+  const char* const function_name_;
+
+  const int flags_;
   int indent_;
 
+  const bool has_method_;
+
   DISALLOW_COPY_AND_ASSIGN(ScopedCheck);
 };
 
-#define CHECK_JNI_ENTRY(flags, types, args...) \
-  ScopedCheck sc(env, flags, __FUNCTION__); \
-  sc.Check(true, types, ##args)
-
-#define CHECK_JNI_EXIT(type, exp) ({ \
-    auto _rc = (exp); \
-  sc.Check(false, type, _rc); \
-  _rc; })
-#define CHECK_JNI_EXIT_VOID() \
-  sc.Check(false, "V")
-
 /*
  * ===========================================================================
  *      Guarded arrays
  * ===========================================================================
  */
 
-#define kGuardLen       512         /* must be multiple of 2 */
-#define kGuardPattern   0xd5e3      /* uncommon values; d5e3d5e3 invalid addr */
-#define kGuardMagic     0xffd5aa96
-
 /* this gets tucked in at the start of the buffer; struct size must be even */
-struct GuardedCopy {
-  uint32_t magic;
-  uLong adler;
-  size_t original_length;
-  const void* original_ptr;
-
-  /* find the GuardedCopy given the pointer into the "live" data */
-  static inline const GuardedCopy* FromData(const void* dataBuf) {
-    return reinterpret_cast<const GuardedCopy*>(ActualBuffer(dataBuf));
-  }
-
+class GuardedCopy {
+ public:
   /*
    * Create an over-sized buffer to hold the contents of "buf".  Copy it in,
    * filling in the area around it with guard data.
-   *
-   * We use a 16-bit pattern to make a rogue memset less likely to elude us.
    */
-  static void* Create(const void* buf, size_t len, bool modOkay) {
-    size_t newLen = ActualLength(len);
-    uint8_t* newBuf = DebugAlloc(newLen);
-
-    // Fill it in with a pattern.
-    uint16_t* pat = reinterpret_cast<uint16_t*>(newBuf);
-    for (size_t i = 0; i < newLen / 2; i++) {
-      *pat++ = kGuardPattern;
-    }
-
-    // Copy the data in; note "len" could be zero.
-    memcpy(newBuf + kGuardLen / 2, buf, len);
+  static void* Create(const void* original_buf, size_t len, bool mod_okay) {
+    const size_t new_len = LengthIncludingRedZones(len);
+    uint8_t* const new_buf = DebugAlloc(new_len);
 
     // If modification is not expected, grab a checksum.
     uLong adler = 0;
-    if (!modOkay) {
-      adler = adler32(0L, Z_NULL, 0);
-      adler = adler32(adler, reinterpret_cast<const Bytef*>(buf), len);
-      *reinterpret_cast<uLong*>(newBuf) = adler;
+    if (!mod_okay) {
+      adler = adler32(adler32(0L, Z_NULL, 0), reinterpret_cast<const Bytef*>(original_buf), len);
     }
 
-    GuardedCopy* pExtra = reinterpret_cast<GuardedCopy*>(newBuf);
-    pExtra->magic = kGuardMagic;
-    pExtra->adler = adler;
-    pExtra->original_ptr = buf;
-    pExtra->original_length = len;
+    GuardedCopy* copy = new (new_buf) GuardedCopy(original_buf, len, adler);
 
-    return newBuf + kGuardLen / 2;
+    // Fill begin region with canary pattern.
+    const size_t kStartCanaryLength = (GuardedCopy::kRedZoneSize / 2) - sizeof(GuardedCopy);
+    for (size_t i = 0, j = 0; i < kStartCanaryLength; ++i) {
+      const_cast<char*>(copy->StartRedZone())[i] = kCanary[j];
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+
+    // Copy the data in; note "len" could be zero.
+    memcpy(const_cast<uint8_t*>(copy->BufferWithinRedZones()), original_buf, len);
+
+    // Fill end region with canary pattern.
+    for (size_t i = 0, j = 0; i < kEndCanaryLength; ++i) {
+      const_cast<char*>(copy->EndRedZone())[i] = kCanary[j];
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+
+    return const_cast<uint8_t*>(copy->BufferWithinRedZones());
   }
 
   /*
+   * Create a guarded copy of a primitive array.  Modifications to the copied
+   * data are allowed.  Returns a pointer to the copied data.
+   */
+  static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* is_copy) {
+    ScopedObjectAccess soa(env);
+
+    mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
+    size_t component_size = a->GetClass()->GetComponentSize();
+    size_t byte_count = a->GetLength() * component_size;
+    void* result = Create(a->GetRawData(component_size, 0), byte_count, true);
+    if (is_copy != nullptr) {
+      *is_copy = JNI_TRUE;
+    }
+    return result;
+  }
+
+  /*
+   * Perform the array "release" operation, which may or may not copy data
+   * back into the managed heap, and may or may not release the underlying storage.
+   */
+  static void* ReleaseGuardedPACopy(const char* function_name, JNIEnv* env, jarray java_array,
+                                   void* embedded_buf, int mode) {
+    ScopedObjectAccess soa(env);
+    mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
+
+    if (!GuardedCopy::Check(function_name, embedded_buf, true)) {
+      return nullptr;
+    }
+    if (mode != JNI_ABORT) {
+      size_t len = FromEmbedded(embedded_buf)->original_length_;
+      memcpy(a->GetRawData(a->GetClass()->GetComponentSize(), 0), embedded_buf, len);
+    }
+    if (mode != JNI_COMMIT) {
+      return Destroy(embedded_buf);
+    }
+    return embedded_buf;
+  }
+
+
+  /*
    * Free up the guard buffer, scrub it, and return the original pointer.
    */
-  static void* Destroy(void* dataBuf) {
-    const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf);
-    void* original_ptr = const_cast<void*>(pExtra->original_ptr);
-    size_t len = pExtra->original_length;
-    DebugFree(dataBuf, len);
+  static void* Destroy(void* embedded_buf) {
+    GuardedCopy* copy = FromEmbedded(embedded_buf);
+    void* original_ptr = const_cast<void*>(copy->original_ptr_);
+    size_t len = LengthIncludingRedZones(copy->original_length_);
+    DebugFree(copy, len);
     return original_ptr;
   }
 
@@ -975,67 +1243,16 @@
    *
    * The caller has already checked that "dataBuf" is non-NULL.
    */
-  static void Check(const char* functionName, const void* dataBuf, bool modOkay) {
-    static const uint32_t kMagicCmp = kGuardMagic;
-    const uint8_t* fullBuf = ActualBuffer(dataBuf);
-    const GuardedCopy* pExtra = GuardedCopy::FromData(dataBuf);
-
-    // Before we do anything with "pExtra", check the magic number.  We
-    // do the check with memcmp rather than "==" in case the pointer is
-    // unaligned.  If it points to completely bogus memory we're going
-    // to crash, but there's no easy way around that.
-    if (memcmp(&pExtra->magic, &kMagicCmp, 4) != 0) {
-      uint8_t buf[4];
-      memcpy(buf, &pExtra->magic, 4);
-      JniAbortF(functionName,
-          "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?",
-          buf[3], buf[2], buf[1], buf[0], dataBuf);  // Assumes little-endian.
-    }
-
-    size_t len = pExtra->original_length;
-
-    // Check bottom half of guard; skip over optional checksum storage.
-    const uint16_t* pat = reinterpret_cast<const uint16_t*>(fullBuf);
-    for (size_t i = sizeof(GuardedCopy) / 2; i < (kGuardLen / 2 - sizeof(GuardedCopy)) / 2; i++) {
-      if (pat[i] != kGuardPattern) {
-        JniAbortF(functionName, "guard pattern(1) disturbed at %p +%zd", fullBuf, i*2);
-      }
-    }
-
-    int offset = kGuardLen / 2 + len;
-    if (offset & 0x01) {
-      // Odd byte; expected value depends on endian.
-      const uint16_t patSample = kGuardPattern;
-      uint8_t expected_byte = reinterpret_cast<const uint8_t*>(&patSample)[1];
-      if (fullBuf[offset] != expected_byte) {
-        JniAbortF(functionName, "guard pattern disturbed in odd byte after %p +%d 0x%02x 0x%02x",
-                  fullBuf, offset, fullBuf[offset], expected_byte);
-      }
-      offset++;
-    }
-
-    // Check top half of guard.
-    pat = reinterpret_cast<const uint16_t*>(fullBuf + offset);
-    for (size_t i = 0; i < kGuardLen / 4; i++) {
-      if (pat[i] != kGuardPattern) {
-        JniAbortF(functionName, "guard pattern(2) disturbed at %p +%zd", fullBuf, offset + i*2);
-      }
-    }
-
-    // If modification is not expected, verify checksum.  Strictly speaking
-    // this is wrong: if we told the client that we made a copy, there's no
-    // reason they can't alter the buffer.
-    if (!modOkay) {
-      uLong adler = adler32(0L, Z_NULL, 0);
-      adler = adler32(adler, (const Bytef*)dataBuf, len);
-      if (pExtra->adler != adler) {
-        JniAbortF(functionName, "buffer modified (0x%08lx vs 0x%08lx) at address %p",
-                  pExtra->adler, adler, dataBuf);
-      }
-    }
+  static bool Check(const char* function_name, const void* embedded_buf, bool mod_okay) {
+    const GuardedCopy* copy = FromEmbedded(embedded_buf);
+    return copy->CheckHeader(function_name, mod_okay) && copy->CheckRedZones(function_name);
   }
 
  private:
+  GuardedCopy(const void* original_buf, size_t len, uLong adler) :
+    magic_(kGuardMagic), adler_(adler), original_ptr_(original_buf), original_length_(len) {
+  }
+
   static uint8_t* DebugAlloc(size_t len) {
     void* result = mmap(nullptr, len, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
     if (result == MAP_FAILED) {
@@ -1044,68 +1261,126 @@
     return reinterpret_cast<uint8_t*>(result);
   }
 
-  static void DebugFree(void* dataBuf, size_t len) {
-    uint8_t* fullBuf = ActualBuffer(dataBuf);
-    size_t totalByteCount = ActualLength(len);
-    // TODO: we could mprotect instead, and keep the allocation around for a while.
-    // This would be even more expensive, but it might catch more errors.
-    // if (mprotect(fullBuf, totalByteCount, PROT_NONE) != 0) {
-    //     PLOG(WARNING) << "mprotect(PROT_NONE) failed";
-    // }
-    if (munmap(fullBuf, totalByteCount) != 0) {
-      PLOG(FATAL) << "munmap(" << reinterpret_cast<void*>(fullBuf) << ", " << totalByteCount << ") failed";
+  static void DebugFree(void* buf, size_t len) {
+    if (munmap(buf, len) != 0) {
+      PLOG(FATAL) << "munmap(" << buf << ", " << len << ") failed";
     }
   }
 
-  static const uint8_t* ActualBuffer(const void* dataBuf) {
-    return reinterpret_cast<const uint8_t*>(dataBuf) - kGuardLen / 2;
+  static size_t LengthIncludingRedZones(size_t len) {
+    return len + kRedZoneSize;
   }
 
-  static uint8_t* ActualBuffer(void* dataBuf) {
-    return reinterpret_cast<uint8_t*>(dataBuf) - kGuardLen / 2;
+  // Get the GuardedCopy from the interior pointer.
+  static GuardedCopy* FromEmbedded(void* embedded_buf) {
+    return reinterpret_cast<GuardedCopy*>(
+        reinterpret_cast<uint8_t*>(embedded_buf) - (kRedZoneSize / 2));
   }
 
-  // Underlying length of a user allocation of 'length' bytes.
-  static size_t ActualLength(size_t length) {
-    return (length + kGuardLen + 1) & ~0x01;
+  static const GuardedCopy* FromEmbedded(const void* embedded_buf) {
+    return reinterpret_cast<const GuardedCopy*>(
+        reinterpret_cast<const uint8_t*>(embedded_buf) - (kRedZoneSize / 2));
   }
+
+  static void AbortF(const char* jni_function_name, const char* fmt, ...) {
+    va_list args;
+    va_start(args, fmt);
+    Runtime::Current()->GetJavaVM()->JniAbortV(jni_function_name, fmt, args);
+    va_end(args);
+  }
+
+  bool CheckHeader(const char* function_name, bool mod_okay) const {
+    static const uint32_t kMagicCmp = kGuardMagic;
+
+    // Before we do anything with "pExtra", check the magic number.  We
+    // do the check with memcmp rather than "==" in case the pointer is
+    // unaligned.  If it points to completely bogus memory we're going
+    // to crash, but there's no easy way around that.
+    if (UNLIKELY(memcmp(&magic_, &kMagicCmp, 4) != 0)) {
+      uint8_t buf[4];
+      memcpy(buf, &magic_, 4);
+      AbortF(function_name,
+             "guard magic does not match (found 0x%02x%02x%02x%02x) -- incorrect data pointer %p?",
+             buf[3], buf[2], buf[1], buf[0], this);  // Assumes little-endian.
+      return false;
+    }
+
+    // If modification is not expected, verify checksum. Strictly speaking this is wrong: if we
+    // told the client that we made a copy, there's no reason they can't alter the buffer.
+    if (!mod_okay) {
+      uLong computed_adler =
+          adler32(adler32(0L, Z_NULL, 0), BufferWithinRedZones(), original_length_);
+      if (computed_adler != adler_) {
+        AbortF(function_name, "buffer modified (0x%08lx vs 0x%08lx) at address %p",
+               computed_adler, adler_, this);
+        return false;
+      }
+    }
+    return true;
+  }
+
+  bool CheckRedZones(const char* function_name) const {
+    // Check the begin red zone.
+    const size_t kStartCanaryLength = (GuardedCopy::kRedZoneSize / 2) - sizeof(GuardedCopy);
+    for (size_t i = 0, j = 0; i < kStartCanaryLength; ++i) {
+      if (UNLIKELY(StartRedZone()[i] != kCanary[j])) {
+        AbortF(function_name, "guard pattern before buffer disturbed at %p +%zd", this, i);
+        return false;
+      }
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+
+    // Check end region.
+    for (size_t i = 0, j = 0; i < kEndCanaryLength; ++i) {
+      if (UNLIKELY(EndRedZone()[i] != kCanary[j])) {
+        size_t offset_from_buffer_start =
+            &(EndRedZone()[i]) - &(StartRedZone()[kStartCanaryLength]);
+        AbortF(function_name, "guard pattern after buffer disturbed at %p +%zd", this,
+               offset_from_buffer_start);
+        return false;
+      }
+      if (kCanary[j] == '\0') {
+        j = 0;
+      }
+    }
+    return true;
+  }
+
+  // Location that canary value will be written before the guarded region.
+  const char* StartRedZone() const {
+    const uint8_t* buf = reinterpret_cast<const uint8_t*>(this);
+    return reinterpret_cast<const char*>(buf + sizeof(GuardedCopy));
+  }
+
+  // Return the interior embedded buffer.
+  const uint8_t* BufferWithinRedZones() const {
+    const uint8_t* embedded_buf = reinterpret_cast<const uint8_t*>(this) + (kRedZoneSize / 2);
+    return embedded_buf;
+  }
+
+  // Location that canary value will be written after the guarded region.
+  const char* EndRedZone() const {
+    const uint8_t* buf = reinterpret_cast<const uint8_t*>(this);
+    size_t buf_len = LengthIncludingRedZones(original_length_);
+    return reinterpret_cast<const char*>(buf + (buf_len - (kRedZoneSize / 2)));
+  }
+
+  static constexpr size_t kRedZoneSize = 512;
+  static constexpr size_t kEndCanaryLength = kRedZoneSize / 2;
+
+  // Value written before and after the guarded array.
+  static const char* const kCanary;
+
+  static constexpr uint32_t kGuardMagic = 0xffd5aa96;
+
+  const uint32_t magic_;
+  const uLong adler_;
+  const void* const original_ptr_;
+  const size_t original_length_;
 };
-
-/*
- * Create a guarded copy of a primitive array.  Modifications to the copied
- * data are allowed.  Returns a pointer to the copied data.
- */
-static void* CreateGuardedPACopy(JNIEnv* env, const jarray java_array, jboolean* isCopy) {
-  ScopedObjectAccess soa(env);
-
-  mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
-  size_t component_size = a->GetClass()->GetComponentSize();
-  size_t byte_count = a->GetLength() * component_size;
-  void* result = GuardedCopy::Create(a->GetRawData(component_size, 0), byte_count, true);
-  if (isCopy != nullptr) {
-    *isCopy = JNI_TRUE;
-  }
-  return result;
-}
-
-/*
- * Perform the array "release" operation, which may or may not copy data
- * back into the managed heap, and may or may not release the underlying storage.
- */
-static void ReleaseGuardedPACopy(JNIEnv* env, jarray java_array, void* dataBuf, int mode) {
-  ScopedObjectAccess soa(env);
-  mirror::Array* a = soa.Decode<mirror::Array*>(java_array);
-
-  GuardedCopy::Check(__FUNCTION__, dataBuf, true);
-
-  if (mode != JNI_ABORT) {
-    size_t len = GuardedCopy::FromData(dataBuf)->original_length;
-    memcpy(a->GetRawData(a->GetClass()->GetComponentSize(), 0), dataBuf, len);
-  }
-  if (mode != JNI_COMMIT) {
-    GuardedCopy::Destroy(dataBuf);
-  }
-}
+const char* const GuardedCopy::kCanary = "JNI BUFFER RED ZONE";
 
 /*
  * ===========================================================================
@@ -1116,668 +1391,1954 @@
 class CheckJNI {
  public:
   static jint GetVersion(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_Default, "E", env);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetVersion(env));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[1] = {{.E = env }};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      result.I = baseEnv(env)->GetVersion(env);
+      if (sc.Check(soa, false, "I", &result)) {
+        return result.I;
+      }
+    }
+    return JNI_ERR;
   }
 
-  static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf, jsize bufLen) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EuLpz", env, name, loader, buf, bufLen);
-    sc.CheckClassName(name);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->DefineClass(env, name, loader, buf, bufLen));
+  static jint GetJavaVM(JNIEnv *env, JavaVM **vm) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env }, {.p = vm}};
+    if (sc.Check(soa, true, "Ep", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->GetJavaVM(env, vm);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env }, {.c = c}, {.p = methods}, {.I = nMethods}};
+    if (sc.Check(soa, true, "EcpI", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->RegisterNatives(env, c, methods, nMethods);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jint UnregisterNatives(JNIEnv* env, jclass c) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env }, {.c = c}};
+    if (sc.Check(soa, true, "Ec", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->UnregisterNatives(env, c);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) {
+    // Note: we use "Ep" rather than "EL" because this is the one JNI function that it's okay to
+    // pass an invalid reference to.
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env }, {.p = obj}};
+    if (sc.Check(soa, true, "Ep", args)) {
+      JniValueType result;
+      result.w = baseEnv(env)->GetObjectRefType(env, obj);
+      if (sc.Check(soa, false, "w", &result)) {
+        return result.w;
+      }
+    }
+    return JNIInvalidRefType;
+  }
+
+  static jclass DefineClass(JNIEnv* env, const char* name, jobject loader, const jbyte* buf,
+                            jsize bufLen) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.u = name}, {.L = loader}, {.p = buf}, {.z = bufLen}};
+    if (sc.Check(soa, true, "EuLpz", args) && sc.CheckClassName(name)) {
+      JniValueType result;
+      result.c = baseEnv(env)->DefineClass(env, name, loader, buf, bufLen);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
   }
 
   static jclass FindClass(JNIEnv* env, const char* name) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Eu", env, name);
-    sc.CheckClassName(name);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->FindClass(env, name));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.u = name}};
+    if (sc.Check(soa, true, "Eu", args) && sc.CheckClassName(name)) {
+      JniValueType result;
+      result.c = baseEnv(env)->FindClass(env, name);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
   }
 
   static jclass GetSuperclass(JNIEnv* env, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->GetSuperclass(env, c));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.c = c}};
+    if (sc.Check(soa, true, "Ec", args)) {
+      JniValueType result;
+      result.c = baseEnv(env)->GetSuperclass(env, c);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
   }
 
   static jboolean IsAssignableFrom(JNIEnv* env, jclass c1, jclass c2) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecc", env, c1, c2);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->IsAssignableFrom(env, c1, c2));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.c = c1}, {.c = c2}};
+    if (sc.Check(soa, true, "Ecc", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->IsAssignableFrom(env, c1, c2);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
   }
 
   static jmethodID FromReflectedMethod(JNIEnv* env, jobject method) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, method);
-    // TODO: check that 'field' is a java.lang.reflect.Method.
-    return CHECK_JNI_EXIT("m", baseEnv(env)->FromReflectedMethod(env, method));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = method}};
+    if (sc.Check(soa, true, "EL", args) && sc.CheckReflectedMethod(soa, method)) {
+      JniValueType result;
+      result.m = baseEnv(env)->FromReflectedMethod(env, method);
+      if (sc.Check(soa, false, "m", &result)) {
+        return result.m;
+      }
+    }
+    return nullptr;
   }
 
   static jfieldID FromReflectedField(JNIEnv* env, jobject field) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, field);
-    // TODO: check that 'field' is a java.lang.reflect.Field.
-    return CHECK_JNI_EXIT("f", baseEnv(env)->FromReflectedField(env, field));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = field}};
+    if (sc.Check(soa, true, "EL", args) && sc.CheckReflectedField(soa, field)) {
+      JniValueType result;
+      result.f = baseEnv(env)->FromReflectedField(env, field);
+      if (sc.Check(soa, false, "f", &result)) {
+        return result.f;
+      }
+    }
+    return nullptr;
   }
 
   static jobject ToReflectedMethod(JNIEnv* env, jclass cls, jmethodID mid, jboolean isStatic) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecmb", env, cls, mid, isStatic);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env}, {.c = cls}, {.m = mid}, {.b = isStatic}};
+    if (sc.Check(soa, true, "Ecmb", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->ToReflectedMethod(env, cls, mid, isStatic);
+      if (sc.Check(soa, false, "L", &result) && (result.L != nullptr)) {
+        DCHECK(sc.CheckReflectedMethod(soa, result.L));
+        return result.L;
+      }
+    }
+    return nullptr;
   }
 
   static jobject ToReflectedField(JNIEnv* env, jclass cls, jfieldID fid, jboolean isStatic) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecfb", env, cls, fid, isStatic);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->ToReflectedField(env, cls, fid, isStatic));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env}, {.c = cls}, {.f = fid}, {.b = isStatic}};
+    if (sc.Check(soa, true, "Ecfb", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->ToReflectedField(env, cls, fid, isStatic);
+      if (sc.Check(soa, false, "L", &result) && (result.L != nullptr)) {
+        DCHECK(sc.CheckReflectedField(soa, result.L));
+        return result.L;
+      }
+    }
+    return nullptr;
   }
 
   static jint Throw(JNIEnv* env, jthrowable obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    // TODO: check that 'obj' is a java.lang.Throwable.
-    return CHECK_JNI_EXIT("I", baseEnv(env)->Throw(env, obj));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.t = obj}};
+    if (sc.Check(soa, true, "Et", args) && sc.CheckThrowable(soa, obj)) {
+      JniValueType result;
+      result.i = baseEnv(env)->Throw(env, obj);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
   }
 
   static jint ThrowNew(JNIEnv* env, jclass c, const char* message) {
-    CHECK_JNI_ENTRY(kFlag_NullableUtf, "Ecu", env, c, message);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->ThrowNew(env, c, message));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_NullableUtf, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.c = c}, {.u = message}};
+    if (sc.Check(soa, true, "Ecu", args) && sc.CheckThrowableClass(soa, c)) {
+      JniValueType result;
+      result.i = baseEnv(env)->ThrowNew(env, c, message);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
   }
 
   static jthrowable ExceptionOccurred(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->ExceptionOccurred(env));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      result.t = baseEnv(env)->ExceptionOccurred(env);
+      if (sc.Check(soa, false, "t", &result)) {
+        return result.t;
+      }
+    }
+    return nullptr;
   }
 
   static void ExceptionDescribe(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env);
-    baseEnv(env)->ExceptionDescribe(env);
-    CHECK_JNI_EXIT_VOID();
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      baseEnv(env)->ExceptionDescribe(env);
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
   }
 
   static void ExceptionClear(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay, "E", env);
-    baseEnv(env)->ExceptionClear(env);
-    CHECK_JNI_EXIT_VOID();
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      baseEnv(env)->ExceptionClear(env);
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jboolean ExceptionCheck(JNIEnv* env) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay | kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[1] = {{.E = env}};
+    if (sc.Check(soa, true, "E", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->ExceptionCheck(env);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
   }
 
   static void FatalError(JNIEnv* env, const char* msg) {
     // The JNI specification doesn't say it's okay to call FatalError with a pending exception,
     // but you're about to abort anyway, and it's quite likely that you have a pending exception,
     // and it's not unimaginable that you don't know that you do. So we allow it.
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_NullableUtf, "Eu", env, msg);
-    baseEnv(env)->FatalError(env, msg);
-    CHECK_JNI_EXIT_VOID();
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay | kFlag_NullableUtf, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.u = msg}};
+    if (sc.Check(soa, true, "Eu", args)) {
+      JniValueType result;
+      baseEnv(env)->FatalError(env, msg);
+      // Unreachable.
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
   }
 
   static jint PushLocalFrame(JNIEnv* env, jint capacity) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EI", env, capacity);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->PushLocalFrame(env, capacity));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.I = capacity}};
+    if (sc.Check(soa, true, "EI", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->PushLocalFrame(env, capacity);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
   }
 
   static jobject PopLocalFrame(JNIEnv* env, jobject res) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, res);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->PopLocalFrame(env, res));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = res}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->PopLocalFrame(env, res);
+      sc.Check(soa, false, "L", &result);
+      return result.L;
+    }
+    return nullptr;
   }
 
   static jobject NewGlobalRef(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewGlobalRef(env, obj));
+    return NewRef(__FUNCTION__, env, obj, kGlobal);
   }
 
-  static jobject NewLocalRef(JNIEnv* env, jobject ref) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, ref);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewLocalRef(env, ref));
-  }
-
-  static void DeleteGlobalRef(JNIEnv* env, jobject globalRef) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, globalRef);
-    if (globalRef != nullptr && GetIndirectRefKind(globalRef) != kGlobal) {
-      JniAbortF(__FUNCTION__, "DeleteGlobalRef on %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(globalRef)).c_str(), globalRef);
-    } else {
-      baseEnv(env)->DeleteGlobalRef(env, globalRef);
-      CHECK_JNI_EXIT_VOID();
-    }
-  }
-
-  static void DeleteWeakGlobalRef(JNIEnv* env, jweak weakGlobalRef) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, weakGlobalRef);
-    if (weakGlobalRef != nullptr && GetIndirectRefKind(weakGlobalRef) != kWeakGlobal) {
-      JniAbortF(__FUNCTION__, "DeleteWeakGlobalRef on %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(weakGlobalRef)).c_str(), weakGlobalRef);
-    } else {
-      baseEnv(env)->DeleteWeakGlobalRef(env, weakGlobalRef);
-      CHECK_JNI_EXIT_VOID();
-    }
-  }
-
-  static void DeleteLocalRef(JNIEnv* env, jobject localRef) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, localRef);
-    if (localRef != nullptr && GetIndirectRefKind(localRef) != kLocal && !IsHandleScopeLocalRef(env, localRef)) {
-      JniAbortF(__FUNCTION__, "DeleteLocalRef on %s: %p",
-                ToStr<IndirectRefKind>(GetIndirectRefKind(localRef)).c_str(), localRef);
-    } else {
-      baseEnv(env)->DeleteLocalRef(env, localRef);
-      CHECK_JNI_EXIT_VOID();
-    }
-  }
-
-  static jint EnsureLocalCapacity(JNIEnv *env, jint capacity) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EI", env, capacity);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->EnsureLocalCapacity(env, capacity));
-  }
-
-  static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) {
-    CHECK_JNI_ENTRY(kFlag_Default, "ELL", env, ref1, ref2);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->IsSameObject(env, ref1, ref2));
-  }
-
-  static jobject AllocObject(JNIEnv* env, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->AllocObject(env, c));
-  }
-
-  static jobject NewObject(JNIEnv* env, jclass c, jmethodID mid, ...) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid);
-    va_list args;
-    va_start(args, mid);
-    jobject result = baseEnv(env)->NewObjectV(env, c, mid, args);
-    va_end(args);
-    return CHECK_JNI_EXIT("L", result);
-  }
-
-  static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list args) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectV(env, c, mid, args));
-  }
-
-  static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewObjectA(env, c, mid, args));
-  }
-
-  static jclass GetObjectClass(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    return CHECK_JNI_EXIT("c", baseEnv(env)->GetObjectClass(env, obj));
-  }
-
-  static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "ELc", env, obj, c);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->IsInstanceOf(env, obj, c));
-  }
-
-  static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("m", baseEnv(env)->GetMethodID(env, c, name, sig));
-  }
-
-  static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("f", baseEnv(env)->GetFieldID(env, c, name, sig));
-  }
-
-  static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("m", baseEnv(env)->GetStaticMethodID(env, c, name, sig));
-  }
-
-  static jfieldID GetStaticFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ecuu", env, c, name, sig);
-    return CHECK_JNI_EXIT("f", baseEnv(env)->GetStaticFieldID(env, c, name, sig));
-  }
-
-#define FIELD_ACCESSORS(_ctype, _jname, _jvalue_type, _type) \
-    static _ctype GetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecf", env, c, fid); \
-        sc.CheckStaticFieldID(c, fid); \
-        return CHECK_JNI_EXIT(_type, baseEnv(env)->GetStatic##_jname##Field(env, c, fid)); \
-    } \
-    static _ctype Get##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELf", env, obj, fid); \
-        sc.CheckInstanceFieldID(obj, fid); \
-        return CHECK_JNI_EXIT(_type, baseEnv(env)->Get##_jname##Field(env, obj, fid)); \
-    } \
-    static void SetStatic##_jname##Field(JNIEnv* env, jclass c, jfieldID fid, _ctype value) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecf" _type, env, c, fid, value); \
-        sc.CheckStaticFieldID(c, fid); \
-        /* "value" arg only used when type == ref */ \
-        jvalue java_type_value; \
-        java_type_value._jvalue_type = value; \
-        sc.CheckFieldType(java_type_value, fid, _type[0], true); \
-        baseEnv(env)->SetStatic##_jname##Field(env, c, fid, value); \
-        CHECK_JNI_EXIT_VOID(); \
-    } \
-    static void Set##_jname##Field(JNIEnv* env, jobject obj, jfieldID fid, _ctype value) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELf" _type, env, obj, fid, value); \
-        sc.CheckInstanceFieldID(obj, fid); \
-        /* "value" arg only used when type == ref */ \
-        jvalue java_type_value; \
-        java_type_value._jvalue_type = value; \
-        sc.CheckFieldType(java_type_value, fid, _type[0], false); \
-        baseEnv(env)->Set##_jname##Field(env, obj, fid, value); \
-        CHECK_JNI_EXIT_VOID(); \
-    }
-
-FIELD_ACCESSORS(jobject, Object, l, "L");
-FIELD_ACCESSORS(jboolean, Boolean, z, "Z");
-FIELD_ACCESSORS(jbyte, Byte, b, "B");
-FIELD_ACCESSORS(jchar, Char, c, "C");
-FIELD_ACCESSORS(jshort, Short, s, "S");
-FIELD_ACCESSORS(jint, Int, i, "I");
-FIELD_ACCESSORS(jlong, Long, j, "J");
-FIELD_ACCESSORS(jfloat, Float, f, "F");
-FIELD_ACCESSORS(jdouble, Double, d, "D");
-
-#define CALL(_ctype, _jname, _retdecl, _retasgn, _retok, _retsig) \
-    /* Virtual... */ \
-    static _ctype Call##_jname##Method(JNIEnv* env, jobject obj, \
-        jmethodID mid, ...) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        va_list args; \
-        va_start(args, mid); \
-        _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \
-        va_end(args); \
-        _retok; \
-    } \
-    static _ctype Call##_jname##MethodV(JNIEnv* env, jobject obj, \
-        jmethodID mid, va_list args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->Call##_jname##MethodV(env, obj, mid, args)); \
-        _retok; \
-    } \
-    static _ctype Call##_jname##MethodA(JNIEnv* env, jobject obj, \
-        jmethodID mid, jvalue* args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELm.", env, obj, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->Call##_jname##MethodA(env, obj, mid, args)); \
-        _retok; \
-    } \
-    /* Non-virtual... */ \
-    static _ctype CallNonvirtual##_jname##Method(JNIEnv* env, \
-        jobject obj, jclass c, jmethodID mid, ...) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        va_list args; \
-        va_start(args, mid); \
-        _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \
-        va_end(args); \
-        _retok; \
-    } \
-    static _ctype CallNonvirtual##_jname##MethodV(JNIEnv* env, \
-        jobject obj, jclass c, jmethodID mid, va_list args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodV(env, obj, c, mid, args)); \
-        _retok; \
-    } \
-    static _ctype CallNonvirtual##_jname##MethodA(JNIEnv* env, \
-        jobject obj, jclass c, jmethodID mid, jvalue* args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "ELcm.", env, obj, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, false); \
-        sc.CheckVirtualMethod(obj, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->CallNonvirtual##_jname##MethodA(env, obj, c, mid, args)); \
-        _retok; \
-    } \
-    /* Static... */ \
-    static _ctype CallStatic##_jname##Method(JNIEnv* env, jclass c, jmethodID mid, ...) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, true); \
-        sc.CheckStaticMethod(c, mid); \
-        _retdecl; \
-        va_list args; \
-        va_start(args, mid); \
-        _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \
-        va_end(args); \
-        _retok; \
-    } \
-    static _ctype CallStatic##_jname##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, true); \
-        sc.CheckStaticMethod(c, mid); \
-        _retdecl; \
-         _retasgn(baseEnv(env)->CallStatic##_jname##MethodV(env, c, mid, args)); \
-        _retok; \
-    } \
-    static _ctype CallStatic##_jname##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* args) \
-    { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ecm.", env, c, mid); /* TODO: args! */ \
-        sc.CheckSig(mid, _retsig, true); \
-        sc.CheckStaticMethod(c, mid); \
-        _retdecl; \
-        _retasgn(baseEnv(env)->CallStatic##_jname##MethodA(env, c, mid, args)); \
-        _retok; \
-    }
-
-#define NON_VOID_RETURN(_retsig, _ctype) return CHECK_JNI_EXIT(_retsig, (_ctype) result)
-#define VOID_RETURN CHECK_JNI_EXIT_VOID()
-
-CALL(jobject, Object, mirror::Object* result, result = reinterpret_cast<mirror::Object*>, NON_VOID_RETURN("L", jobject), "L");
-CALL(jboolean, Boolean, jboolean result, result =, NON_VOID_RETURN("Z", jboolean), "Z");
-CALL(jbyte, Byte, jbyte result, result =, NON_VOID_RETURN("B", jbyte), "B");
-CALL(jchar, Char, jchar result, result =, NON_VOID_RETURN("C", jchar), "C");
-CALL(jshort, Short, jshort result, result =, NON_VOID_RETURN("S", jshort), "S");
-CALL(jint, Int, jint result, result =, NON_VOID_RETURN("I", jint), "I");
-CALL(jlong, Long, jlong result, result =, NON_VOID_RETURN("J", jlong), "J");
-CALL(jfloat, Float, jfloat result, result =, NON_VOID_RETURN("F", jfloat), "F");
-CALL(jdouble, Double, jdouble result, result =, NON_VOID_RETURN("D", jdouble), "D");
-CALL(void, Void, , , VOID_RETURN, "V");
-
-  static jstring NewString(JNIEnv* env, const jchar* unicodeChars, jsize len) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Epz", env, unicodeChars, len);
-    return CHECK_JNI_EXIT("s", baseEnv(env)->NewString(env, unicodeChars, len));
-  }
-
-  static jsize GetStringLength(JNIEnv* env, jstring string) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringLength(env, string));
-  }
-
-  static const jchar* GetStringChars(JNIEnv* env, jstring java_string, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, java_string, isCopy);
-    const jchar* result = baseEnv(env)->GetStringChars(env, java_string, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      mirror::String* s = sc.soa().Decode<mirror::String*>(java_string);
-      int byteCount = s->GetLength() * 2;
-      result = (const jchar*) GuardedCopy::Create(result, byteCount, false);
-      if (isCopy != nullptr) {
-        *isCopy = JNI_TRUE;
-      }
-    }
-    return CHECK_JNI_EXIT("p", result);
-  }
-
-  static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Esp", env, string, chars);
-    sc.CheckNonNull(chars);
-    if (sc.ForceCopy()) {
-      GuardedCopy::Check(__FUNCTION__, chars, false);
-      chars = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<jchar*>(chars)));
-    }
-    baseEnv(env)->ReleaseStringChars(env, string, chars);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static jstring NewStringUTF(JNIEnv* env, const char* bytes) {
-    CHECK_JNI_ENTRY(kFlag_NullableUtf, "Eu", env, bytes);  // TODO: show pointer and truncate string.
-    return CHECK_JNI_EXIT("s", baseEnv(env)->NewStringUTF(env, bytes));
-  }
-
-  static jsize GetStringUTFLength(JNIEnv* env, jstring string) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Es", env, string);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetStringUTFLength(env, string));
-  }
-
-  static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Esp", env, string, isCopy);
-    const char* result = baseEnv(env)->GetStringUTFChars(env, string, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      result = (const char*) GuardedCopy::Create(result, strlen(result) + 1, false);
-      if (isCopy != nullptr) {
-        *isCopy = JNI_TRUE;
-      }
-    }
-    return CHECK_JNI_EXIT("u", result);  // TODO: show pointer and truncate string.
-  }
-
-  static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf) {
-    CHECK_JNI_ENTRY(kFlag_ExcepOkay | kFlag_Release, "Esu", env, string, utf);  // TODO: show pointer and truncate string.
-    if (sc.ForceCopy()) {
-      GuardedCopy::Check(__FUNCTION__, utf, false);
-      utf = reinterpret_cast<const char*>(GuardedCopy::Destroy(const_cast<char*>(utf)));
-    }
-    baseEnv(env)->ReleaseStringUTFChars(env, string, utf);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static jsize GetArrayLength(JNIEnv* env, jarray array) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "Ea", env, array);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetArrayLength(env, array));
-  }
-
-  static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass elementClass, jobject initialElement) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EzcL", env, length, elementClass, initialElement);
-    return CHECK_JNI_EXIT("a", baseEnv(env)->NewObjectArray(env, length, elementClass, initialElement));
-  }
-
-  static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EaI", env, array, index);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->GetObjectArrayElement(env, array, index));
-  }
-
-  static void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EaIL", env, array, index, value);
-    baseEnv(env)->SetObjectArrayElement(env, array, index, value);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-#define NEW_PRIMITIVE_ARRAY(_artype, _jname) \
-    static _artype New##_jname##Array(JNIEnv* env, jsize length) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "Ez", env, length); \
-        return CHECK_JNI_EXIT("a", baseEnv(env)->New##_jname##Array(env, length)); \
-    }
-NEW_PRIMITIVE_ARRAY(jbooleanArray, Boolean);
-NEW_PRIMITIVE_ARRAY(jbyteArray, Byte);
-NEW_PRIMITIVE_ARRAY(jcharArray, Char);
-NEW_PRIMITIVE_ARRAY(jshortArray, Short);
-NEW_PRIMITIVE_ARRAY(jintArray, Int);
-NEW_PRIMITIVE_ARRAY(jlongArray, Long);
-NEW_PRIMITIVE_ARRAY(jfloatArray, Float);
-NEW_PRIMITIVE_ARRAY(jdoubleArray, Double);
-
-struct ForceCopyGetChecker {
- public:
-  ForceCopyGetChecker(ScopedCheck& sc, jboolean* isCopy) {
-    force_copy = sc.ForceCopy();
-    no_copy = 0;
-    if (force_copy && isCopy != nullptr) {
-      // Capture this before the base call tramples on it.
-      no_copy = *reinterpret_cast<uint32_t*>(isCopy);
-    }
-  }
-
-  template<typename ResultT>
-  ResultT Check(JNIEnv* env, jarray array, jboolean* isCopy, ResultT result) {
-    if (force_copy && result != nullptr) {
-      result = reinterpret_cast<ResultT>(CreateGuardedPACopy(env, array, isCopy));
-    }
-    return result;
-  }
-
-  uint32_t no_copy;
-  bool force_copy;
-};
-
-#define GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
-  static _ctype* Get##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, jboolean* isCopy) { \
-    CHECK_JNI_ENTRY(kFlag_Default, "Eap", env, array, isCopy); \
-    _ctype* result = ForceCopyGetChecker(sc, isCopy).Check(env, array, isCopy, baseEnv(env)->Get##_jname##ArrayElements(env, array, isCopy)); \
-    return CHECK_JNI_EXIT("p", result); \
-  }
-
-#define RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname) \
-  static void Release##_jname##ArrayElements(JNIEnv* env, _ctype##Array array, _ctype* elems, jint mode) { \
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "Eapr", env, array, elems, mode); \
-    sc.CheckNonNull(elems); \
-    if (sc.ForceCopy()) { \
-      ReleaseGuardedPACopy(env, array, elems, mode); \
-    } \
-    baseEnv(env)->Release##_jname##ArrayElements(env, array, elems, mode); \
-    CHECK_JNI_EXIT_VOID(); \
-  }
-
-#define GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
-    static void Get##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, _ctype* buf) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \
-        baseEnv(env)->Get##_jname##ArrayRegion(env, array, start, len, buf); \
-        CHECK_JNI_EXIT_VOID(); \
-    }
-
-#define SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname) \
-    static void Set##_jname##ArrayRegion(JNIEnv* env, _ctype##Array array, jsize start, jsize len, const _ctype* buf) { \
-        CHECK_JNI_ENTRY(kFlag_Default, "EaIIp", env, array, start, len, buf); \
-        baseEnv(env)->Set##_jname##ArrayRegion(env, array, start, len, buf); \
-        CHECK_JNI_EXIT_VOID(); \
-    }
-
-#define PRIMITIVE_ARRAY_FUNCTIONS(_ctype, _jname, _typechar) \
-    GET_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \
-    RELEASE_PRIMITIVE_ARRAY_ELEMENTS(_ctype, _jname); \
-    GET_PRIMITIVE_ARRAY_REGION(_ctype, _jname); \
-    SET_PRIMITIVE_ARRAY_REGION(_ctype, _jname);
-
-// TODO: verify primitive array type matches call type.
-PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, 'Z');
-PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, 'B');
-PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, 'C');
-PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, 'S');
-PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, 'I');
-PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, 'J');
-PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, 'F');
-PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, 'D');
-
-  static jint RegisterNatives(JNIEnv* env, jclass c, const JNINativeMethod* methods, jint nMethods) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EcpI", env, c, methods, nMethods);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->RegisterNatives(env, c, methods, nMethods));
-  }
-
-  static jint UnregisterNatives(JNIEnv* env, jclass c) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ec", env, c);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->UnregisterNatives(env, c));
-  }
-
-  static jint MonitorEnter(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    if (!sc.CheckInstance(ScopedCheck::kObject, obj)) {
-      return JNI_ERR;  // Only for jni_internal_test. Real code will have aborted already.
-    }
-    return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorEnter(env, obj));
-  }
-
-  static jint MonitorExit(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default | kFlag_ExcepOkay, "EL", env, obj);
-    if (!sc.CheckInstance(ScopedCheck::kObject, obj)) {
-      return JNI_ERR;  // Only for jni_internal_test. Real code will have aborted already.
-    }
-    return CHECK_JNI_EXIT("I", baseEnv(env)->MonitorExit(env, obj));
-  }
-
-  static jint GetJavaVM(JNIEnv *env, JavaVM **vm) {
-    CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, vm);
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetJavaVM(env, vm));
-  }
-
-  static void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf);
-    baseEnv(env)->GetStringRegion(env, str, start, len, buf);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start, jsize len, char* buf) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay, "EsIIp", env, str, start, len, buf);
-    baseEnv(env)->GetStringUTFRegion(env, str, start, len, buf);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritGet, "Eap", env, array, isCopy);
-    void* result = baseEnv(env)->GetPrimitiveArrayCritical(env, array, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      result = CreateGuardedPACopy(env, array, isCopy);
-    }
-    return CHECK_JNI_EXIT("p", result);
-  }
-
-  static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) {
-    CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Eapr", env, array, carray, mode);
-    sc.CheckNonNull(carray);
-    if (sc.ForceCopy()) {
-      ReleaseGuardedPACopy(env, array, carray, mode);
-    }
-    baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode);
-    CHECK_JNI_EXIT_VOID();
-  }
-
-  static const jchar* GetStringCritical(JNIEnv* env, jstring java_string, jboolean* isCopy) {
-    CHECK_JNI_ENTRY(kFlag_CritGet, "Esp", env, java_string, isCopy);
-    const jchar* result = baseEnv(env)->GetStringCritical(env, java_string, isCopy);
-    if (sc.ForceCopy() && result != nullptr) {
-      mirror::String* s = sc.soa().Decode<mirror::String*>(java_string);
-      int byteCount = s->GetLength() * 2;
-      result = (const jchar*) GuardedCopy::Create(result, byteCount, false);
-      if (isCopy != nullptr) {
-        *isCopy = JNI_TRUE;
-      }
-    }
-    return CHECK_JNI_EXIT("p", result);
-  }
-
-  static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* carray) {
-    CHECK_JNI_ENTRY(kFlag_CritRelease | kFlag_ExcepOkay, "Esp", env, string, carray);
-    sc.CheckNonNull(carray);
-    if (sc.ForceCopy()) {
-      GuardedCopy::Check(__FUNCTION__, carray, false);
-      carray = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<jchar*>(carray)));
-    }
-    baseEnv(env)->ReleaseStringCritical(env, string, carray);
-    CHECK_JNI_EXIT_VOID();
+  static jobject NewLocalRef(JNIEnv* env, jobject obj) {
+    return NewRef(__FUNCTION__, env, obj, kLocal);
   }
 
   static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, obj);
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewWeakGlobalRef(env, obj));
+    return NewRef(__FUNCTION__, env, obj, kWeakGlobal);
   }
 
-  static jboolean ExceptionCheck(JNIEnv* env) {
-    CHECK_JNI_ENTRY(kFlag_CritOkay | kFlag_ExcepOkay, "E", env);
-    return CHECK_JNI_EXIT("b", baseEnv(env)->ExceptionCheck(env));
+  static void DeleteGlobalRef(JNIEnv* env, jobject obj) {
+    DeleteRef(__FUNCTION__, env, obj, kGlobal);
   }
 
-  static jobjectRefType GetObjectRefType(JNIEnv* env, jobject obj) {
-    // Note: we use "Ep" rather than "EL" because this is the one JNI function
-    // that it's okay to pass an invalid reference to.
-    CHECK_JNI_ENTRY(kFlag_Default, "Ep", env, obj);
-    // TODO: proper decoding of jobjectRefType!
-    return CHECK_JNI_EXIT("I", baseEnv(env)->GetObjectRefType(env, obj));
+  static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
+    DeleteRef(__FUNCTION__, env, obj, kWeakGlobal);
+  }
+
+  static void DeleteLocalRef(JNIEnv* env, jobject obj) {
+    DeleteRef(__FUNCTION__, env, obj, kLocal);
+  }
+
+  static jint EnsureLocalCapacity(JNIEnv *env, jint capacity) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.I = capacity}};
+    if (sc.Check(soa, true, "EI", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->EnsureLocalCapacity(env, capacity);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jboolean IsSameObject(JNIEnv* env, jobject ref1, jobject ref2) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.L = ref1}, {.L = ref2}};
+    if (sc.Check(soa, true, "ELL", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->IsSameObject(env, ref1, ref2);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
+  }
+
+  static jobject AllocObject(JNIEnv* env, jclass c) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.c = c}};
+    if (sc.Check(soa, true, "Ec", args) && sc.CheckInstantiableNonArray(soa, c)) {
+      JniValueType result;
+      result.L = baseEnv(env)->AllocObject(env, c);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static jobject NewObjectV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}};
+    if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) &&
+        sc.CheckConstructor(soa, mid)) {
+      JniValueType result;
+      result.L = baseEnv(env)->NewObjectV(env, c, mid, vargs);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static jobject NewObject(JNIEnv* env, jclass c, jmethodID mid, ...) {
+    va_list args;
+    va_start(args, mid);
+    jobject result = NewObjectV(env, c, mid, args);
+    va_end(args);
+    return result;
+  }
+
+  static jobject NewObjectA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}};
+    if (sc.Check(soa, true, "Ecm.", args) && sc.CheckInstantiableNonArray(soa, c) &&
+        sc.CheckConstructor(soa, mid)) {
+      JniValueType result;
+      result.L = baseEnv(env)->NewObjectA(env, c, mid, vargs);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static jclass GetObjectClass(JNIEnv* env, jobject obj) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.c = baseEnv(env)->GetObjectClass(env, obj);
+      if (sc.Check(soa, false, "c", &result)) {
+        return result.c;
+      }
+    }
+    return nullptr;
+  }
+
+  static jboolean IsInstanceOf(JNIEnv* env, jobject obj, jclass c) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.L = obj}, {.c = c}};
+    if (sc.Check(soa, true, "ELc", args)) {
+      JniValueType result;
+      result.b = baseEnv(env)->IsInstanceOf(env, obj, c);
+      if (sc.Check(soa, false, "b", &result)) {
+        return result.b;
+      }
+    }
+    return JNI_FALSE;
+  }
+
+  static jmethodID GetMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetMethodIDInternal(__FUNCTION__, env, c, name, sig, false);
+  }
+
+  static jmethodID GetStaticMethodID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetMethodIDInternal(__FUNCTION__, env, c, name, sig, true);
+  }
+
+  static jfieldID GetFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetFieldIDInternal(__FUNCTION__, env, c, name, sig, false);
+  }
+
+  static jfieldID GetStaticFieldID(JNIEnv* env, jclass c, const char* name, const char* sig) {
+    return GetFieldIDInternal(__FUNCTION__, env, c, name, sig, true);
+  }
+
+#define FIELD_ACCESSORS(jtype, name, ptype, shorty) \
+  static jtype GetStatic##name##Field(JNIEnv* env, jclass c, jfieldID fid) { \
+    return GetField(__FUNCTION__, env, c, fid, true, ptype).shorty; \
+  } \
+  \
+  static jtype Get##name##Field(JNIEnv* env, jobject obj, jfieldID fid) { \
+    return GetField(__FUNCTION__, env, obj, fid, false, ptype).shorty; \
+  } \
+  \
+  static void SetStatic##name##Field(JNIEnv* env, jclass c, jfieldID fid, jtype v) { \
+    JniValueType value; \
+    value.shorty = v; \
+    SetField(__FUNCTION__, env, c, fid, true, ptype, value); \
+  } \
+  \
+  static void Set##name##Field(JNIEnv* env, jobject obj, jfieldID fid, jtype v) { \
+    JniValueType value; \
+    value.shorty = v; \
+    SetField(__FUNCTION__, env, obj, fid, false, ptype, value); \
+  }
+
+  FIELD_ACCESSORS(jobject, Object, Primitive::kPrimNot, L)
+  FIELD_ACCESSORS(jboolean, Boolean, Primitive::kPrimBoolean, Z)
+  FIELD_ACCESSORS(jbyte, Byte, Primitive::kPrimByte, B)
+  FIELD_ACCESSORS(jchar, Char, Primitive::kPrimChar, C)
+  FIELD_ACCESSORS(jshort, Short, Primitive::kPrimShort, S)
+  FIELD_ACCESSORS(jint, Int, Primitive::kPrimInt, I)
+  FIELD_ACCESSORS(jlong, Long, Primitive::kPrimLong, J)
+  FIELD_ACCESSORS(jfloat, Float, Primitive::kPrimFloat, F)
+  FIELD_ACCESSORS(jdouble, Double, Primitive::kPrimDouble, D)
+#undef FIELD_ACCESSORS
+
+  static void CallVoidMethodA(JNIEnv* env, jobject obj, jmethodID mid, jvalue* vargs) {
+    CallMethodA(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual);
+  }
+
+  static void CallNonvirtualVoidMethodA(JNIEnv* env, jobject obj, jclass c, jmethodID mid,
+                                        jvalue* vargs) {
+    CallMethodA(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
+  }
+
+  static void CallStaticVoidMethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) {
+    CallMethodA(__FUNCTION__, env, c, nullptr, mid, vargs, Primitive::kPrimVoid, kStatic);
+  }
+
+  static void CallVoidMethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list vargs) {
+    CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual);
+  }
+
+  static void CallNonvirtualVoidMethodV(JNIEnv* env, jobject obj, jclass c, jmethodID mid,
+                                        va_list vargs) {
+    CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
+  }
+
+  static void CallStaticVoidMethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) {
+    CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic);
+  }
+
+  static void CallVoidMethod(JNIEnv* env, jobject obj, jmethodID mid, ...) {
+    va_list vargs;
+    va_start(vargs, mid);
+    CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, Primitive::kPrimVoid, kVirtual);
+    va_end(vargs);
+  }
+
+  static void CallNonvirtualVoidMethod(JNIEnv* env, jobject obj, jclass c, jmethodID mid, ...) {
+    va_list vargs;
+    va_start(vargs, mid);
+    CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, Primitive::kPrimVoid, kDirect);
+    va_end(vargs);
+  }
+
+  static void CallStaticVoidMethod(JNIEnv* env, jclass c, jmethodID mid, ...) {
+    va_list vargs;
+    va_start(vargs, mid);
+    CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, Primitive::kPrimVoid, kStatic);
+    va_end(vargs);
+  }
+
+#define CALL(rtype, name, ptype, shorty) \
+  static rtype Call##name##MethodA(JNIEnv* env, jobject obj, jmethodID mid, jvalue* vargs) { \
+    return CallMethodA(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \
+  } \
+  \
+  static rtype CallNonvirtual##name##MethodA(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \
+                                             jvalue* vargs) { \
+    return CallMethodA(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \
+  } \
+  \
+  static rtype CallStatic##name##MethodA(JNIEnv* env, jclass c, jmethodID mid, jvalue* vargs) { \
+    return CallMethodA(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \
+  } \
+  \
+  static rtype Call##name##MethodV(JNIEnv* env, jobject obj, jmethodID mid, va_list vargs) { \
+    return CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \
+  } \
+  \
+  static rtype CallNonvirtual##name##MethodV(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \
+                                             va_list vargs) { \
+    return CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \
+  } \
+  \
+  static rtype CallStatic##name##MethodV(JNIEnv* env, jclass c, jmethodID mid, va_list vargs) { \
+    return CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \
+  } \
+  \
+  static rtype Call##name##Method(JNIEnv* env, jobject obj, jmethodID mid, ...) { \
+    va_list vargs; \
+    va_start(vargs, mid); \
+    rtype result = \
+        CallMethodV(__FUNCTION__, env, obj, nullptr, mid, vargs, ptype, kVirtual).shorty; \
+    va_end(vargs); \
+    return result; \
+  } \
+  \
+  static rtype CallNonvirtual##name##Method(JNIEnv* env, jobject obj, jclass c, jmethodID mid, \
+                                            ...) { \
+    va_list vargs; \
+    va_start(vargs, mid); \
+    rtype result = \
+        CallMethodV(__FUNCTION__, env, obj, c, mid, vargs, ptype, kDirect).shorty; \
+    va_end(vargs); \
+    return result; \
+  } \
+  \
+  static rtype CallStatic##name##Method(JNIEnv* env, jclass c, jmethodID mid, ...) { \
+    va_list vargs; \
+    va_start(vargs, mid); \
+    rtype result = \
+        CallMethodV(__FUNCTION__, env, nullptr, c, mid, vargs, ptype, kStatic).shorty; \
+    va_end(vargs); \
+    return result; \
+  }
+
+  CALL(jobject, Object, Primitive::kPrimNot, L)
+  CALL(jboolean, Boolean, Primitive::kPrimBoolean, Z)
+  CALL(jbyte, Byte, Primitive::kPrimByte, B)
+  CALL(jchar, Char, Primitive::kPrimChar, C)
+  CALL(jshort, Short, Primitive::kPrimShort, S)
+  CALL(jint, Int, Primitive::kPrimInt, I)
+  CALL(jlong, Long, Primitive::kPrimLong, J)
+  CALL(jfloat, Float, Primitive::kPrimFloat, F)
+  CALL(jdouble, Double, Primitive::kPrimDouble, D)
+#undef CALL
+
+  static jstring NewString(JNIEnv* env, const jchar* unicode_chars, jsize len) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.p = unicode_chars}, {.z = len}};
+    if (sc.Check(soa, true, "Epz", args)) {
+      JniValueType result;
+      result.s = baseEnv(env)->NewString(env, unicode_chars, len);
+      if (sc.Check(soa, false, "s", &result)) {
+        return result.s;
+      }
+    }
+    return nullptr;
+  }
+
+  static jstring NewStringUTF(JNIEnv* env, const char* chars) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_NullableUtf, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.u = chars}};
+    if (sc.Check(soa, true, "Eu", args)) {
+      JniValueType result;
+      // TODO: stale? show pointer and truncate string.
+      result.s = baseEnv(env)->NewStringUTF(env, chars);
+      if (sc.Check(soa, false, "s", &result)) {
+        return result.s;
+      }
+    }
+    return nullptr;
+  }
+
+  static jsize GetStringLength(JNIEnv* env, jstring string) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.s = string}};
+    if (sc.Check(soa, true, "Es", args)) {
+      JniValueType result;
+      result.z = baseEnv(env)->GetStringLength(env, string);
+      if (sc.Check(soa, false, "z", &result)) {
+        return result.z;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jsize GetStringUTFLength(JNIEnv* env, jstring string) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.s = string}};
+    if (sc.Check(soa, true, "Es", args)) {
+      JniValueType result;
+      result.z = baseEnv(env)->GetStringUTFLength(env, string);
+      if (sc.Check(soa, false, "z", &result)) {
+        return result.z;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static const jchar* GetStringChars(JNIEnv* env, jstring string, jboolean* is_copy) {
+    return reinterpret_cast<const jchar*>(GetStringCharsInternal(__FUNCTION__, env, string,
+                                                                 is_copy, false, false));
+  }
+
+  static const char* GetStringUTFChars(JNIEnv* env, jstring string, jboolean* is_copy) {
+    return reinterpret_cast<const char*>(GetStringCharsInternal(__FUNCTION__, env, string,
+                                                                is_copy, true, false));
+  }
+
+  static const jchar* GetStringCritical(JNIEnv* env, jstring string, jboolean* is_copy) {
+    return reinterpret_cast<const jchar*>(GetStringCharsInternal(__FUNCTION__, env, string,
+                                                                 is_copy, false, true));
+  }
+
+  static void ReleaseStringChars(JNIEnv* env, jstring string, const jchar* chars) {
+    ReleaseStringCharsInternal(__FUNCTION__, env, string, chars, false, false);
+  }
+
+  static void ReleaseStringUTFChars(JNIEnv* env, jstring string, const char* utf) {
+    ReleaseStringCharsInternal(__FUNCTION__, env, string, utf, true, false);
+  }
+
+  static void ReleaseStringCritical(JNIEnv* env, jstring string, const jchar* chars) {
+    ReleaseStringCharsInternal(__FUNCTION__, env, string, chars, false, true);
+  }
+
+  static void GetStringRegion(JNIEnv* env, jstring string, jsize start, jsize len, jchar* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.s = string}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EsIIp", args)) {
+      baseEnv(env)->GetStringRegion(env, string, start, len, buf);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static void GetStringUTFRegion(JNIEnv* env, jstring string, jsize start, jsize len, char* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[5] = {{.E = env}, {.s = string}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EsIIp", args)) {
+      baseEnv(env)->GetStringUTFRegion(env, string, start, len, buf);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jsize GetArrayLength(JNIEnv* env, jarray array) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.a = array}};
+    if (sc.Check(soa, true, "Ea", args)) {
+      JniValueType result;
+      result.z = baseEnv(env)->GetArrayLength(env, array);
+      if (sc.Check(soa, false, "z", &result)) {
+        return result.z;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_class,
+                                     jobject initial_element) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] =
+        {{.E = env}, {.z = length}, {.c = element_class}, {.L = initial_element}};
+    if (sc.Check(soa, true, "EzcL", args)) {
+      JniValueType result;
+      // Note: assignability tests of initial_element are done in the base implementation.
+      result.a = baseEnv(env)->NewObjectArray(env, length, element_class, initial_element);
+      if (sc.Check(soa, false, "a", &result)) {
+        return down_cast<jobjectArray>(result.a);
+      }
+    }
+    return nullptr;
+  }
+
+  static jobject GetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.a = array}, {.z = index}};
+    if (sc.Check(soa, true, "Eaz", args)) {
+      JniValueType result;
+      result.L = baseEnv(env)->GetObjectArrayElement(env, array, index);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static void SetObjectArrayElement(JNIEnv* env, jobjectArray array, jsize index, jobject value) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[4] = {{.E = env}, {.a = array}, {.z = index}, {.L = value}};
+    // Note: the index arguments is checked as 'I' rather than 'z' as invalid indices result in
+    // ArrayIndexOutOfBoundsExceptions in the base implementation. Similarly invalid stores result
+    // in ArrayStoreExceptions.
+    if (sc.Check(soa, true, "EaIL", args)) {
+      baseEnv(env)->SetObjectArrayElement(env, array, index, value);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jbooleanArray NewBooleanArray(JNIEnv* env, jsize length) {
+    return down_cast<jbooleanArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                      Primitive::kPrimBoolean));
+  }
+
+  static jbyteArray NewByteArray(JNIEnv* env, jsize length) {
+    return down_cast<jbyteArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                   Primitive::kPrimByte));
+  }
+
+  static jcharArray NewCharArray(JNIEnv* env, jsize length) {
+    return down_cast<jcharArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                   Primitive::kPrimChar));
+  }
+
+  static jshortArray NewShortArray(JNIEnv* env, jsize length) {
+    return down_cast<jshortArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                    Primitive::kPrimShort));
+  }
+
+  static jintArray NewIntArray(JNIEnv* env, jsize length) {
+    return down_cast<jintArray>(NewPrimitiveArray(__FUNCTION__, env, length, Primitive::kPrimInt));
+  }
+
+  static jlongArray NewLongArray(JNIEnv* env, jsize length) {
+    return down_cast<jlongArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                   Primitive::kPrimLong));
+  }
+
+  static jfloatArray NewFloatArray(JNIEnv* env, jsize length) {
+    return down_cast<jfloatArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                    Primitive::kPrimFloat));
+  }
+
+  static jdoubleArray NewDoubleArray(JNIEnv* env, jsize length) {
+    return down_cast<jdoubleArray>(NewPrimitiveArray(__FUNCTION__, env, length,
+                                                     Primitive::kPrimDouble));
+  }
+
+#define PRIMITIVE_ARRAY_FUNCTIONS(ctype, name, ptype) \
+  static ctype* Get##name##ArrayElements(JNIEnv* env, ctype##Array array, jboolean* is_copy) { \
+    return reinterpret_cast<ctype*>( \
+        GetPrimitiveArrayElements(__FUNCTION__, ptype, env, array, is_copy)); \
+  } \
+  \
+  static void Release##name##ArrayElements(JNIEnv* env, ctype##Array array, ctype* elems, \
+                                           jint mode) { \
+    ReleasePrimitiveArrayElements(__FUNCTION__, ptype, env, array, elems, mode); \
+  } \
+  \
+  static void Get##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \
+                                     ctype* buf) { \
+    GetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \
+  } \
+  \
+  static void Set##name##ArrayRegion(JNIEnv* env, ctype##Array array, jsize start, jsize len, \
+                                     const ctype* buf) { \
+    SetPrimitiveArrayRegion(__FUNCTION__, ptype, env, array, start, len, buf); \
+  }
+
+  PRIMITIVE_ARRAY_FUNCTIONS(jboolean, Boolean, Primitive::kPrimBoolean)
+  PRIMITIVE_ARRAY_FUNCTIONS(jbyte, Byte, Primitive::kPrimByte)
+  PRIMITIVE_ARRAY_FUNCTIONS(jchar, Char, Primitive::kPrimChar)
+  PRIMITIVE_ARRAY_FUNCTIONS(jshort, Short, Primitive::kPrimShort)
+  PRIMITIVE_ARRAY_FUNCTIONS(jint, Int, Primitive::kPrimInt)
+  PRIMITIVE_ARRAY_FUNCTIONS(jlong, Long, Primitive::kPrimLong)
+  PRIMITIVE_ARRAY_FUNCTIONS(jfloat, Float, Primitive::kPrimFloat)
+  PRIMITIVE_ARRAY_FUNCTIONS(jdouble, Double, Primitive::kPrimDouble)
+#undef PRIMITIVE_ARRAY_FUNCTIONS
+
+  static jint MonitorEnter(JNIEnv* env, jobject obj) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->MonitorEnter(env, obj);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static jint MonitorExit(JNIEnv* env, jobject obj) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      result.i = baseEnv(env)->MonitorExit(env, obj);
+      if (sc.Check(soa, false, "i", &result)) {
+        return result.i;
+      }
+    }
+    return JNI_ERR;
+  }
+
+  static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* is_copy) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritGet, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.a = array}, {.p = is_copy}};
+    if (sc.Check(soa, true, "Eap", args)) {
+      JniValueType result;
+      result.p = baseEnv(env)->GetPrimitiveArrayCritical(env, array, is_copy);
+      if (result.p != nullptr && soa.ForceCopy()) {
+        result.p = GuardedCopy::CreateGuardedPACopy(env, array, is_copy);
+      }
+      if (sc.Check(soa, false, "p", &result)) {
+        return const_cast<void*>(result.p);
+      }
+    }
+    return nullptr;
+  }
+
+  static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_CritRelease | kFlag_ExcepOkay, __FUNCTION__);
+    sc.CheckNonNull(carray);
+    JniValueType args[4] = {{.E = env}, {.a = array}, {.p = carray}, {.r = mode}};
+    if (sc.Check(soa, true, "Eapr", args)) {
+      if (soa.ForceCopy()) {
+        GuardedCopy::ReleaseGuardedPACopy(__FUNCTION__, env, array, carray, mode);
+      }
+      baseEnv(env)->ReleasePrimitiveArrayCritical(env, array, carray, mode);
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
   }
 
   static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EpJ", env, address, capacity);
-    if (address == nullptr) {
-      JniAbortF(__FUNCTION__, "non-nullable address is NULL");
-      return nullptr;
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[3] = {{.E = env}, {.p = address}, {.J = capacity}};
+    if (sc.Check(soa, true, "EpJ", args)) {
+      JniValueType result;
+      // Note: the validity of address and capacity are checked in the base implementation.
+      result.L = baseEnv(env)->NewDirectByteBuffer(env, address, capacity);
+      if (sc.Check(soa, false, "L", &result)) {
+        return result.L;
+      }
     }
-    return CHECK_JNI_EXIT("L", baseEnv(env)->NewDirectByteBuffer(env, address, capacity));
+    return nullptr;
   }
 
   static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf);
-    // TODO: check that 'buf' is a java.nio.Buffer.
-    return CHECK_JNI_EXIT("p", baseEnv(env)->GetDirectBufferAddress(env, buf));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = buf}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      // Note: this is implemented in the base environment by a GetLongField which will sanity
+      // check the type of buf in GetLongField above.
+      result.p = baseEnv(env)->GetDirectBufferAddress(env, buf);
+      if (sc.Check(soa, false, "p", &result)) {
+        return const_cast<void*>(result.p);
+      }
+    }
+    return nullptr;
   }
 
   static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) {
-    CHECK_JNI_ENTRY(kFlag_Default, "EL", env, buf);
-    // TODO: check that 'buf' is a java.nio.Buffer.
-    return CHECK_JNI_EXIT("J", baseEnv(env)->GetDirectBufferCapacity(env, buf));
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.L = buf}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      // Note: this is implemented in the base environment by a GetIntField which will sanity
+      // check the type of buf in GetIntField above.
+      result.J = baseEnv(env)->GetDirectBufferCapacity(env, buf);
+      if (sc.Check(soa, false, "J", &result)) {
+        return result.J;
+      }
+    }
+    return JNI_ERR;
   }
 
  private:
-  static inline const JNINativeInterface* baseEnv(JNIEnv* env) {
+  static JavaVMExt* GetJavaVMExt(JNIEnv* env) {
+    return reinterpret_cast<JNIEnvExt*>(env)->vm;
+  }
+
+  static const JNINativeInterface* baseEnv(JNIEnv* env) {
     return reinterpret_cast<JNIEnvExt*>(env)->unchecked_functions;
   }
+
+  static jobject NewRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    if (sc.Check(soa, true, "EL", args)) {
+      JniValueType result;
+      switch (kind) {
+        case kGlobal:
+          result.L = baseEnv(env)->NewGlobalRef(env, obj);
+          break;
+        case kLocal:
+          result.L = baseEnv(env)->NewLocalRef(env, obj);
+          break;
+        case kWeakGlobal:
+          result.L = baseEnv(env)->NewWeakGlobalRef(env, obj);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected reference kind: " << kind;
+      }
+      if (sc.Check(soa, false, "L", &result)) {
+        DCHECK_EQ(IsSameObject(env, obj, result.L), JNI_TRUE);
+        DCHECK(sc.CheckReferenceKind(kind, soa.Vm(), soa.Self(), result.L));
+        return result.L;
+      }
+    }
+    return nullptr;
+  }
+
+  static void DeleteRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, function_name);
+    JniValueType args[2] = {{.E = env}, {.L = obj}};
+    sc.Check(soa, true, "EL", args);
+    if (sc.CheckReferenceKind(kind, soa.Vm(), soa.Self(), obj)) {
+      JniValueType result;
+      switch (kind) {
+        case kGlobal:
+          baseEnv(env)->DeleteGlobalRef(env, obj);
+          break;
+        case kLocal:
+          baseEnv(env)->DeleteLocalRef(env, obj);
+          break;
+        case kWeakGlobal:
+          baseEnv(env)->DeleteWeakGlobalRef(env, obj);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected reference kind: " << kind;
+      }
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static jmethodID GetMethodIDInternal(const char* function_name, JNIEnv* env, jclass c,
+                                       const char* name, const char* sig, bool is_static) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[4] = {{.E = env}, {.c = c}, {.u = name}, {.u = sig}};
+    if (sc.Check(soa, true, "Ecuu", args)) {
+      JniValueType result;
+      if (is_static) {
+        result.m = baseEnv(env)->GetStaticMethodID(env, c, name, sig);
+      } else {
+        result.m = baseEnv(env)->GetMethodID(env, c, name, sig);
+      }
+      if (sc.Check(soa, false, "m", &result)) {
+        return result.m;
+      }
+    }
+    return nullptr;
+  }
+
+  static jfieldID GetFieldIDInternal(const char* function_name, JNIEnv* env, jclass c,
+                                     const char* name, const char* sig, bool is_static) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[4] = {{.E = env}, {.c = c}, {.u = name}, {.u = sig}};
+    if (sc.Check(soa, true, "Ecuu", args)) {
+      JniValueType result;
+      if (is_static) {
+        result.f = baseEnv(env)->GetStaticFieldID(env, c, name, sig);
+      } else {
+        result.f = baseEnv(env)->GetFieldID(env, c, name, sig);
+      }
+      if (sc.Check(soa, false, "f", &result)) {
+        return result.f;
+      }
+    }
+    return nullptr;
+  }
+
+  static JniValueType GetField(const char* function_name, JNIEnv* env, jobject obj, jfieldID fid,
+                               bool is_static, Primitive::Type type) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[3] = {{.E = env}, {.L = obj}, {.f = fid}};
+    JniValueType result;
+    if (sc.Check(soa, true, is_static ? "Ecf" : "ELf", args) &&
+        sc.CheckFieldAccess(soa, obj, fid, is_static, type)) {
+      const char* result_check = nullptr;
+      switch (type) {
+        case Primitive::kPrimNot:
+          if (is_static) {
+            result.L = baseEnv(env)->GetStaticObjectField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.L = baseEnv(env)->GetObjectField(env, obj, fid);
+          }
+          result_check = "L";
+          break;
+        case Primitive::kPrimBoolean:
+          if (is_static) {
+            result.Z = baseEnv(env)->GetStaticBooleanField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.Z = baseEnv(env)->GetBooleanField(env, obj, fid);
+          }
+          result_check = "Z";
+          break;
+        case Primitive::kPrimByte:
+          if (is_static) {
+            result.B = baseEnv(env)->GetStaticByteField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.B = baseEnv(env)->GetByteField(env, obj, fid);
+          }
+          result_check = "B";
+          break;
+        case Primitive::kPrimChar:
+          if (is_static) {
+            result.C = baseEnv(env)->GetStaticCharField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.C = baseEnv(env)->GetCharField(env, obj, fid);
+          }
+          result_check = "C";
+          break;
+        case Primitive::kPrimShort:
+          if (is_static) {
+            result.S = baseEnv(env)->GetStaticShortField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.S = baseEnv(env)->GetShortField(env, obj, fid);
+          }
+          result_check = "S";
+          break;
+        case Primitive::kPrimInt:
+          if (is_static) {
+            result.I = baseEnv(env)->GetStaticIntField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.I = baseEnv(env)->GetIntField(env, obj, fid);
+          }
+          result_check = "I";
+          break;
+        case Primitive::kPrimLong:
+          if (is_static) {
+            result.J = baseEnv(env)->GetStaticLongField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.J = baseEnv(env)->GetLongField(env, obj, fid);
+          }
+          result_check = "J";
+          break;
+        case Primitive::kPrimFloat:
+          if (is_static) {
+            result.F = baseEnv(env)->GetStaticFloatField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.F = baseEnv(env)->GetFloatField(env, obj, fid);
+          }
+          result_check = "F";
+          break;
+        case Primitive::kPrimDouble:
+          if (is_static) {
+            result.D = baseEnv(env)->GetStaticDoubleField(env, down_cast<jclass>(obj), fid);
+          } else {
+            result.D = baseEnv(env)->GetDoubleField(env, obj, fid);
+          }
+          result_check = "D";
+          break;
+        case Primitive::kPrimVoid:
+          LOG(FATAL) << "Unexpected type: " << type;
+          break;
+      }
+      if (sc.Check(soa, false, result_check, &result)) {
+        return result;
+      }
+    }
+    result.J = 0;
+    return result;
+  }
+
+  static void SetField(const char* function_name, JNIEnv* env, jobject obj, jfieldID fid,
+                       bool is_static, Primitive::Type type, JniValueType value) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[4] = {{.E = env}, {.L = obj}, {.f = fid}, value};
+    char sig[5] = { 'E', is_static ? 'c' : 'L', 'f',
+        type == Primitive::kPrimNot ? 'L' : Primitive::Descriptor(type)[0], '\0'};
+    if (sc.Check(soa, true, sig, args) &&
+        sc.CheckFieldAccess(soa, obj, fid, is_static, type)) {
+      switch (type) {
+        case Primitive::kPrimNot:
+          if (is_static) {
+            baseEnv(env)->SetStaticObjectField(env, down_cast<jclass>(obj), fid, value.L);
+          } else {
+            baseEnv(env)->SetObjectField(env, obj, fid, value.L);
+          }
+          break;
+        case Primitive::kPrimBoolean:
+          if (is_static) {
+            baseEnv(env)->SetStaticBooleanField(env, down_cast<jclass>(obj), fid, value.Z);
+          } else {
+            baseEnv(env)->SetBooleanField(env, obj, fid, value.Z);
+          }
+          break;
+        case Primitive::kPrimByte:
+          if (is_static) {
+            baseEnv(env)->SetStaticByteField(env, down_cast<jclass>(obj), fid, value.B);
+          } else {
+            baseEnv(env)->SetByteField(env, obj, fid, value.B);
+          }
+          break;
+        case Primitive::kPrimChar:
+          if (is_static) {
+            baseEnv(env)->SetStaticCharField(env, down_cast<jclass>(obj), fid, value.C);
+          } else {
+            baseEnv(env)->SetCharField(env, obj, fid, value.C);
+          }
+          break;
+        case Primitive::kPrimShort:
+          if (is_static) {
+            baseEnv(env)->SetStaticShortField(env, down_cast<jclass>(obj), fid, value.S);
+          } else {
+            baseEnv(env)->SetShortField(env, obj, fid, value.S);
+          }
+          break;
+        case Primitive::kPrimInt:
+          if (is_static) {
+            baseEnv(env)->SetStaticIntField(env, down_cast<jclass>(obj), fid, value.I);
+          } else {
+            baseEnv(env)->SetIntField(env, obj, fid, value.I);
+          }
+          break;
+        case Primitive::kPrimLong:
+          if (is_static) {
+            baseEnv(env)->SetStaticLongField(env, down_cast<jclass>(obj), fid, value.J);
+          } else {
+            baseEnv(env)->SetLongField(env, obj, fid, value.J);
+          }
+          break;
+        case Primitive::kPrimFloat:
+          if (is_static) {
+            baseEnv(env)->SetStaticFloatField(env, down_cast<jclass>(obj), fid, value.F);
+          } else {
+            baseEnv(env)->SetFloatField(env, obj, fid, value.F);
+          }
+          break;
+        case Primitive::kPrimDouble:
+          if (is_static) {
+            baseEnv(env)->SetStaticDoubleField(env, down_cast<jclass>(obj), fid, value.D);
+          } else {
+            baseEnv(env)->SetDoubleField(env, obj, fid, value.D);
+          }
+          break;
+        case Primitive::kPrimVoid:
+          LOG(FATAL) << "Unexpected type: " << type;
+          break;
+      }
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static bool CheckCallArgs(ScopedObjectAccess& soa, ScopedCheck& sc, JNIEnv* env, jobject obj,
+                            jclass c, jmethodID mid, InvokeType invoke)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    bool checked;
+    switch (invoke) {
+      case kVirtual: {
+        DCHECK(c == nullptr);
+        JniValueType args[3] = {{.E = env}, {.L = obj}, {.m = mid}};
+        checked = sc.Check(soa, true, "ELm.", args);
+        break;
+      }
+      case kDirect: {
+        JniValueType args[4] = {{.E = env}, {.L = obj}, {.c = c}, {.m = mid}};
+        checked = sc.Check(soa, true, "ELcm.", args);
+        break;
+      }
+      case kStatic: {
+        DCHECK(obj == nullptr);
+        JniValueType args[3] = {{.E = env}, {.c = c}, {.m = mid}};
+        checked = sc.Check(soa, true, "Ecm.", args);
+        break;
+      }
+      default:
+        LOG(FATAL) << "Unexpected invoke: " << invoke;
+        checked = false;
+        break;
+    }
+    return checked;
+  }
+
+  static JniValueType CallMethodA(const char* function_name, JNIEnv* env, jobject obj, jclass c,
+                                  jmethodID mid, jvalue* vargs, Primitive::Type type,
+                                  InvokeType invoke) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType result;
+    if (CheckCallArgs(soa, sc, env, obj, c, mid, invoke) &&
+        sc.CheckMethodAndSig(soa, obj, c, mid, type, invoke)) {
+      const char* result_check;
+      switch (type) {
+        case Primitive::kPrimNot:
+          result_check = "L";
+          switch (invoke) {
+            case kVirtual:
+              result.L = baseEnv(env)->CallObjectMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.L = baseEnv(env)->CallNonvirtualObjectMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.L = baseEnv(env)->CallStaticObjectMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimBoolean:
+          result_check = "Z";
+          switch (invoke) {
+            case kVirtual:
+              result.Z = baseEnv(env)->CallBooleanMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.Z = baseEnv(env)->CallNonvirtualBooleanMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.Z = baseEnv(env)->CallStaticBooleanMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimByte:
+          result_check = "B";
+          switch (invoke) {
+            case kVirtual:
+              result.B = baseEnv(env)->CallByteMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.B = baseEnv(env)->CallNonvirtualByteMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.B = baseEnv(env)->CallStaticByteMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimChar:
+          result_check = "C";
+          switch (invoke) {
+            case kVirtual:
+              result.C = baseEnv(env)->CallCharMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.C = baseEnv(env)->CallNonvirtualCharMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.C = baseEnv(env)->CallStaticCharMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimShort:
+          result_check = "S";
+          switch (invoke) {
+            case kVirtual:
+              result.S = baseEnv(env)->CallShortMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.S = baseEnv(env)->CallNonvirtualShortMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.S = baseEnv(env)->CallStaticShortMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimInt:
+          result_check = "I";
+          switch (invoke) {
+            case kVirtual:
+              result.I = baseEnv(env)->CallIntMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.I = baseEnv(env)->CallNonvirtualIntMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.I = baseEnv(env)->CallStaticIntMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimLong:
+          result_check = "J";
+          switch (invoke) {
+            case kVirtual:
+              result.J = baseEnv(env)->CallLongMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.J = baseEnv(env)->CallNonvirtualLongMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.J = baseEnv(env)->CallStaticLongMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimFloat:
+          result_check = "F";
+          switch (invoke) {
+            case kVirtual:
+              result.F = baseEnv(env)->CallFloatMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.F = baseEnv(env)->CallNonvirtualFloatMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.F = baseEnv(env)->CallStaticFloatMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimDouble:
+          result_check = "D";
+          switch (invoke) {
+            case kVirtual:
+              result.D = baseEnv(env)->CallDoubleMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.D = baseEnv(env)->CallNonvirtualDoubleMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.D = baseEnv(env)->CallStaticDoubleMethodA(env, c, mid, vargs);
+              break;
+            default:
+              break;
+          }
+          break;
+        case Primitive::kPrimVoid:
+          result_check = "V";
+          result.V = nullptr;
+          switch (invoke) {
+            case kVirtual:
+              baseEnv(env)->CallVoidMethodA(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              baseEnv(env)->CallNonvirtualVoidMethodA(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              baseEnv(env)->CallStaticVoidMethodA(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        default:
+          LOG(FATAL) << "Unexpected return type: " << type;
+          result_check = nullptr;
+      }
+      if (sc.Check(soa, false, result_check, &result)) {
+        return result;
+      }
+    }
+    result.J = 0;
+    return result;
+  }
+
+  static JniValueType CallMethodV(const char* function_name, JNIEnv* env, jobject obj, jclass c,
+                                  jmethodID mid, va_list vargs, Primitive::Type type,
+                                  InvokeType invoke) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType result;
+    if (CheckCallArgs(soa, sc, env, obj, c, mid, invoke) &&
+        sc.CheckMethodAndSig(soa, obj, c, mid, type, invoke)) {
+      const char* result_check;
+      switch (type) {
+        case Primitive::kPrimNot:
+          result_check = "L";
+          switch (invoke) {
+            case kVirtual:
+              result.L = baseEnv(env)->CallObjectMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.L = baseEnv(env)->CallNonvirtualObjectMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.L = baseEnv(env)->CallStaticObjectMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimBoolean:
+          result_check = "Z";
+          switch (invoke) {
+            case kVirtual:
+              result.Z = baseEnv(env)->CallBooleanMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.Z = baseEnv(env)->CallNonvirtualBooleanMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.Z = baseEnv(env)->CallStaticBooleanMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimByte:
+          result_check = "B";
+          switch (invoke) {
+            case kVirtual:
+              result.B = baseEnv(env)->CallByteMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.B = baseEnv(env)->CallNonvirtualByteMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.B = baseEnv(env)->CallStaticByteMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimChar:
+          result_check = "C";
+          switch (invoke) {
+            case kVirtual:
+              result.C = baseEnv(env)->CallCharMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.C = baseEnv(env)->CallNonvirtualCharMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.C = baseEnv(env)->CallStaticCharMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimShort:
+          result_check = "S";
+          switch (invoke) {
+            case kVirtual:
+              result.S = baseEnv(env)->CallShortMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.S = baseEnv(env)->CallNonvirtualShortMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.S = baseEnv(env)->CallStaticShortMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimInt:
+          result_check = "I";
+          switch (invoke) {
+            case kVirtual:
+              result.I = baseEnv(env)->CallIntMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.I = baseEnv(env)->CallNonvirtualIntMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.I = baseEnv(env)->CallStaticIntMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimLong:
+          result_check = "J";
+          switch (invoke) {
+            case kVirtual:
+              result.J = baseEnv(env)->CallLongMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.J = baseEnv(env)->CallNonvirtualLongMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.J = baseEnv(env)->CallStaticLongMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimFloat:
+          result_check = "F";
+          switch (invoke) {
+            case kVirtual:
+              result.F = baseEnv(env)->CallFloatMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.F = baseEnv(env)->CallNonvirtualFloatMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.F = baseEnv(env)->CallStaticFloatMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimDouble:
+          result_check = "D";
+          switch (invoke) {
+            case kVirtual:
+              result.D = baseEnv(env)->CallDoubleMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              result.D = baseEnv(env)->CallNonvirtualDoubleMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              result.D = baseEnv(env)->CallStaticDoubleMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        case Primitive::kPrimVoid:
+          result_check = "V";
+          result.V = nullptr;
+          switch (invoke) {
+            case kVirtual:
+              baseEnv(env)->CallVoidMethodV(env, obj, mid, vargs);
+              break;
+            case kDirect:
+              baseEnv(env)->CallNonvirtualVoidMethodV(env, obj, c, mid, vargs);
+              break;
+            case kStatic:
+              baseEnv(env)->CallStaticVoidMethodV(env, c, mid, vargs);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected invoke: " << invoke;
+          }
+          break;
+        default:
+          LOG(FATAL) << "Unexpected return type: " << type;
+          result_check = nullptr;
+      }
+      if (sc.Check(soa, false, result_check, &result)) {
+        return result;
+      }
+    }
+    result.J = 0;
+    return result;
+  }
+
+  static const void* GetStringCharsInternal(const char* function_name, JNIEnv* env, jstring string,
+                                            jboolean* is_copy, bool utf, bool critical) {
+    ScopedObjectAccess soa(env);
+    int flags = critical ? kFlag_CritGet : kFlag_CritOkay;
+    ScopedCheck sc(flags, function_name);
+    JniValueType args[3] = {{.E = env}, {.s = string}, {.p = is_copy}};
+    if (sc.Check(soa, true, "Esp", args)) {
+      JniValueType result;
+      if (utf) {
+        CHECK(!critical);
+        result.u = baseEnv(env)->GetStringUTFChars(env, string, is_copy);
+      } else {
+        if (critical) {
+          result.p = baseEnv(env)->GetStringCritical(env, string, is_copy);
+        } else {
+          result.p = baseEnv(env)->GetStringChars(env, string, is_copy);
+        }
+      }
+      // TODO: could we be smarter about not copying when local_is_copy?
+      if (result.p != nullptr && soa.ForceCopy()) {
+        if (utf) {
+          size_t length_in_bytes = strlen(result.u) + 1;
+          result.u =
+              reinterpret_cast<const char*>(GuardedCopy::Create(result.u, length_in_bytes, false));
+        } else {
+          size_t length_in_bytes = baseEnv(env)->GetStringLength(env, string) * 2;
+          result.p =
+              reinterpret_cast<const jchar*>(GuardedCopy::Create(result.p, length_in_bytes, false));
+        }
+        if (is_copy != nullptr) {
+          *is_copy = JNI_TRUE;
+        }
+      }
+      if (sc.Check(soa, false, utf ? "u" : "p", &result)) {
+        return utf ? result.u : result.p;
+      }
+    }
+    return nullptr;
+  }
+
+  static void ReleaseStringCharsInternal(const char* function_name, JNIEnv* env, jstring string,
+                                         const void* chars, bool utf, bool critical) {
+    ScopedObjectAccess soa(env);
+    int flags = kFlag_ExcepOkay | kFlag_Release;
+    if (critical) {
+      flags |= kFlag_CritRelease;
+    }
+    ScopedCheck sc(flags, function_name);
+    sc.CheckNonNull(chars);
+    bool force_copy_ok = !soa.ForceCopy() || GuardedCopy::Check(function_name, chars, false);
+    if (force_copy_ok && soa.ForceCopy()) {
+      chars = reinterpret_cast<const jchar*>(GuardedCopy::Destroy(const_cast<void*>(chars)));
+    }
+    if (force_copy_ok) {
+      JniValueType args[3] = {{.E = env}, {.s = string}, {.p = chars}};
+      if (sc.Check(soa, true, utf ? "Esu" : "Esp", args)) {
+        if (utf) {
+          CHECK(!critical);
+          baseEnv(env)->ReleaseStringUTFChars(env, string, reinterpret_cast<const char*>(chars));
+        } else {
+          if (critical) {
+            baseEnv(env)->ReleaseStringCritical(env, string, reinterpret_cast<const jchar*>(chars));
+          } else {
+            baseEnv(env)->ReleaseStringChars(env, string, reinterpret_cast<const jchar*>(chars));
+          }
+        }
+        JniValueType result;
+        sc.Check(soa, false, "V", &result);
+      }
+    }
+  }
+
+  static jarray NewPrimitiveArray(const char* function_name, JNIEnv* env, jsize length,
+                                  Primitive::Type type) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, __FUNCTION__);
+    JniValueType args[2] = {{.E = env}, {.z = length}};
+    if (sc.Check(soa, true, "Ez", args)) {
+      JniValueType result;
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          result.a = baseEnv(env)->NewBooleanArray(env, length);
+          break;
+        case Primitive::kPrimByte:
+          result.a = baseEnv(env)->NewByteArray(env, length);
+          break;
+        case Primitive::kPrimChar:
+          result.a = baseEnv(env)->NewCharArray(env, length);
+          break;
+        case Primitive::kPrimShort:
+          result.a = baseEnv(env)->NewShortArray(env, length);
+          break;
+        case Primitive::kPrimInt:
+          result.a = baseEnv(env)->NewIntArray(env, length);
+          break;
+        case Primitive::kPrimLong:
+          result.a = baseEnv(env)->NewLongArray(env, length);
+          break;
+        case Primitive::kPrimFloat:
+          result.a = baseEnv(env)->NewFloatArray(env, length);
+          break;
+        case Primitive::kPrimDouble:
+          result.a = baseEnv(env)->NewDoubleArray(env, length);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      if (sc.Check(soa, false, "a", &result)) {
+        return result.a;
+      }
+    }
+    return nullptr;
+  }
+
+  static void* GetPrimitiveArrayElements(const char* function_name, Primitive::Type type,
+                                         JNIEnv* env, jarray array, jboolean* is_copy) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[3] = {{.E = env}, {.a = array}, {.p = is_copy}};
+    if (sc.Check(soa, true, "Eap", args) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      JniValueType result;
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          result.p = baseEnv(env)->GetBooleanArrayElements(env, down_cast<jbooleanArray>(array),
+                                                           is_copy);
+          break;
+        case Primitive::kPrimByte:
+          result.p = baseEnv(env)->GetByteArrayElements(env, down_cast<jbyteArray>(array),
+                                                        is_copy);
+          break;
+        case Primitive::kPrimChar:
+          result.p = baseEnv(env)->GetCharArrayElements(env, down_cast<jcharArray>(array),
+                                                        is_copy);
+          break;
+        case Primitive::kPrimShort:
+          result.p = baseEnv(env)->GetShortArrayElements(env, down_cast<jshortArray>(array),
+                                                         is_copy);
+          break;
+        case Primitive::kPrimInt:
+          result.p = baseEnv(env)->GetIntArrayElements(env, down_cast<jintArray>(array), is_copy);
+          break;
+        case Primitive::kPrimLong:
+          result.p = baseEnv(env)->GetLongArrayElements(env, down_cast<jlongArray>(array),
+                                                        is_copy);
+          break;
+        case Primitive::kPrimFloat:
+          result.p = baseEnv(env)->GetFloatArrayElements(env, down_cast<jfloatArray>(array),
+                                                         is_copy);
+          break;
+        case Primitive::kPrimDouble:
+          result.p = baseEnv(env)->GetDoubleArrayElements(env, down_cast<jdoubleArray>(array),
+                                                          is_copy);
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      if (result.p != nullptr && soa.ForceCopy()) {
+        result.p = GuardedCopy::CreateGuardedPACopy(env, array, is_copy);
+        if (is_copy != nullptr) {
+          *is_copy = JNI_TRUE;
+        }
+      }
+      if (sc.Check(soa, false, "p", &result)) {
+        return const_cast<void*>(result.p);
+      }
+    }
+    return nullptr;
+  }
+
+  static void ReleasePrimitiveArrayElements(const char* function_name, Primitive::Type type,
+                                            JNIEnv* env, jarray array, void* elems, jint mode) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_ExcepOkay, function_name);
+    if (sc.CheckNonNull(elems) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      if (soa.ForceCopy()) {
+        elems = GuardedCopy::ReleaseGuardedPACopy(function_name, env, array, elems, mode);
+      }
+      if (!soa.ForceCopy() || elems != nullptr) {
+        JniValueType args[4] = {{.E = env}, {.a = array}, {.p = elems}, {.r = mode}};
+        if (sc.Check(soa, true, "Eapr", args)) {
+          switch (type) {
+            case Primitive::kPrimBoolean:
+              baseEnv(env)->ReleaseBooleanArrayElements(env, down_cast<jbooleanArray>(array),
+                                                        reinterpret_cast<jboolean*>(elems), mode);
+              break;
+            case Primitive::kPrimByte:
+              baseEnv(env)->ReleaseByteArrayElements(env, down_cast<jbyteArray>(array),
+                                                     reinterpret_cast<jbyte*>(elems), mode);
+              break;
+            case Primitive::kPrimChar:
+              baseEnv(env)->ReleaseCharArrayElements(env, down_cast<jcharArray>(array),
+                                                     reinterpret_cast<jchar*>(elems), mode);
+              break;
+            case Primitive::kPrimShort:
+              baseEnv(env)->ReleaseShortArrayElements(env, down_cast<jshortArray>(array),
+                                                      reinterpret_cast<jshort*>(elems), mode);
+              break;
+            case Primitive::kPrimInt:
+              baseEnv(env)->ReleaseIntArrayElements(env, down_cast<jintArray>(array),
+                                                    reinterpret_cast<jint*>(elems), mode);
+              break;
+            case Primitive::kPrimLong:
+              baseEnv(env)->ReleaseLongArrayElements(env, down_cast<jlongArray>(array),
+                                                     reinterpret_cast<jlong*>(elems), mode);
+              break;
+            case Primitive::kPrimFloat:
+              baseEnv(env)->ReleaseFloatArrayElements(env, down_cast<jfloatArray>(array),
+                                                      reinterpret_cast<jfloat*>(elems), mode);
+              break;
+            case Primitive::kPrimDouble:
+              baseEnv(env)->ReleaseDoubleArrayElements(env, down_cast<jdoubleArray>(array),
+                                                       reinterpret_cast<jdouble*>(elems), mode);
+              break;
+            default:
+              LOG(FATAL) << "Unexpected primitive type: " << type;
+          }
+          JniValueType result;
+          result.V = nullptr;
+          sc.Check(soa, false, "V", &result);
+        }
+      }
+    }
+  }
+
+  static void GetPrimitiveArrayRegion(const char* function_name, Primitive::Type type, JNIEnv* env,
+                                      jarray array, jsize start, jsize len, void* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[5] = {{.E = env}, {.a = array}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EaIIp", args) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          baseEnv(env)->GetBooleanArrayRegion(env, down_cast<jbooleanArray>(array), start, len,
+                                              reinterpret_cast<jboolean*>(buf));
+          break;
+        case Primitive::kPrimByte:
+          baseEnv(env)->GetByteArrayRegion(env, down_cast<jbyteArray>(array), start, len,
+                                           reinterpret_cast<jbyte*>(buf));
+          break;
+        case Primitive::kPrimChar:
+          baseEnv(env)->GetCharArrayRegion(env, down_cast<jcharArray>(array), start, len,
+                                           reinterpret_cast<jchar*>(buf));
+          break;
+        case Primitive::kPrimShort:
+          baseEnv(env)->GetShortArrayRegion(env, down_cast<jshortArray>(array), start, len,
+                                            reinterpret_cast<jshort*>(buf));
+          break;
+        case Primitive::kPrimInt:
+          baseEnv(env)->GetIntArrayRegion(env, down_cast<jintArray>(array), start, len,
+                                          reinterpret_cast<jint*>(buf));
+          break;
+        case Primitive::kPrimLong:
+          baseEnv(env)->GetLongArrayRegion(env, down_cast<jlongArray>(array), start, len,
+                                           reinterpret_cast<jlong*>(buf));
+          break;
+        case Primitive::kPrimFloat:
+          baseEnv(env)->GetFloatArrayRegion(env, down_cast<jfloatArray>(array), start, len,
+                                            reinterpret_cast<jfloat*>(buf));
+          break;
+        case Primitive::kPrimDouble:
+          baseEnv(env)->GetDoubleArrayRegion(env, down_cast<jdoubleArray>(array), start, len,
+                                             reinterpret_cast<jdouble*>(buf));
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
+
+  static void SetPrimitiveArrayRegion(const char* function_name, Primitive::Type type, JNIEnv* env,
+                                      jarray array, jsize start, jsize len, const void* buf) {
+    ScopedObjectAccess soa(env);
+    ScopedCheck sc(kFlag_Default, function_name);
+    JniValueType args[5] = {{.E = env}, {.a = array}, {.z = start}, {.z = len}, {.p = buf}};
+    // Note: the start and len arguments are checked as 'I' rather than 'z' as invalid indices
+    // result in ArrayIndexOutOfBoundsExceptions in the base implementation.
+    if (sc.Check(soa, true, "EaIIp", args) && sc.CheckPrimitiveArrayType(soa, array, type)) {
+      switch (type) {
+        case Primitive::kPrimBoolean:
+          baseEnv(env)->SetBooleanArrayRegion(env, down_cast<jbooleanArray>(array), start, len,
+                                              reinterpret_cast<const jboolean*>(buf));
+          break;
+        case Primitive::kPrimByte:
+          baseEnv(env)->SetByteArrayRegion(env, down_cast<jbyteArray>(array), start, len,
+                                           reinterpret_cast<const jbyte*>(buf));
+          break;
+        case Primitive::kPrimChar:
+          baseEnv(env)->SetCharArrayRegion(env, down_cast<jcharArray>(array), start, len,
+                                           reinterpret_cast<const jchar*>(buf));
+          break;
+        case Primitive::kPrimShort:
+          baseEnv(env)->SetShortArrayRegion(env, down_cast<jshortArray>(array), start, len,
+                                              reinterpret_cast<const jshort*>(buf));
+          break;
+        case Primitive::kPrimInt:
+          baseEnv(env)->SetIntArrayRegion(env, down_cast<jintArray>(array), start, len,
+                                          reinterpret_cast<const jint*>(buf));
+          break;
+        case Primitive::kPrimLong:
+          baseEnv(env)->SetLongArrayRegion(env, down_cast<jlongArray>(array), start, len,
+                                              reinterpret_cast<const jlong*>(buf));
+          break;
+        case Primitive::kPrimFloat:
+          baseEnv(env)->SetFloatArrayRegion(env, down_cast<jfloatArray>(array), start, len,
+                                            reinterpret_cast<const jfloat*>(buf));
+          break;
+        case Primitive::kPrimDouble:
+          baseEnv(env)->SetDoubleArrayRegion(env, down_cast<jdoubleArray>(array), start, len,
+                                             reinterpret_cast<const jdouble*>(buf));
+          break;
+        default:
+          LOG(FATAL) << "Unexpected primitive type: " << type;
+      }
+      JniValueType result;
+      result.V = nullptr;
+      sc.Check(soa, false, "V", &result);
+    }
+  }
 };
 
 const JNINativeInterface gCheckNativeInterface = {
@@ -2023,38 +3584,58 @@
 class CheckJII {
  public:
   static jint DestroyJavaVM(JavaVM* vm) {
-    ScopedCheck sc(vm, false, __FUNCTION__);
-    sc.Check(true, "v", vm);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->DestroyJavaVM(vm));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__, false);
+    JniValueType args[1] = {{.v = vm}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "v", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->DestroyJavaVM(vm);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
   static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-    ScopedCheck sc(vm, false, __FUNCTION__);
-    sc.Check(true, "vpp", vm, p_env, thr_args);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.p = thr_args}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpp", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->AttachCurrentThread(vm, p_env, thr_args);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
   static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-    ScopedCheck sc(vm, false, __FUNCTION__);
-    sc.Check(true, "vpp", vm, p_env, thr_args);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.p = thr_args}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpp", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->AttachCurrentThreadAsDaemon(vm, p_env, thr_args);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
   static jint DetachCurrentThread(JavaVM* vm) {
-    ScopedCheck sc(vm, true, __FUNCTION__);
-    sc.Check(true, "v", vm);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->DetachCurrentThread(vm));
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[1] = {{.v = vm}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "v", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->DetachCurrentThread(vm);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
-  static jint GetEnv(JavaVM* vm, void** env, jint version) {
-    ScopedCheck sc(vm, true, __FUNCTION__);
-    sc.Check(true, "vpI", vm);
-    return CHECK_JNI_EXIT("I", BaseVm(vm)->GetEnv(vm, env, version));
+  static jint GetEnv(JavaVM* vm, void** p_env, jint version) {
+    ScopedCheck sc(kFlag_Invocation, __FUNCTION__);
+    JniValueType args[3] = {{.v = vm}, {.p = p_env}, {.I = version}};
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), true, "vpI", args);
+    JniValueType result;
+    result.i = BaseVm(vm)->GetEnv(vm, p_env, version);
+    sc.CheckNonHeap(reinterpret_cast<JavaVMExt*>(vm), false, "i", &result);
+    return result.i;
   }
 
  private:
-  static inline const JNIInvokeInterface* BaseVm(JavaVM* vm) {
-    return reinterpret_cast<JavaVMExt*>(vm)->unchecked_functions;
+  static const JNIInvokeInterface* BaseVm(JavaVM* vm) {
+    return reinterpret_cast<JavaVMExt*>(vm)->GetUncheckedFunctions();
   }
 };
 
diff --git a/runtime/check_jni.h b/runtime/check_jni.h
new file mode 100644
index 0000000..f41abf8
--- /dev/null
+++ b/runtime/check_jni.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2011 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_RUNTIME_CHECK_JNI_H_
+#define ART_RUNTIME_CHECK_JNI_H_
+
+#include <jni.h>
+
+namespace art {
+
+const JNINativeInterface* GetCheckJniNativeInterface();
+const JNIInvokeInterface* GetCheckJniInvokeInterface();
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_CHECK_JNI_H_
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 72572a05..3b4976f 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -16,9 +16,6 @@
 
 #include "class_linker.h"
 
-#include <fcntl.h>
-#include <sys/file.h>
-#include <sys/stat.h>
 #include <deque>
 #include <memory>
 #include <string>
diff --git a/runtime/common_runtime_test.cc b/runtime/common_runtime_test.cc
index 6f3b3a3..ab4a2bb 100644
--- a/runtime/common_runtime_test.cc
+++ b/runtime/common_runtime_test.cc
@@ -354,23 +354,22 @@
   for (const DexFile* dex_file : dex_files) {
     class_linker_->RegisterDexFile(*dex_file);
   }
-  ScopedObjectAccessUnchecked soa(Thread::Current());
-  ScopedLocalRef<jobject> class_loader_local(soa.Env(),
-      soa.Env()->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader));
-  jobject class_loader = soa.Env()->NewGlobalRef(class_loader_local.get());
-  soa.Self()->SetClassLoaderOverride(soa.Decode<mirror::ClassLoader*>(class_loader_local.get()));
+  Thread* self = Thread::Current();
+  JNIEnvExt* env = self->GetJniEnv();
+  ScopedLocalRef<jobject> class_loader_local(env,
+      env->AllocObject(WellKnownClasses::dalvik_system_PathClassLoader));
+  jobject class_loader = env->NewGlobalRef(class_loader_local.get());
+  self->SetClassLoaderOverride(class_loader_local.get());
   Runtime::Current()->SetCompileTimeClassPath(class_loader, dex_files);
   return class_loader;
 }
 
 CheckJniAbortCatcher::CheckJniAbortCatcher() : vm_(Runtime::Current()->GetJavaVM()) {
-  vm_->check_jni_abort_hook = Hook;
-  vm_->check_jni_abort_hook_data = &actual_;
+  vm_->SetCheckJniAbortHook(Hook, &actual_);
 }
 
 CheckJniAbortCatcher::~CheckJniAbortCatcher() {
-  vm_->check_jni_abort_hook = nullptr;
-  vm_->check_jni_abort_hook_data = nullptr;
+  vm_->SetCheckJniAbortHook(nullptr, nullptr);
   EXPECT_TRUE(actual_.empty()) << actual_;
 }
 
diff --git a/runtime/common_runtime_test.h b/runtime/common_runtime_test.h
index 0fefc21..12c1241 100644
--- a/runtime/common_runtime_test.h
+++ b/runtime/common_runtime_test.h
@@ -138,7 +138,7 @@
  private:
   static void Hook(void* data, const std::string& reason);
 
-  JavaVMExt* vm_;
+  JavaVMExt* const vm_;
   std::string actual_;
 
   DISALLOW_COPY_AND_ASSIGN(CheckJniAbortCatcher);
diff --git a/runtime/entrypoints/entrypoint_utils.cc b/runtime/entrypoints/entrypoint_utils.cc
index be3895a..4755b9e 100644
--- a/runtime/entrypoints/entrypoint_utils.cc
+++ b/runtime/entrypoints/entrypoint_utils.cc
@@ -220,7 +220,8 @@
   }
   mirror::ArtMethod* m = self->GetCurrentMethod(NULL);
   if (o == kInvalidIndirectRefObject) {
-    JniAbortF(NULL, "invalid reference returned from %s", PrettyMethod(m).c_str());
+    Runtime::Current()->GetJavaVM()->JniAbortF(NULL, "invalid reference returned from %s",
+                                               PrettyMethod(m).c_str());
   }
   // Make sure that the result is an instance of the type this method was expected to return.
   StackHandleScope<1> hs(self);
@@ -228,8 +229,9 @@
   mirror::Class* return_type = MethodHelper(h_m).GetReturnType();
 
   if (!o->InstanceOf(return_type)) {
-    JniAbortF(NULL, "attempt to return an instance of %s from %s", PrettyTypeOf(o).c_str(),
-              PrettyMethod(h_m.Get()).c_str());
+    Runtime::Current()->GetJavaVM()->JniAbortF(NULL, "attempt to return an instance of %s from %s",
+                                               PrettyTypeOf(o).c_str(),
+                                               PrettyMethod(h_m.Get()).c_str());
   }
 }
 
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index b61105f..6d8190e 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -2514,6 +2514,17 @@
   }
 }
 
+void Heap::AssertThreadLocalBuffersAreRevoked(Thread* thread) {
+  if (kIsDebugBuild) {
+    if (rosalloc_space_ != nullptr) {
+      rosalloc_space_->AssertThreadLocalBuffersAreRevoked(thread);
+    }
+    if (bump_pointer_space_ != nullptr) {
+      bump_pointer_space_->AssertThreadLocalBuffersAreRevoked(thread);
+    }
+  }
+}
+
 void Heap::AssertAllBumpPointerSpaceThreadLocalBuffersAreRevoked() {
   if (kIsDebugBuild) {
     if (bump_pointer_space_ != nullptr) {
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index a82392a..1851662 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -455,6 +455,7 @@
   void RevokeThreadLocalBuffers(Thread* thread);
   void RevokeRosAllocThreadLocalBuffers(Thread* thread);
   void RevokeAllThreadLocalBuffers();
+  void AssertThreadLocalBuffersAreRevoked(Thread* thread);
   void AssertAllBumpPointerSpaceThreadLocalBuffersAreRevoked();
   void RosAllocVerification(TimingLogger* timings, const char* name)
       EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
diff --git a/runtime/gc/space/rosalloc_space.cc b/runtime/gc/space/rosalloc_space.cc
index 92c6f53..3f39c77 100644
--- a/runtime/gc/space/rosalloc_space.cc
+++ b/runtime/gc/space/rosalloc_space.cc
@@ -338,6 +338,12 @@
   rosalloc_->RevokeAllThreadLocalRuns();
 }
 
+void RosAllocSpace::AssertThreadLocalBuffersAreRevoked(Thread* thread) {
+  if (kIsDebugBuild) {
+    rosalloc_->AssertThreadLocalRunsAreRevoked(thread);
+  }
+}
+
 void RosAllocSpace::AssertAllThreadLocalBuffersAreRevoked() {
   if (kIsDebugBuild) {
     rosalloc_->AssertAllThreadLocalRunsAreRevoked();
diff --git a/runtime/gc/space/rosalloc_space.h b/runtime/gc/space/rosalloc_space.h
index f505305..f1ce115 100644
--- a/runtime/gc/space/rosalloc_space.h
+++ b/runtime/gc/space/rosalloc_space.h
@@ -101,6 +101,7 @@
 
   void RevokeThreadLocalBuffers(Thread* thread);
   void RevokeAllThreadLocalBuffers();
+  void AssertThreadLocalBuffersAreRevoked(Thread* thread);
   void AssertAllThreadLocalBuffersAreRevoked();
 
   // Returns the class of a recently freed object.
diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc
index 9b2b82e..1ba2291 100644
--- a/runtime/indirect_reference_table.cc
+++ b/runtime/indirect_reference_table.cc
@@ -56,7 +56,8 @@
 
 void IndirectReferenceTable::AbortIfNoCheckJNI() {
   // If -Xcheck:jni is on, it'll give a more detailed error before aborting.
-  if (!Runtime::Current()->GetJavaVM()->check_jni) {
+  JavaVMExt* vm = Runtime::Current()->GetJavaVM();
+  if (!vm->IsCheckJniEnabled()) {
     // Otherwise, we want to abort rather than hand back a bad reference.
     LOG(FATAL) << "JNI ERROR (app bug): see above.";
   }
diff --git a/runtime/indirect_reference_table.h b/runtime/indirect_reference_table.h
index fb910e2..d25bc42 100644
--- a/runtime/indirect_reference_table.h
+++ b/runtime/indirect_reference_table.h
@@ -25,16 +25,18 @@
 #include "base/logging.h"
 #include "base/mutex.h"
 #include "gc_root.h"
-#include "mem_map.h"
 #include "object_callbacks.h"
 #include "offsets.h"
 #include "read_barrier_option.h"
 
 namespace art {
+
 namespace mirror {
 class Object;
 }  // namespace mirror
 
+class MemMap;
+
 /*
  * Maintain a table of indirect references.  Used for local/global JNI
  * references.
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
new file mode 100644
index 0000000..9eab3fd
--- /dev/null
+++ b/runtime/java_vm_ext.cc
@@ -0,0 +1,829 @@
+/*
+ * Copyright (C) 2011 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 "jni_internal.h"
+
+#include <dlfcn.h>
+
+#include "base/mutex.h"
+#include "base/stl_util.h"
+#include "check_jni.h"
+#include "indirect_reference_table-inl.h"
+#include "mirror/art_method.h"
+#include "mirror/class-inl.h"
+#include "mirror/class_loader.h"
+#include "native_bridge.h"
+#include "java_vm_ext.h"
+#include "parsed_options.h"
+#include "ScopedLocalRef.h"
+#include "scoped_thread_state_change.h"
+#include "thread-inl.h"
+#include "thread_list.h"
+
+namespace art {
+
+static const size_t kPinTableInitial = 16;  // Arbitrary.
+static const size_t kPinTableMax = 1024;  // Arbitrary sanity check.
+
+static size_t gGlobalsInitial = 512;  // Arbitrary.
+static size_t gGlobalsMax = 51200;  // Arbitrary sanity check. (Must fit in 16 bits.)
+
+static const size_t kWeakGlobalsInitial = 16;  // Arbitrary.
+static const size_t kWeakGlobalsMax = 51200;  // Arbitrary sanity check. (Must fit in 16 bits.)
+
+static bool IsBadJniVersion(int version) {
+  // We don't support JNI_VERSION_1_1. These are the only other valid versions.
+  return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
+}
+
+class SharedLibrary {
+ public:
+  SharedLibrary(JNIEnv* env, Thread* self, const std::string& path, void* handle,
+                jobject class_loader)
+      : path_(path),
+        handle_(handle),
+        needs_native_bridge_(false),
+        class_loader_(env->NewGlobalRef(class_loader)),
+        jni_on_load_lock_("JNI_OnLoad lock"),
+        jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_),
+        jni_on_load_thread_id_(self->GetThreadId()),
+        jni_on_load_result_(kPending) {
+  }
+
+  ~SharedLibrary() {
+    Thread* self = Thread::Current();
+    if (self != nullptr) {
+      self->GetJniEnv()->DeleteGlobalRef(class_loader_);
+    }
+  }
+
+  jobject GetClassLoader() const {
+    return class_loader_;
+  }
+
+  const std::string& GetPath() const {
+    return path_;
+  }
+
+  /*
+   * Check the result of an earlier call to JNI_OnLoad on this library.
+   * If the call has not yet finished in another thread, wait for it.
+   */
+  bool CheckOnLoadResult()
+      LOCKS_EXCLUDED(jni_on_load_lock_) {
+    Thread* self = Thread::Current();
+    bool okay;
+    {
+      MutexLock mu(self, jni_on_load_lock_);
+
+      if (jni_on_load_thread_id_ == self->GetThreadId()) {
+        // Check this so we don't end up waiting for ourselves.  We need to return "true" so the
+        // caller can continue.
+        LOG(INFO) << *self << " recursive attempt to load library " << "\"" << path_ << "\"";
+        okay = true;
+      } else {
+        while (jni_on_load_result_ == kPending) {
+          VLOG(jni) << "[" << *self << " waiting for \"" << path_ << "\" " << "JNI_OnLoad...]";
+          jni_on_load_cond_.Wait(self);
+        }
+
+        okay = (jni_on_load_result_ == kOkay);
+        VLOG(jni) << "[Earlier JNI_OnLoad for \"" << path_ << "\" "
+            << (okay ? "succeeded" : "failed") << "]";
+      }
+    }
+    return okay;
+  }
+
+  void SetResult(bool result) LOCKS_EXCLUDED(jni_on_load_lock_) {
+    Thread* self = Thread::Current();
+    MutexLock mu(self, jni_on_load_lock_);
+
+    jni_on_load_result_ = result ? kOkay : kFailed;
+    jni_on_load_thread_id_ = 0;
+
+    // Broadcast a wakeup to anybody sleeping on the condition variable.
+    jni_on_load_cond_.Broadcast(self);
+  }
+
+  void SetNeedsNativeBridge() {
+    needs_native_bridge_ = true;
+  }
+
+  bool NeedsNativeBridge() const {
+    return needs_native_bridge_;
+  }
+
+  void* FindSymbol(const std::string& symbol_name) {
+    return dlsym(handle_, symbol_name.c_str());
+  }
+
+  void* FindSymbolWithNativeBridge(const std::string& symbol_name, const char* shorty) {
+    CHECK(NeedsNativeBridge());
+
+    uint32_t len = 0;
+    return NativeBridgeGetTrampoline(handle_, symbol_name.c_str(), shorty, len);
+  }
+
+ private:
+  enum JNI_OnLoadState {
+    kPending,
+    kFailed,
+    kOkay,
+  };
+
+  // Path to library "/system/lib/libjni.so".
+  const std::string path_;
+
+  // The void* returned by dlopen(3).
+  void* const handle_;
+
+  // True if a native bridge is required.
+  bool needs_native_bridge_;
+
+  // The ClassLoader this library is associated with, a global JNI reference that is
+  // created/deleted with the scope of the library.
+  const jobject class_loader_;
+
+  // Guards remaining items.
+  Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  // Wait for JNI_OnLoad in other thread.
+  ConditionVariable jni_on_load_cond_ GUARDED_BY(jni_on_load_lock_);
+  // Recursive invocation guard.
+  uint32_t jni_on_load_thread_id_ GUARDED_BY(jni_on_load_lock_);
+  // Result of earlier JNI_OnLoad call.
+  JNI_OnLoadState jni_on_load_result_ GUARDED_BY(jni_on_load_lock_);
+};
+
+// This exists mainly to keep implementation details out of the header file.
+class Libraries {
+ public:
+  Libraries() {
+  }
+
+  ~Libraries() {
+    STLDeleteValues(&libraries_);
+  }
+
+  void Dump(std::ostream& os) const {
+    bool first = true;
+    for (const auto& library : libraries_) {
+      if (!first) {
+        os << ' ';
+      }
+      first = false;
+      os << library.first;
+    }
+  }
+
+  size_t size() const {
+    return libraries_.size();
+  }
+
+  SharedLibrary* Get(const std::string& path) {
+    auto it = libraries_.find(path);
+    return (it == libraries_.end()) ? nullptr : it->second;
+  }
+
+  void Put(const std::string& path, SharedLibrary* library) {
+    libraries_.Put(path, library);
+  }
+
+  // See section 11.3 "Linking Native Methods" of the JNI spec.
+  void* FindNativeMethod(mirror::ArtMethod* m, std::string& detail)
+      EXCLUSIVE_LOCKS_REQUIRED(Locks::jni_libraries_lock_)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    std::string jni_short_name(JniShortName(m));
+    std::string jni_long_name(JniLongName(m));
+    const mirror::ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader();
+    ScopedObjectAccessUnchecked soa(Thread::Current());
+    for (const auto& lib : libraries_) {
+      SharedLibrary* library = lib.second;
+      if (soa.Decode<mirror::ClassLoader*>(library->GetClassLoader()) != declaring_class_loader) {
+        // We only search libraries loaded by the appropriate ClassLoader.
+        continue;
+      }
+      // Try the short name then the long name...
+      void* fn;
+      if (library->NeedsNativeBridge()) {
+        const char* shorty = m->GetShorty();
+        fn = library->FindSymbolWithNativeBridge(jni_short_name, shorty);
+        if (fn == nullptr) {
+          fn = library->FindSymbolWithNativeBridge(jni_long_name, shorty);
+        }
+      } else {
+        fn = library->FindSymbol(jni_short_name);
+        if (fn == nullptr) {
+          fn = library->FindSymbol(jni_long_name);
+        }
+      }
+      if (fn == nullptr) {
+        fn = library->FindSymbol(jni_long_name);
+      }
+      if (fn != nullptr) {
+        VLOG(jni) << "[Found native code for " << PrettyMethod(m)
+                  << " in \"" << library->GetPath() << "\"]";
+        return fn;
+      }
+    }
+    detail += "No implementation found for ";
+    detail += PrettyMethod(m);
+    detail += " (tried " + jni_short_name + " and " + jni_long_name + ")";
+    LOG(ERROR) << detail;
+    return nullptr;
+  }
+
+ private:
+  SafeMap<std::string, SharedLibrary*> libraries_;
+};
+
+
+class JII {
+ public:
+  static jint DestroyJavaVM(JavaVM* vm) {
+    if (vm == nullptr) {
+      return JNI_ERR;
+    }
+    JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
+    delete raw_vm->GetRuntime();
+    return JNI_OK;
+  }
+
+  static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
+    return AttachCurrentThreadInternal(vm, p_env, thr_args, false);
+  }
+
+  static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
+    return AttachCurrentThreadInternal(vm, p_env, thr_args, true);
+  }
+
+  static jint DetachCurrentThread(JavaVM* vm) {
+    if (vm == nullptr || Thread::Current() == nullptr) {
+      return JNI_ERR;
+    }
+    JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
+    Runtime* runtime = raw_vm->GetRuntime();
+    runtime->DetachCurrentThread();
+    return JNI_OK;
+  }
+
+  static jint GetEnv(JavaVM* vm, void** env, jint version) {
+    // GetEnv always returns a JNIEnv* for the most current supported JNI version,
+    // and unlike other calls that take a JNI version doesn't care if you supply
+    // JNI_VERSION_1_1, which we don't otherwise support.
+    if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) {
+      LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version;
+      return JNI_EVERSION;
+    }
+    if (vm == nullptr || env == nullptr) {
+      return JNI_ERR;
+    }
+    Thread* thread = Thread::Current();
+    if (thread == nullptr) {
+      *env = nullptr;
+      return JNI_EDETACHED;
+    }
+    *env = thread->GetJniEnv();
+    return JNI_OK;
+  }
+
+ private:
+  static jint AttachCurrentThreadInternal(JavaVM* vm, JNIEnv** p_env, void* raw_args, bool as_daemon) {
+    if (vm == nullptr || p_env == nullptr) {
+      return JNI_ERR;
+    }
+
+    // Return immediately if we're already attached.
+    Thread* self = Thread::Current();
+    if (self != nullptr) {
+      *p_env = self->GetJniEnv();
+      return JNI_OK;
+    }
+
+    Runtime* runtime = reinterpret_cast<JavaVMExt*>(vm)->GetRuntime();
+
+    // No threads allowed in zygote mode.
+    if (runtime->IsZygote()) {
+      LOG(ERROR) << "Attempt to attach a thread in the zygote";
+      return JNI_ERR;
+    }
+
+    JavaVMAttachArgs* args = static_cast<JavaVMAttachArgs*>(raw_args);
+    const char* thread_name = nullptr;
+    jobject thread_group = nullptr;
+    if (args != nullptr) {
+      if (IsBadJniVersion(args->version)) {
+        LOG(ERROR) << "Bad JNI version passed to "
+                   << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": "
+                   << args->version;
+        return JNI_EVERSION;
+      }
+      thread_name = args->name;
+      thread_group = args->group;
+    }
+
+    if (!runtime->AttachCurrentThread(thread_name, as_daemon, thread_group, !runtime->IsCompiler())) {
+      *p_env = nullptr;
+      return JNI_ERR;
+    } else {
+      *p_env = Thread::Current()->GetJniEnv();
+      return JNI_OK;
+    }
+  }
+};
+
+const JNIInvokeInterface gJniInvokeInterface = {
+  nullptr,  // reserved0
+  nullptr,  // reserved1
+  nullptr,  // reserved2
+  JII::DestroyJavaVM,
+  JII::AttachCurrentThread,
+  JII::DetachCurrentThread,
+  JII::GetEnv,
+  JII::AttachCurrentThreadAsDaemon
+};
+
+JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options)
+    : runtime_(runtime),
+      check_jni_abort_hook_(nullptr),
+      check_jni_abort_hook_data_(nullptr),
+      check_jni_(false),  // Initialized properly in the constructor body below.
+      force_copy_(options->force_copy_),
+      tracing_enabled_(!options->jni_trace_.empty() || VLOG_IS_ON(third_party_jni)),
+      trace_(options->jni_trace_),
+      pins_lock_("JNI pin table lock", kPinTableLock),
+      pin_table_("pin table", kPinTableInitial, kPinTableMax),
+      globals_lock_("JNI global reference table lock"),
+      globals_(gGlobalsInitial, gGlobalsMax, kGlobal),
+      libraries_(new Libraries),
+      unchecked_functions_(&gJniInvokeInterface),
+      weak_globals_lock_("JNI weak global reference table lock"),
+      weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal),
+      allow_new_weak_globals_(true),
+      weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) {
+  functions = unchecked_functions_;
+  if (options->check_jni_) {
+    SetCheckJniEnabled(true);
+  }
+}
+
+JavaVMExt::~JavaVMExt() {
+}
+
+void JavaVMExt::JniAbort(const char* jni_function_name, const char* msg) {
+  Thread* self = Thread::Current();
+  ScopedObjectAccess soa(self);
+  mirror::ArtMethod* current_method = self->GetCurrentMethod(nullptr);
+
+  std::ostringstream os;
+  os << "JNI DETECTED ERROR IN APPLICATION: " << msg;
+
+  if (jni_function_name != nullptr) {
+    os << "\n    in call to " << jni_function_name;
+  }
+  // TODO: is this useful given that we're about to dump the calling thread's stack?
+  if (current_method != nullptr) {
+    os << "\n    from " << PrettyMethod(current_method);
+  }
+  os << "\n";
+  self->Dump(os);
+
+  if (check_jni_abort_hook_ != nullptr) {
+    check_jni_abort_hook_(check_jni_abort_hook_data_, os.str());
+  } else {
+    // Ensure that we get a native stack trace for this thread.
+    self->TransitionFromRunnableToSuspended(kNative);
+    LOG(FATAL) << os.str();
+    self->TransitionFromSuspendedToRunnable();  // Unreachable, keep annotalysis happy.
+  }
+}
+
+void JavaVMExt::JniAbortV(const char* jni_function_name, const char* fmt, va_list ap) {
+  std::string msg;
+  StringAppendV(&msg, fmt, ap);
+  JniAbort(jni_function_name, msg.c_str());
+}
+
+void JavaVMExt::JniAbortF(const char* jni_function_name, const char* fmt, ...) {
+  va_list args;
+  va_start(args, fmt);
+  JniAbortV(jni_function_name, fmt, args);
+  va_end(args);
+}
+
+bool JavaVMExt::ShouldTrace(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  // Fast where no tracing is enabled.
+  if (trace_.empty() && !VLOG_IS_ON(third_party_jni)) {
+    return false;
+  }
+  // Perform checks based on class name.
+  StringPiece class_name(method->GetDeclaringClassDescriptor());
+  if (!trace_.empty() && class_name.find(trace_) != std::string::npos) {
+    return true;
+  }
+  if (!VLOG_IS_ON(third_party_jni)) {
+    return false;
+  }
+  // Return true if we're trying to log all third-party JNI activity and 'method' doesn't look
+  // like part of Android.
+  static const char* gBuiltInPrefixes[] = {
+      "Landroid/",
+      "Lcom/android/",
+      "Lcom/google/android/",
+      "Ldalvik/",
+      "Ljava/",
+      "Ljavax/",
+      "Llibcore/",
+      "Lorg/apache/harmony/",
+  };
+  for (size_t i = 0; i < arraysize(gBuiltInPrefixes); ++i) {
+    if (class_name.starts_with(gBuiltInPrefixes[i])) {
+      return false;
+    }
+  }
+  return true;
+}
+
+jobject JavaVMExt::AddGlobalRef(Thread* self, mirror::Object* obj) {
+  // Check for null after decoding the object to handle cleared weak globals.
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  WriterMutexLock mu(self, globals_lock_);
+  IndirectRef ref = globals_.Add(IRT_FIRST_SEGMENT, obj);
+  return reinterpret_cast<jobject>(ref);
+}
+
+jweak JavaVMExt::AddWeakGlobalRef(Thread* self, mirror::Object* obj) {
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  MutexLock mu(self, weak_globals_lock_);
+  while (UNLIKELY(!allow_new_weak_globals_)) {
+    weak_globals_add_condition_.WaitHoldingLocks(self);
+  }
+  IndirectRef ref = weak_globals_.Add(IRT_FIRST_SEGMENT, obj);
+  return reinterpret_cast<jweak>(ref);
+}
+
+void JavaVMExt::DeleteGlobalRef(Thread* self, jobject obj) {
+  if (obj == nullptr) {
+    return;
+  }
+  WriterMutexLock mu(self, globals_lock_);
+  if (!globals_.Remove(IRT_FIRST_SEGMENT, obj)) {
+    LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") "
+                 << "failed to find entry";
+  }
+}
+
+void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) {
+  if (obj == nullptr) {
+    return;
+  }
+  MutexLock mu(self, weak_globals_lock_);
+  if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) {
+    LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "
+                 << "failed to find entry";
+  }
+}
+
+static void ThreadEnableCheckJni(Thread* thread, void* arg) {
+  bool* check_jni = reinterpret_cast<bool*>(arg);
+  thread->GetJniEnv()->SetCheckJniEnabled(*check_jni);
+}
+
+bool JavaVMExt::SetCheckJniEnabled(bool enabled) {
+  bool old_check_jni = check_jni_;
+  check_jni_ = enabled;
+  functions = enabled ? GetCheckJniInvokeInterface() : unchecked_functions_;
+  MutexLock mu(Thread::Current(), *Locks::thread_list_lock_);
+  runtime_->GetThreadList()->ForEach(ThreadEnableCheckJni, &check_jni_);
+  return old_check_jni;
+}
+
+void JavaVMExt::DumpForSigQuit(std::ostream& os) {
+  os << "JNI: CheckJNI is " << (check_jni_ ? "on" : "off");
+  if (force_copy_) {
+    os << " (with forcecopy)";
+  }
+  Thread* self = Thread::Current();
+  {
+    MutexLock mu(self, pins_lock_);
+    os << "; pins=" << pin_table_.Size();
+  }
+  {
+    ReaderMutexLock mu(self, globals_lock_);
+    os << "; globals=" << globals_.Capacity();
+  }
+  {
+    MutexLock mu(self, weak_globals_lock_);
+    if (weak_globals_.Capacity() > 0) {
+      os << " (plus " << weak_globals_.Capacity() << " weak)";
+    }
+  }
+  os << '\n';
+
+  {
+    MutexLock mu(self, *Locks::jni_libraries_lock_);
+    os << "Libraries: " << Dumpable<Libraries>(*libraries_) << " (" << libraries_->size() << ")\n";
+  }
+}
+
+void JavaVMExt::DisallowNewWeakGlobals() {
+  MutexLock mu(Thread::Current(), weak_globals_lock_);
+  allow_new_weak_globals_ = false;
+}
+
+void JavaVMExt::AllowNewWeakGlobals() {
+  Thread* self = Thread::Current();
+  MutexLock mu(self, weak_globals_lock_);
+  allow_new_weak_globals_ = true;
+  weak_globals_add_condition_.Broadcast(self);
+}
+
+mirror::Object* JavaVMExt::DecodeGlobal(Thread* self, IndirectRef ref) {
+  return globals_.SynchronizedGet(self, &globals_lock_, ref);
+}
+
+mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) {
+  MutexLock mu(self, weak_globals_lock_);
+  while (UNLIKELY(!allow_new_weak_globals_)) {
+    weak_globals_add_condition_.WaitHoldingLocks(self);
+  }
+  return weak_globals_.Get(ref);
+}
+
+void JavaVMExt::PinPrimitiveArray(Thread* self, mirror::Array* array) {
+  MutexLock mu(self, pins_lock_);
+  pin_table_.Add(array);
+}
+
+void JavaVMExt::UnpinPrimitiveArray(Thread* self, mirror::Array* array) {
+  MutexLock mu(self, pins_lock_);
+  pin_table_.Remove(array);
+}
+
+void JavaVMExt::DumpReferenceTables(std::ostream& os) {
+  Thread* self = Thread::Current();
+  {
+    ReaderMutexLock mu(self, globals_lock_);
+    globals_.Dump(os);
+  }
+  {
+    MutexLock mu(self, weak_globals_lock_);
+    weak_globals_.Dump(os);
+  }
+  {
+    MutexLock mu(self, pins_lock_);
+    pin_table_.Dump(os);
+  }
+}
+
+bool JavaVMExt::LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject class_loader,
+                                  std::string* error_msg) {
+  error_msg->clear();
+
+  // See if we've already loaded this library.  If we have, and the class loader
+  // matches, return successfully without doing anything.
+  // TODO: for better results we should canonicalize the pathname (or even compare
+  // inodes). This implementation is fine if everybody is using System.loadLibrary.
+  SharedLibrary* library;
+  Thread* self = Thread::Current();
+  {
+    // TODO: move the locking (and more of this logic) into Libraries.
+    MutexLock mu(self, *Locks::jni_libraries_lock_);
+    library = libraries_->Get(path);
+  }
+  if (library != nullptr) {
+    if (env->IsSameObject(library->GetClassLoader(), class_loader) == JNI_FALSE) {
+      // The library will be associated with class_loader. The JNI
+      // spec says we can't load the same library into more than one
+      // class loader.
+      StringAppendF(error_msg, "Shared library \"%s\" already opened by "
+          "ClassLoader %p; can't open in ClassLoader %p",
+          path.c_str(), library->GetClassLoader(), class_loader);
+      LOG(WARNING) << error_msg;
+      return false;
+    }
+    VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
+              << " ClassLoader " << class_loader << "]";
+    if (!library->CheckOnLoadResult()) {
+      StringAppendF(error_msg, "JNI_OnLoad failed on a previous attempt "
+          "to load \"%s\"", path.c_str());
+      return false;
+    }
+    return true;
+  }
+
+  // Open the shared library.  Because we're using a full path, the system
+  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
+  // resolve this library's dependencies though.)
+
+  // Failures here are expected when java.library.path has several entries
+  // and we have to hunt for the lib.
+
+  // Below we dlopen but there is no paired dlclose, this would be necessary if we supported
+  // class unloading. Libraries will only be unloaded when the reference count (incremented by
+  // dlopen) becomes zero from dlclose.
+
+  Locks::mutator_lock_->AssertNotHeld(self);
+  const char* path_str = path.empty() ? nullptr : path.c_str();
+  void* handle = dlopen(path_str, RTLD_LAZY);
+  bool needs_native_bridge = false;
+  if (handle == nullptr) {
+    if (NativeBridgeIsSupported(path_str)) {
+      handle = NativeBridgeLoadLibrary(path_str, RTLD_LAZY);
+      needs_native_bridge = true;
+    }
+  }
+
+  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]";
+
+  if (handle == nullptr) {
+    *error_msg = dlerror();
+    LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *error_msg;
+    return false;
+  }
+
+  if (env->ExceptionCheck() == JNI_TRUE) {
+    LOG(ERROR) << "Unexpected exception:";
+    env->ExceptionDescribe();
+    env->ExceptionClear();
+  }
+  // Create a new entry.
+  // TODO: move the locking (and more of this logic) into Libraries.
+  bool created_library = false;
+  {
+    // Create SharedLibrary ahead of taking the libraries lock to maintain lock ordering.
+    std::unique_ptr<SharedLibrary> new_library(
+        new SharedLibrary(env, self, path, handle, class_loader));
+    MutexLock mu(self, *Locks::jni_libraries_lock_);
+    library = libraries_->Get(path);
+    if (library == nullptr) {  // We won race to get libraries_lock.
+      library = new_library.release();
+      libraries_->Put(path, library);
+      created_library = true;
+    }
+  }
+  if (!created_library) {
+    LOG(INFO) << "WOW: we lost a race to add shared library: "
+        << "\"" << path << "\" ClassLoader=" << class_loader;
+    return library->CheckOnLoadResult();
+  }
+  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader << "]";
+
+  bool was_successful = false;
+  void* sym;
+  if (needs_native_bridge) {
+    library->SetNeedsNativeBridge();
+    sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
+  } else {
+    sym = dlsym(handle, "JNI_OnLoad");
+  }
+  if (sym == nullptr) {
+    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
+    was_successful = true;
+  } else {
+    // Call JNI_OnLoad.  We have to override the current class
+    // loader, which will always be "null" since the stuff at the
+    // top of the stack is around Runtime.loadLibrary().  (See
+    // the comments in the JNI FindClass function.)
+    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));
+    self->SetClassLoaderOverride(class_loader);
+
+    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
+    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
+    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
+    int version = (*jni_on_load)(this, nullptr);
+
+    self->SetClassLoaderOverride(old_class_loader.get());
+
+    if (version == JNI_ERR) {
+      StringAppendF(error_msg, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
+    } else if (IsBadJniVersion(version)) {
+      StringAppendF(error_msg, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
+                    path.c_str(), version);
+      // It's unwise to call dlclose() here, but we can mark it
+      // as bad and ensure that future load attempts will fail.
+      // We don't know how far JNI_OnLoad got, so there could
+      // be some partially-initialized stuff accessible through
+      // newly-registered native method calls.  We could try to
+      // unregister them, but that doesn't seem worthwhile.
+    } else {
+      was_successful = true;
+    }
+    VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
+              << " from JNI_OnLoad in \"" << path << "\"]";
+  }
+
+  library->SetResult(was_successful);
+  return was_successful;
+}
+
+void* JavaVMExt::FindCodeForNativeMethod(mirror::ArtMethod* m) {
+  CHECK(m->IsNative());
+  mirror::Class* c = m->GetDeclaringClass();
+  // If this is a static method, it could be called before the class has been initialized.
+  CHECK(c->IsInitializing()) << c->GetStatus() << " " << PrettyMethod(m);
+  std::string detail;
+  void* native_method;
+  Thread* self = Thread::Current();
+  {
+    MutexLock mu(self, *Locks::jni_libraries_lock_);
+    native_method = libraries_->FindNativeMethod(m, detail);
+  }
+  // Throwing can cause libraries_lock to be reacquired.
+  if (native_method == nullptr) {
+    ThrowLocation throw_location = self->GetCurrentLocationForThrow();
+    self->ThrowNewException(throw_location, "Ljava/lang/UnsatisfiedLinkError;", detail.c_str());
+  }
+  return native_method;
+}
+
+void JavaVMExt::SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) {
+  MutexLock mu(Thread::Current(), weak_globals_lock_);
+  for (mirror::Object** entry : weak_globals_) {
+    // Since this is called by the GC, we don't need a read barrier.
+    mirror::Object* obj = *entry;
+    mirror::Object* new_obj = callback(obj, arg);
+    if (new_obj == nullptr) {
+      new_obj = kClearedJniWeakGlobal;
+    }
+    *entry = new_obj;
+  }
+}
+
+void JavaVMExt::VisitRoots(RootCallback* callback, void* arg) {
+  Thread* self = Thread::Current();
+  {
+    ReaderMutexLock mu(self, globals_lock_);
+    globals_.VisitRoots(callback, arg, 0, kRootJNIGlobal);
+  }
+  {
+    MutexLock mu(self, pins_lock_);
+    pin_table_.VisitRoots(callback, arg, 0, kRootVMInternal);
+  }
+  // The weak_globals table is visited by the GC itself (because it mutates the table).
+}
+
+// JNI Invocation interface.
+
+extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
+  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
+  if (IsBadJniVersion(args->version)) {
+    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
+    return JNI_EVERSION;
+  }
+  RuntimeOptions options;
+  for (int i = 0; i < args->nOptions; ++i) {
+    JavaVMOption* option = &args->options[i];
+    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
+  }
+  bool ignore_unrecognized = args->ignoreUnrecognized;
+  if (!Runtime::Create(options, ignore_unrecognized)) {
+    return JNI_ERR;
+  }
+  Runtime* runtime = Runtime::Current();
+  bool started = runtime->Start();
+  if (!started) {
+    delete Thread::Current()->GetJniEnv();
+    delete runtime->GetJavaVM();
+    LOG(WARNING) << "CreateJavaVM failed";
+    return JNI_ERR;
+  }
+  *p_env = Thread::Current()->GetJniEnv();
+  *p_vm = runtime->GetJavaVM();
+  return JNI_OK;
+}
+
+extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize, jsize* vm_count) {
+  Runtime* runtime = Runtime::Current();
+  if (runtime == nullptr) {
+    *vm_count = 0;
+  } else {
+    *vm_count = 1;
+    vms[0] = runtime->GetJavaVM();
+  }
+  return JNI_OK;
+}
+
+// Historically unsupported.
+extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* /*vm_args*/) {
+  return JNI_ERR;
+}
+
+}  // namespace art
diff --git a/runtime/java_vm_ext.h b/runtime/java_vm_ext.h
new file mode 100644
index 0000000..da0b8e3
--- /dev/null
+++ b/runtime/java_vm_ext.h
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2011 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_RUNTIME_JAVA_VM_EXT_H_
+#define ART_RUNTIME_JAVA_VM_EXT_H_
+
+#include "jni.h"
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "indirect_reference_table.h"
+#include "reference_table.h"
+
+namespace art {
+
+namespace mirror {
+  class ArtMethod;
+  class Array;
+}  // namespace mirror
+
+class Libraries;
+class ParsedOptions;
+class Runtime;
+
+class JavaVMExt : public JavaVM {
+ public:
+  JavaVMExt(Runtime* runtime, ParsedOptions* options);
+  ~JavaVMExt();
+
+  bool ForceCopy() const {
+    return force_copy_;
+  }
+
+  bool IsCheckJniEnabled() const {
+    return check_jni_;
+  }
+
+  bool IsTracingEnabled() const {
+    return tracing_enabled_;
+  }
+
+  Runtime* GetRuntime() const {
+    return runtime_;
+  }
+
+  void SetCheckJniAbortHook(void (*hook)(void*, const std::string&), void* data) {
+    check_jni_abort_hook_ = hook;
+    check_jni_abort_hook_data_ = data;
+  }
+
+  // Aborts execution unless there is an abort handler installed in which case it will return. Its
+  // therefore important that callers return after aborting as otherwise code following the abort
+  // will be executed in the abort handler case.
+  void JniAbort(const char* jni_function_name, const char* msg);
+
+  void JniAbortV(const char* jni_function_name, const char* fmt, va_list ap);
+
+  void JniAbortF(const char* jni_function_name, const char* fmt, ...)
+      __attribute__((__format__(__printf__, 3, 4)));
+
+  // If both "-Xcheck:jni" and "-Xjnitrace:" are enabled, we print trace messages
+  // when a native method that matches the -Xjnitrace argument calls a JNI function
+  // such as NewByteArray.
+  // If -verbose:third-party-jni is on, we want to log any JNI function calls
+  // made by a third-party native method.
+  bool ShouldTrace(mirror::ArtMethod* method) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  /**
+   * Loads the given shared library. 'path' is an absolute pathname.
+   *
+   * Returns 'true' on success. On failure, sets 'detail' to a
+   * human-readable description of the error.
+   */
+  bool LoadNativeLibrary(JNIEnv* env, const std::string& path, jobject javaLoader,
+                         std::string* error_msg);
+
+  /**
+   * Returns a pointer to the code for the native method 'm', found
+   * using dlsym(3) on every native library that's been loaded so far.
+   */
+  void* FindCodeForNativeMethod(mirror::ArtMethod* m)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void DumpForSigQuit(std::ostream& os)
+      LOCKS_EXCLUDED(Locks::jni_libraries_lock_, globals_lock_, weak_globals_lock_, pins_lock_);
+
+  void DumpReferenceTables(std::ostream& os)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  bool SetCheckJniEnabled(bool enabled);
+
+  void VisitRoots(RootCallback* callback, void* arg) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void DisallowNewWeakGlobals() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void AllowNewWeakGlobals() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  jobject AddGlobalRef(Thread* self, mirror::Object* obj)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  jweak AddWeakGlobalRef(Thread* self, mirror::Object* obj)
+    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void DeleteGlobalRef(Thread* self, jobject obj);
+
+  void DeleteWeakGlobalRef(Thread* self, jweak obj);
+
+  void SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  mirror::Object* DecodeGlobal(Thread* self, IndirectRef ref)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  mirror::Object* DecodeWeakGlobal(Thread* self, IndirectRef ref)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void PinPrimitiveArray(Thread* self, mirror::Array* array)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
+      LOCKS_EXCLUDED(pins_lock_);
+
+  void UnpinPrimitiveArray(Thread* self, mirror::Array* array)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_)
+      LOCKS_EXCLUDED(pins_lock_);
+
+  const JNIInvokeInterface* GetUncheckedFunctions() const {
+    return unchecked_functions_;
+  }
+
+ private:
+  Runtime* const runtime_;
+
+  // Used for testing. By default, we'll LOG(FATAL) the reason.
+  void (*check_jni_abort_hook_)(void* data, const std::string& reason);
+  void* check_jni_abort_hook_data_;
+
+  // Extra checking.
+  bool check_jni_;
+  bool force_copy_;
+  const bool tracing_enabled_;
+
+  // Extra diagnostics.
+  const std::string trace_;
+
+  // Used to hold references to pinned primitive arrays.
+  Mutex pins_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  ReferenceTable pin_table_ GUARDED_BY(pins_lock_);
+
+  // JNI global references.
+  ReaderWriterMutex globals_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject.
+  IndirectReferenceTable globals_;
+
+  std::unique_ptr<Libraries> libraries_ GUARDED_BY(Locks::jni_libraries_lock_);
+
+  // Used by -Xcheck:jni.
+  const JNIInvokeInterface* const unchecked_functions_;
+
+  // JNI weak global references.
+  Mutex weak_globals_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  // Since weak_globals_ contain weak roots, be careful not to
+  // directly access the object references in it. Use Get() with the
+  // read barrier enabled.
+  IndirectReferenceTable weak_globals_ GUARDED_BY(weak_globals_lock_);
+  bool allow_new_weak_globals_ GUARDED_BY(weak_globals_lock_);
+  ConditionVariable weak_globals_add_condition_ GUARDED_BY(weak_globals_lock_);
+
+  DISALLOW_COPY_AND_ASSIGN(JavaVMExt);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_JAVA_VM_EXT_H_
diff --git a/runtime/jni_internal-inl.h b/runtime/jni_env_ext-inl.h
similarity index 89%
rename from runtime/jni_internal-inl.h
rename to runtime/jni_env_ext-inl.h
index 6cf9a61..dc6a3e8 100644
--- a/runtime/jni_internal-inl.h
+++ b/runtime/jni_env_ext-inl.h
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef ART_RUNTIME_JNI_INTERNAL_INL_H_
-#define ART_RUNTIME_JNI_INTERNAL_INL_H_
+#ifndef ART_RUNTIME_JNI_ENV_EXT_INL_H_
+#define ART_RUNTIME_JNI_ENV_EXT_INL_H_
 
-#include "jni_internal.h"
+#include "jni_env_ext.h"
 
 #include "utils.h"
 
@@ -44,4 +44,4 @@
 
 }  // namespace art
 
-#endif  // ART_RUNTIME_JNI_INTERNAL_INL_H_
+#endif  // ART_RUNTIME_JNI_ENV_EXT_INL_H_
diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc
new file mode 100644
index 0000000..180e3d7
--- /dev/null
+++ b/runtime/jni_env_ext.cc
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2011 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 "jni_env_ext.h"
+
+#include "check_jni.h"
+#include "indirect_reference_table.h"
+#include "java_vm_ext.h"
+#include "jni_internal.h"
+
+namespace art {
+
+static constexpr size_t kMonitorsInitial = 32;  // Arbitrary.
+static constexpr size_t kMonitorsMax = 4096;  // Arbitrary sanity check.
+
+static constexpr size_t kLocalsInitial = 64;  // Arbitrary.
+
+JNIEnvExt::JNIEnvExt(Thread* self, JavaVMExt* vm)
+    : self(self),
+      vm(vm),
+      local_ref_cookie(IRT_FIRST_SEGMENT),
+      locals(kLocalsInitial, kLocalsMax, kLocal),
+      check_jni(false),
+      critical(0),
+      monitors("monitors", kMonitorsInitial, kMonitorsMax) {
+  functions = unchecked_functions = GetJniNativeInterface();
+  if (vm->IsCheckJniEnabled()) {
+    SetCheckJniEnabled(true);
+  }
+}
+
+JNIEnvExt::~JNIEnvExt() {
+}
+
+jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  if (obj == nullptr) {
+    return nullptr;
+  }
+  return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj));
+}
+
+void JNIEnvExt::DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  if (obj != nullptr) {
+    locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj));
+  }
+}
+
+void JNIEnvExt::SetCheckJniEnabled(bool enabled) {
+  check_jni = enabled;
+  functions = enabled ? GetCheckJniNativeInterface() : GetJniNativeInterface();
+}
+
+void JNIEnvExt::DumpReferenceTables(std::ostream& os) {
+  locals.Dump(os);
+  monitors.Dump(os);
+}
+
+void JNIEnvExt::PushFrame(int capacity) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  UNUSED(capacity);  // cpplint gets confused with (int) and thinks its a cast.
+  // TODO: take 'capacity' into account.
+  stacked_local_ref_cookies.push_back(local_ref_cookie);
+  local_ref_cookie = locals.GetSegmentState();
+}
+
+void JNIEnvExt::PopFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  locals.SetSegmentState(local_ref_cookie);
+  local_ref_cookie = stacked_local_ref_cookies.back();
+  stacked_local_ref_cookies.pop_back();
+}
+
+Offset JNIEnvExt::SegmentStateOffset() {
+  return Offset(OFFSETOF_MEMBER(JNIEnvExt, locals) +
+                IndirectReferenceTable::SegmentStateOffset().Int32Value());
+}
+
+}  // namespace art
diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h
new file mode 100644
index 0000000..af87cb4
--- /dev/null
+++ b/runtime/jni_env_ext.h
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 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_RUNTIME_JNI_ENV_EXT_H_
+#define ART_RUNTIME_JNI_ENV_EXT_H_
+
+#include <jni.h>
+
+#include "base/macros.h"
+#include "base/mutex.h"
+#include "indirect_reference_table.h"
+#include "object_callbacks.h"
+#include "reference_table.h"
+
+namespace art {
+
+class JavaVMExt;
+
+// Maximum number of local references in the indirect reference table. The value is arbitrary but
+// low enough that it forces sanity checks.
+static constexpr size_t kLocalsMax = 512;
+
+struct JNIEnvExt : public JNIEnv {
+  JNIEnvExt(Thread* self, JavaVMExt* vm);
+  ~JNIEnvExt();
+
+  void DumpReferenceTables(std::ostream& os)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  void SetCheckJniEnabled(bool enabled);
+
+  void PushFrame(int capacity);
+  void PopFrame();
+
+  template<typename T>
+  T AddLocalReference(mirror::Object* obj)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  static Offset SegmentStateOffset();
+
+  static Offset LocalRefCookieOffset() {
+    return Offset(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie));
+  }
+
+  static Offset SelfOffset() {
+    return Offset(OFFSETOF_MEMBER(JNIEnvExt, self));
+  }
+
+  jobject NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+
+  Thread* const self;
+  JavaVMExt* const vm;
+
+  // Cookie used when using the local indirect reference table.
+  uint32_t local_ref_cookie;
+
+  // JNI local references.
+  IndirectReferenceTable locals GUARDED_BY(Locks::mutator_lock_);
+
+  // Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls.
+  // TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return)
+  // to a native method.
+  std::vector<uint32_t> stacked_local_ref_cookies;
+
+  // Frequently-accessed fields cached from JavaVM.
+  bool check_jni;
+
+  // How many nested "critical" JNI calls are we in?
+  int critical;
+
+  // Entered JNI monitors, for bulk exit on thread detach.
+  ReferenceTable monitors;
+
+  // Used by -Xcheck:jni.
+  const JNINativeInterface* unchecked_functions;
+};
+
+// Used to save and restore the JNIEnvExt state when not going through code created by the JNI
+// compiler.
+class ScopedJniEnvLocalRefState {
+ public:
+  explicit ScopedJniEnvLocalRefState(JNIEnvExt* env) : env_(env) {
+    saved_local_ref_cookie_ = env->local_ref_cookie;
+    env->local_ref_cookie = env->locals.GetSegmentState();
+  }
+
+  ~ScopedJniEnvLocalRefState() {
+    env_->locals.SetSegmentState(env_->local_ref_cookie);
+    env_->local_ref_cookie = saved_local_ref_cookie_;
+  }
+
+ private:
+  JNIEnvExt* const env_;
+  uint32_t saved_local_ref_cookie_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedJniEnvLocalRefState);
+};
+
+}  // namespace art
+
+#endif  // ART_RUNTIME_JNI_ENV_EXT_H_
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 43b9912..d5e92a4 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -33,7 +33,8 @@
 #include "gc/accounting/card_table-inl.h"
 #include "indirect_reference_table-inl.h"
 #include "interpreter/interpreter.h"
-#include "jni.h"
+#include "jni_env_ext.h"
+#include "java_vm_ext.h"
 #include "mirror/art_field-inl.h"
 #include "mirror/art_method-inl.h"
 #include "mirror/class-inl.h"
@@ -55,31 +56,6 @@
 
 namespace art {
 
-static const size_t kMonitorsInitial = 32;  // Arbitrary.
-static const size_t kMonitorsMax = 4096;  // Arbitrary sanity check.
-
-static const size_t kLocalsInitial = 64;  // Arbitrary.
-static const size_t kLocalsMax = 512;  // Arbitrary sanity check.
-
-static const size_t kPinTableInitial = 16;  // Arbitrary.
-static const size_t kPinTableMax = 1024;  // Arbitrary sanity check.
-
-static size_t gGlobalsInitial = 512;  // Arbitrary.
-static size_t gGlobalsMax = 51200;  // Arbitrary sanity check. (Must fit in 16 bits.)
-
-static const size_t kWeakGlobalsInitial = 16;  // Arbitrary.
-static const size_t kWeakGlobalsMax = 51200;  // Arbitrary sanity check. (Must fit in 16 bits.)
-
-static jweak AddWeakGlobalReference(ScopedObjectAccess& soa, mirror::Object* obj)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  return soa.Vm()->AddWeakGlobalReference(soa.Self(), obj);
-}
-
-static bool IsBadJniVersion(int version) {
-  // We don't support JNI_VERSION_1_1. These are the only other valid versions.
-  return version != JNI_VERSION_1_2 && version != JNI_VERSION_1_4 && version != JNI_VERSION_1_6;
-}
-
 // Section 12.3.2 of the JNI spec describes JNI class descriptors. They're
 // separated with slashes but aren't wrapped with "L;" like regular descriptors
 // (i.e. "a/b/C" rather than "La/b/C;"). Arrays of reference types are an
@@ -169,7 +145,7 @@
   mirror::ArtMethod* method = soa.Self()->GetCurrentMethod(nullptr);
   // If we are running Runtime.nativeLoad, use the overriding ClassLoader it set.
   if (method == soa.DecodeMethod(WellKnownClasses::java_lang_Runtime_nativeLoad)) {
-    return soa.Self()->GetClassLoaderOverride();
+    return soa.Decode<mirror::ClassLoader*>(soa.Self()->GetClassLoaderOverride());
   }
   // If we have a method, use its ClassLoader for context.
   if (method != nullptr) {
@@ -182,7 +158,7 @@
     return class_loader;
   }
   // See if the override ClassLoader is set for gtests.
-  class_loader = soa.Self()->GetClassLoaderOverride();
+  class_loader = soa.Decode<mirror::ClassLoader*>(soa.Self()->GetClassLoaderOverride());
   if (class_loader != nullptr) {
     // If so, CommonCompilerTest should have set UseCompileTimeClassPath.
     CHECK(Runtime::Current()->UseCompileTimeClassPath());
@@ -240,20 +216,6 @@
   return soa.EncodeField(field);
 }
 
-static void PinPrimitiveArray(const ScopedObjectAccess& soa, mirror::Array* array)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  JavaVMExt* vm = soa.Vm();
-  MutexLock mu(soa.Self(), vm->pins_lock);
-  vm->pin_table.Add(array);
-}
-
-static void UnpinPrimitiveArray(const ScopedObjectAccess& soa, mirror::Array* array)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  JavaVMExt* vm = soa.Vm();
-  MutexLock mu(soa.Self(), vm->pins_lock);
-  vm->pin_table.Remove(array);
-}
-
 static void ThrowAIOOBE(ScopedObjectAccess& soa, mirror::Array* array, jsize start,
                         jsize length, const char* identifier)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
@@ -316,255 +278,10 @@
   return JNI_OK;
 }
 
-static jint JII_AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* raw_args, bool as_daemon) {
-  if (vm == nullptr || p_env == nullptr) {
-    return JNI_ERR;
-  }
-
-  // Return immediately if we're already attached.
-  Thread* self = Thread::Current();
-  if (self != nullptr) {
-    *p_env = self->GetJniEnv();
-    return JNI_OK;
-  }
-
-  Runtime* runtime = reinterpret_cast<JavaVMExt*>(vm)->runtime;
-
-  // No threads allowed in zygote mode.
-  if (runtime->IsZygote()) {
-    LOG(ERROR) << "Attempt to attach a thread in the zygote";
-    return JNI_ERR;
-  }
-
-  JavaVMAttachArgs* args = static_cast<JavaVMAttachArgs*>(raw_args);
-  const char* thread_name = nullptr;
-  jobject thread_group = nullptr;
-  if (args != nullptr) {
-    if (IsBadJniVersion(args->version)) {
-      LOG(ERROR) << "Bad JNI version passed to "
-                 << (as_daemon ? "AttachCurrentThreadAsDaemon" : "AttachCurrentThread") << ": "
-                 << args->version;
-      return JNI_EVERSION;
-    }
-    thread_name = args->name;
-    thread_group = args->group;
-  }
-
-  if (!runtime->AttachCurrentThread(thread_name, as_daemon, thread_group, !runtime->IsCompiler())) {
-    *p_env = nullptr;
-    return JNI_ERR;
-  } else {
-    *p_env = Thread::Current()->GetJniEnv();
-    return JNI_OK;
-  }
+static JavaVMExt* JavaVmExtFromEnv(JNIEnv* env) {
+  return reinterpret_cast<JNIEnvExt*>(env)->vm;
 }
 
-class SharedLibrary {
- public:
-  SharedLibrary(const std::string& path, void* handle, mirror::Object* class_loader)
-      : path_(path),
-        handle_(handle),
-        needs_native_bridge_(false),
-        class_loader_(GcRoot<mirror::Object>(class_loader)),
-        jni_on_load_lock_("JNI_OnLoad lock"),
-        jni_on_load_cond_("JNI_OnLoad condition variable", jni_on_load_lock_),
-        jni_on_load_thread_id_(Thread::Current()->GetThreadId()),
-        jni_on_load_result_(kPending) {
-  }
-
-  mirror::Object* GetClassLoader() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    return class_loader_.Read();
-  }
-
-  std::string GetPath() {
-    return path_;
-  }
-
-  /*
-   * Check the result of an earlier call to JNI_OnLoad on this library.
-   * If the call has not yet finished in another thread, wait for it.
-   */
-  bool CheckOnLoadResult()
-      LOCKS_EXCLUDED(jni_on_load_lock_)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    Thread* self = Thread::Current();
-    self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad);
-    bool okay;
-    {
-      MutexLock mu(self, jni_on_load_lock_);
-
-      if (jni_on_load_thread_id_ == self->GetThreadId()) {
-        // Check this so we don't end up waiting for ourselves.  We need to return "true" so the
-        // caller can continue.
-        LOG(INFO) << *self << " recursive attempt to load library " << "\"" << path_ << "\"";
-        okay = true;
-      } else {
-        while (jni_on_load_result_ == kPending) {
-          VLOG(jni) << "[" << *self << " waiting for \"" << path_ << "\" " << "JNI_OnLoad...]";
-          jni_on_load_cond_.Wait(self);
-        }
-
-        okay = (jni_on_load_result_ == kOkay);
-        VLOG(jni) << "[Earlier JNI_OnLoad for \"" << path_ << "\" "
-            << (okay ? "succeeded" : "failed") << "]";
-      }
-    }
-    self->TransitionFromSuspendedToRunnable();
-    return okay;
-  }
-
-  void SetResult(bool result) LOCKS_EXCLUDED(jni_on_load_lock_) {
-    Thread* self = Thread::Current();
-    MutexLock mu(self, jni_on_load_lock_);
-
-    jni_on_load_result_ = result ? kOkay : kFailed;
-    jni_on_load_thread_id_ = 0;
-
-    // Broadcast a wakeup to anybody sleeping on the condition variable.
-    jni_on_load_cond_.Broadcast(self);
-  }
-
-  void SetNeedsNativeBridge() {
-    needs_native_bridge_ = true;
-  }
-
-  bool NeedsNativeBridge() const {
-    return needs_native_bridge_;
-  }
-
-  void* FindSymbol(const std::string& symbol_name) {
-    return dlsym(handle_, symbol_name.c_str());
-  }
-
-  void* FindSymbolWithNativeBridge(const std::string& symbol_name, mirror::ArtMethod* m)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    CHECK(NeedsNativeBridge());
-
-    uint32_t len = 0;
-    const char* shorty = nullptr;
-    if (m != nullptr) {
-      shorty = m->GetShorty(&len);
-    }
-    return NativeBridge::GetTrampoline(handle_, symbol_name.c_str(), shorty, len);
-  }
-
-  void VisitRoots(RootCallback* visitor, void* arg) {
-    if (!class_loader_.IsNull()) {
-      class_loader_.VisitRoot(visitor, arg, 0, kRootVMInternal);
-    }
-  }
-
- private:
-  enum JNI_OnLoadState {
-    kPending,
-    kFailed,
-    kOkay,
-  };
-
-  // Path to library "/system/lib/libjni.so".
-  std::string path_;
-
-  // The void* returned by dlopen(3).
-  void* handle_;
-
-  // True if a native bridge is required.
-  bool needs_native_bridge_;
-
-  // The ClassLoader this library is associated with.
-  GcRoot<mirror::Object> class_loader_;
-
-  // Guards remaining items.
-  Mutex jni_on_load_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  // Wait for JNI_OnLoad in other thread.
-  ConditionVariable jni_on_load_cond_ GUARDED_BY(jni_on_load_lock_);
-  // Recursive invocation guard.
-  uint32_t jni_on_load_thread_id_ GUARDED_BY(jni_on_load_lock_);
-  // Result of earlier JNI_OnLoad call.
-  JNI_OnLoadState jni_on_load_result_ GUARDED_BY(jni_on_load_lock_);
-};
-
-// This exists mainly to keep implementation details out of the header file.
-class Libraries {
- public:
-  Libraries() {
-  }
-
-  ~Libraries() {
-    STLDeleteValues(&libraries_);
-  }
-
-  void Dump(std::ostream& os) const {
-    bool first = true;
-    for (const auto& library : libraries_) {
-      if (!first) {
-        os << ' ';
-      }
-      first = false;
-      os << library.first;
-    }
-  }
-
-  size_t size() const {
-    return libraries_.size();
-  }
-
-  SharedLibrary* Get(const std::string& path) {
-    auto it = libraries_.find(path);
-    return (it == libraries_.end()) ? nullptr : it->second;
-  }
-
-  void Put(const std::string& path, SharedLibrary* library) {
-    libraries_.Put(path, library);
-  }
-
-  // See section 11.3 "Linking Native Methods" of the JNI spec.
-  void* FindNativeMethod(mirror::ArtMethod* m, std::string& detail)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    std::string jni_short_name(JniShortName(m));
-    std::string jni_long_name(JniLongName(m));
-    const mirror::ClassLoader* declaring_class_loader = m->GetDeclaringClass()->GetClassLoader();
-    for (const auto& lib : libraries_) {
-      SharedLibrary* library = lib.second;
-      if (library->GetClassLoader() != declaring_class_loader) {
-        // We only search libraries loaded by the appropriate ClassLoader.
-        continue;
-      }
-      // Try the short name then the long name...
-      void* fn = nullptr;
-      if (UNLIKELY(library->NeedsNativeBridge())) {
-        fn = library->FindSymbolWithNativeBridge(jni_short_name, m);
-        if (fn == nullptr) {
-          fn = library->FindSymbolWithNativeBridge(jni_long_name, m);
-        }
-      } else {
-        fn = library->FindSymbol(jni_short_name);
-        if (fn == nullptr) {
-          fn = library->FindSymbol(jni_long_name);
-        }
-      }
-      if (fn != nullptr) {
-        VLOG(jni) << "[Found native code for " << PrettyMethod(m)
-                  << " in \"" << library->GetPath() << "\"]";
-        return fn;
-      }
-    }
-    detail += "No implementation found for ";
-    detail += PrettyMethod(m);
-    detail += " (tried " + jni_short_name + " and " + jni_long_name + ")";
-    LOG(ERROR) << detail;
-    return nullptr;
-  }
-
-  void VisitRoots(RootCallback* callback, void* arg) {
-    for (auto& lib_pair : libraries_) {
-      lib_pair.second->VisitRoots(callback, arg);
-    }
-  }
-
- private:
-  SafeMap<std::string, SharedLibrary*> libraries_;
-};
-
 #define CHECK_NON_NULL_ARGUMENT(value) \
     CHECK_NON_NULL_ARGUMENT_FN_NAME(__FUNCTION__, value, nullptr)
 
@@ -579,13 +296,13 @@
 
 #define CHECK_NON_NULL_ARGUMENT_FN_NAME(name, value, return_val) \
   if (UNLIKELY(value == nullptr)) { \
-    JniAbortF(name, #value " == null"); \
+    JavaVmExtFromEnv(env)->JniAbortF(name, #value " == null"); \
     return return_val; \
   }
 
 #define CHECK_NON_NULL_MEMCPY_ARGUMENT(length, value) \
   if (UNLIKELY(length != 0 && value == nullptr)) { \
-    JniAbortF(__FUNCTION__, #value " == null"); \
+    JavaVmExtFromEnv(env)->JniAbortF(__FUNCTION__, #value " == null"); \
     return; \
   }
 
@@ -786,10 +503,10 @@
   static jint PushLocalFrame(JNIEnv* env, jint capacity) {
     // TODO: SOA may not be necessary but I do it to please lock annotations.
     ScopedObjectAccess soa(env);
-    if (EnsureLocalCapacity(soa, capacity, "PushLocalFrame") != JNI_OK) {
+    if (EnsureLocalCapacityInternal(soa, capacity, "PushLocalFrame") != JNI_OK) {
       return JNI_ERR;
     }
-    static_cast<JNIEnvExt*>(env)->PushFrame(capacity);
+    down_cast<JNIEnvExt*>(env)->PushFrame(capacity);
     return JNI_OK;
   }
 
@@ -803,48 +520,31 @@
   static jint EnsureLocalCapacity(JNIEnv* env, jint desired_capacity) {
     // TODO: SOA may not be necessary but I do it to please lock annotations.
     ScopedObjectAccess soa(env);
-    return EnsureLocalCapacity(soa, desired_capacity, "EnsureLocalCapacity");
+    return EnsureLocalCapacityInternal(soa, desired_capacity, "EnsureLocalCapacity");
   }
 
   static jobject NewGlobalRef(JNIEnv* env, jobject obj) {
     ScopedObjectAccess soa(env);
     mirror::Object* decoded_obj = soa.Decode<mirror::Object*>(obj);
-    // Check for null after decoding the object to handle cleared weak globals.
-    if (decoded_obj == nullptr) {
-      return nullptr;
-    }
-    JavaVMExt* vm = soa.Vm();
-    IndirectReferenceTable& globals = vm->globals;
-    WriterMutexLock mu(soa.Self(), vm->globals_lock);
-    IndirectRef ref = globals.Add(IRT_FIRST_SEGMENT, decoded_obj);
-    return reinterpret_cast<jobject>(ref);
+    return soa.Vm()->AddGlobalRef(soa.Self(), decoded_obj);
   }
 
   static void DeleteGlobalRef(JNIEnv* env, jobject obj) {
-    if (obj == nullptr) {
-      return;
-    }
-    JavaVMExt* vm = reinterpret_cast<JNIEnvExt*>(env)->vm;
-    IndirectReferenceTable& globals = vm->globals;
-    Thread* self = reinterpret_cast<JNIEnvExt*>(env)->self;
-    WriterMutexLock mu(self, vm->globals_lock);
-
-    if (!globals.Remove(IRT_FIRST_SEGMENT, obj)) {
-      LOG(WARNING) << "JNI WARNING: DeleteGlobalRef(" << obj << ") "
-                   << "failed to find entry";
-    }
+    JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm;
+    Thread* self = down_cast<JNIEnvExt*>(env)->self;
+    vm->DeleteGlobalRef(self, obj);
   }
 
   static jweak NewWeakGlobalRef(JNIEnv* env, jobject obj) {
     ScopedObjectAccess soa(env);
-    return AddWeakGlobalReference(soa, soa.Decode<mirror::Object*>(obj));
+    mirror::Object* decoded_obj = soa.Decode<mirror::Object*>(obj);
+    return soa.Vm()->AddWeakGlobalRef(soa.Self(), decoded_obj);
   }
 
   static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
-    if (obj != nullptr) {
-      ScopedObjectAccess soa(env);
-      soa.Vm()->DeleteWeakGlobalRef(soa.Self(), obj);
-    }
+    JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm;
+    Thread* self = down_cast<JNIEnvExt*>(env)->self;
+    vm->DeleteWeakGlobalRef(self, obj);
   }
 
   static jobject NewLocalRef(JNIEnv* env, jobject obj) {
@@ -861,7 +561,6 @@
     if (obj == nullptr) {
       return;
     }
-    ScopedObjectAccess soa(env);
     IndirectReferenceTable& locals = reinterpret_cast<JNIEnvExt*>(env)->locals;
 
     uint32_t cookie = reinterpret_cast<JNIEnvExt*>(env)->local_ref_cookie;
@@ -1927,11 +1626,11 @@
 
   static jstring NewString(JNIEnv* env, const jchar* chars, jsize char_count) {
     if (UNLIKELY(char_count < 0)) {
-      JniAbortF("NewString", "char_count < 0: %d", char_count);
+      JavaVmExtFromEnv(env)->JniAbortF("NewString", "char_count < 0: %d", char_count);
       return nullptr;
     }
     if (UNLIKELY(chars == nullptr && char_count > 0)) {
-      JniAbortF("NewString", "chars == null && char_count > 0");
+      JavaVmExtFromEnv(env)->JniAbortF("NewString", "chars == null && char_count > 0");
       return nullptr;
     }
     ScopedObjectAccess soa(env);
@@ -1993,7 +1692,7 @@
     ScopedObjectAccess soa(env);
     mirror::String* s = soa.Decode<mirror::String*>(java_string);
     mirror::CharArray* chars = s->GetCharArray();
-    PinPrimitiveArray(soa, chars);
+    soa.Vm()->PinPrimitiveArray(soa.Self(), chars);
     gc::Heap* heap = Runtime::Current()->GetHeap();
     if (heap->IsMovableObject(chars)) {
       if (is_copy != nullptr) {
@@ -2022,7 +1721,7 @@
     if (chars != (s_chars->GetData() + s->GetOffset())) {
       delete[] chars;
     }
-    UnpinPrimitiveArray(soa, s->GetCharArray());
+    soa.Vm()->UnpinPrimitiveArray(soa.Self(), s->GetCharArray());
   }
 
   static const jchar* GetStringCritical(JNIEnv* env, jstring java_string, jboolean* is_copy) {
@@ -2031,7 +1730,7 @@
     mirror::String* s = soa.Decode<mirror::String*>(java_string);
     mirror::CharArray* chars = s->GetCharArray();
     int32_t offset = s->GetOffset();
-    PinPrimitiveArray(soa, chars);
+    soa.Vm()->PinPrimitiveArray(soa.Self(), chars);
     gc::Heap* heap = Runtime::Current()->GetHeap();
     if (heap->IsMovableObject(chars)) {
       StackHandleScope<1> hs(soa.Self());
@@ -2047,7 +1746,8 @@
   static void ReleaseStringCritical(JNIEnv* env, jstring java_string, const jchar* chars) {
     CHECK_NON_NULL_ARGUMENT_RETURN_VOID(java_string);
     ScopedObjectAccess soa(env);
-    UnpinPrimitiveArray(soa, soa.Decode<mirror::String*>(java_string)->GetCharArray());
+    soa.Vm()->UnpinPrimitiveArray(soa.Self(),
+                                  soa.Decode<mirror::String*>(java_string)->GetCharArray());
     gc::Heap* heap = Runtime::Current()->GetHeap();
     mirror::String* s = soa.Decode<mirror::String*>(java_string);
     mirror::CharArray* s_chars = s->GetCharArray();
@@ -2083,7 +1783,8 @@
     ScopedObjectAccess soa(env);
     mirror::Object* obj = soa.Decode<mirror::Object*>(java_array);
     if (UNLIKELY(!obj->IsArrayInstance())) {
-      JniAbortF("GetArrayLength", "not an array: %s", PrettyTypeOf(obj).c_str());
+      soa.Vm()->JniAbortF("GetArrayLength", "not an array: %s", PrettyTypeOf(obj).c_str());
+      return 0;
     }
     mirror::Array* array = obj->AsArray();
     return array->GetLength();
@@ -2138,7 +1839,7 @@
   static jobjectArray NewObjectArray(JNIEnv* env, jsize length, jclass element_jclass,
                                      jobject initial_element) {
     if (UNLIKELY(length < 0)) {
-      JniAbortF("NewObjectArray", "negative array length: %d", length);
+      JavaVmExtFromEnv(env)->JniAbortF("NewObjectArray", "negative array length: %d", length);
       return nullptr;
     }
     CHECK_NON_NULL_ARGUMENT(element_jclass);
@@ -2149,8 +1850,8 @@
     {
       mirror::Class* element_class = soa.Decode<mirror::Class*>(element_jclass);
       if (UNLIKELY(element_class->IsPrimitive())) {
-        JniAbortF("NewObjectArray", "not an object type: %s",
-                  PrettyDescriptor(element_class).c_str());
+        soa.Vm()->JniAbortF("NewObjectArray", "not an object type: %s",
+                            PrettyDescriptor(element_class).c_str());
         return nullptr;
       }
       ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
@@ -2168,10 +1869,11 @@
       if (initial_object != nullptr) {
         mirror::Class* element_class = result->GetClass()->GetComponentType();
         if (UNLIKELY(!element_class->IsAssignableFrom(initial_object->GetClass()))) {
-          JniAbortF("NewObjectArray", "cannot assign object of type '%s' to array with element "
-                    "type of '%s'", PrettyDescriptor(initial_object->GetClass()).c_str(),
-                    PrettyDescriptor(element_class).c_str());
-
+          soa.Vm()->JniAbortF("NewObjectArray", "cannot assign object of type '%s' to array with "
+                              "element type of '%s'",
+                              PrettyDescriptor(initial_object->GetClass()).c_str(),
+                              PrettyDescriptor(element_class).c_str());
+          return nullptr;
         } else {
           for (jsize i = 0; i < length; ++i) {
             result->SetWithoutChecks<false>(i, initial_object);
@@ -2191,8 +1893,8 @@
     ScopedObjectAccess soa(env);
     mirror::Array* array = soa.Decode<mirror::Array*>(java_array);
     if (UNLIKELY(!array->GetClass()->IsPrimitiveArray())) {
-      JniAbortF("GetPrimitiveArrayCritical", "expected primitive array, given %s",
-                PrettyDescriptor(array->GetClass()).c_str());
+      soa.Vm()->JniAbortF("GetPrimitiveArrayCritical", "expected primitive array, given %s",
+                          PrettyDescriptor(array->GetClass()).c_str());
       return nullptr;
     }
     gc::Heap* heap = Runtime::Current()->GetHeap();
@@ -2201,7 +1903,7 @@
       // Re-decode in case the object moved since IncrementDisableGC waits for GC to complete.
       array = soa.Decode<mirror::Array*>(java_array);
     }
-    PinPrimitiveArray(soa, array);
+    soa.Vm()->PinPrimitiveArray(soa.Self(), array);
     if (is_copy != nullptr) {
       *is_copy = JNI_FALSE;
     }
@@ -2214,8 +1916,8 @@
     ScopedObjectAccess soa(env);
     mirror::Array* array = soa.Decode<mirror::Array*>(java_array);
     if (UNLIKELY(!array->GetClass()->IsPrimitiveArray())) {
-      JniAbortF("ReleasePrimitiveArrayCritical", "expected primitive array, given %s",
-                PrettyDescriptor(array->GetClass()).c_str());
+      soa.Vm()->JniAbortF("ReleasePrimitiveArrayCritical", "expected primitive array, given %s",
+                          PrettyDescriptor(array->GetClass()).c_str());
       return;
     }
     const size_t component_size = array->GetClass()->GetComponentSize();
@@ -2387,8 +2089,9 @@
   static jint RegisterNativeMethods(JNIEnv* env, jclass java_class, const JNINativeMethod* methods,
                                     jint method_count, bool return_errors) {
     if (UNLIKELY(method_count < 0)) {
-      JniAbortF("RegisterNatives", "negative method count: %d", method_count);
-      return JNI_ERR;  // Not reached.
+      JavaVmExtFromEnv(env)->JniAbortF("RegisterNatives", "negative method count: %d",
+                                       method_count);
+      return JNI_ERR;  // Not reached except in unit tests.
     }
     CHECK_NON_NULL_ARGUMENT_FN_NAME("RegisterNatives", java_class, JNI_ERR);
     ScopedObjectAccess soa(env);
@@ -2512,17 +2215,21 @@
 
   static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
     if (capacity < 0) {
-      JniAbortF("NewDirectByteBuffer", "negative buffer capacity: %" PRId64, capacity);
+      JavaVmExtFromEnv(env)->JniAbortF("NewDirectByteBuffer", "negative buffer capacity: %" PRId64,
+                                       capacity);
       return nullptr;
     }
     if (address == nullptr && capacity != 0) {
-      JniAbortF("NewDirectByteBuffer", "non-zero capacity for nullptr pointer: %" PRId64, capacity);
+      JavaVmExtFromEnv(env)->JniAbortF("NewDirectByteBuffer",
+                                       "non-zero capacity for nullptr pointer: %" PRId64, capacity);
       return nullptr;
     }
 
     // At the moment, the capacity of DirectByteBuffer is limited to a signed int.
     if (capacity > INT_MAX) {
-      JniAbortF("NewDirectByteBuffer", "buffer capacity greater than maximum jint: %" PRId64, capacity);
+      JavaVmExtFromEnv(env)->JniAbortF("NewDirectByteBuffer",
+                                       "buffer capacity greater than maximum jint: %" PRId64,
+                                       capacity);
       return nullptr;
     }
     jlong address_arg = reinterpret_cast<jlong>(address);
@@ -2576,8 +2283,9 @@
   }
 
  private:
-  static jint EnsureLocalCapacity(ScopedObjectAccess& soa, jint desired_capacity,
-                                  const char* caller) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  static jint EnsureLocalCapacityInternal(ScopedObjectAccess& soa, jint desired_capacity,
+                                          const char* caller)
+      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     // TODO: we should try to expand the table if necessary.
     if (desired_capacity < 0 || desired_capacity > static_cast<jint>(kLocalsMax)) {
       LOG(ERROR) << "Invalid capacity given to " << caller << ": " << desired_capacity;
@@ -2594,11 +2302,11 @@
 
   template<typename JniT, typename ArtT>
   static JniT NewPrimitiveArray(JNIEnv* env, jsize length) {
+    ScopedObjectAccess soa(env);
     if (UNLIKELY(length < 0)) {
-      JniAbortF("NewPrimitiveArray", "negative array length: %d", length);
+      soa.Vm()->JniAbortF("NewPrimitiveArray", "negative array length: %d", length);
       return nullptr;
     }
-    ScopedObjectAccess soa(env);
     ArtT* result = ArtT::Alloc(soa.Self(), length);
     return soa.AddLocalReference<JniT>(result);
   }
@@ -2609,9 +2317,11 @@
       SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     ArtArrayT* array = soa.Decode<ArtArrayT*>(java_array);
     if (UNLIKELY(ArtArrayT::GetArrayClass() != array->GetClass())) {
-      JniAbortF(fn_name, "attempt to %s %s primitive array elements with an object of type %s",
-                operation, PrettyDescriptor(ArtArrayT::GetArrayClass()->GetComponentType()).c_str(),
-                PrettyDescriptor(array->GetClass()).c_str());
+      soa.Vm()->JniAbortF(fn_name,
+                          "attempt to %s %s primitive array elements with an object of type %s",
+                          operation,
+                          PrettyDescriptor(ArtArrayT::GetArrayClass()->GetComponentType()).c_str(),
+                          PrettyDescriptor(array->GetClass()).c_str());
       return nullptr;
     }
     DCHECK_EQ(sizeof(ElementT), array->GetClass()->GetComponentSize());
@@ -2628,7 +2338,7 @@
     if (UNLIKELY(array == nullptr)) {
       return nullptr;
     }
-    PinPrimitiveArray(soa, array);
+    soa.Vm()->PinPrimitiveArray(soa.Self(), array);
     // Only make a copy if necessary.
     if (Runtime::Current()->GetHeap()->IsMovableObject(array)) {
       if (is_copy != nullptr) {
@@ -2674,8 +2384,9 @@
       // heap address. TODO: This might be slow to check, may be worth keeping track of which
       // copies we make?
       if (heap->IsNonDiscontinuousSpaceHeapAddress(reinterpret_cast<mirror::Object*>(elements))) {
-        JniAbortF("ReleaseArrayElements", "invalid element pointer %p, array elements are %p",
-                  reinterpret_cast<void*>(elements), array_data);
+        soa.Vm()->JniAbortF("ReleaseArrayElements",
+                            "invalid element pointer %p, array elements are %p",
+                            reinterpret_cast<void*>(elements), array_data);
         return;
       }
     }
@@ -2690,7 +2401,7 @@
         // Non copy to a movable object must means that we had disabled the moving GC.
         heap->DecrementDisableMovingGC(soa.Self());
       }
-      UnpinPrimitiveArray(soa, array);
+      soa.Vm()->UnpinPrimitiveArray(soa.Self(), array);
     }
   }
 
@@ -2971,487 +2682,8 @@
   JNI::GetObjectRefType,
 };
 
-JNIEnvExt::JNIEnvExt(Thread* self, JavaVMExt* vm)
-    : self(self),
-      vm(vm),
-      local_ref_cookie(IRT_FIRST_SEGMENT),
-      locals(kLocalsInitial, kLocalsMax, kLocal),
-      check_jni(false),
-      critical(0),
-      monitors("monitors", kMonitorsInitial, kMonitorsMax) {
-  functions = unchecked_functions = &gJniNativeInterface;
-  if (vm->check_jni) {
-    SetCheckJniEnabled(true);
-  }
-}
-
-JNIEnvExt::~JNIEnvExt() {
-}
-
-jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  if (obj == nullptr) {
-    return nullptr;
-  }
-  return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj));
-}
-
-void JNIEnvExt::DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  if (obj != nullptr) {
-    locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj));
-  }
-}
-void JNIEnvExt::SetCheckJniEnabled(bool enabled) {
-  check_jni = enabled;
-  functions = enabled ? GetCheckJniNativeInterface() : &gJniNativeInterface;
-}
-
-void JNIEnvExt::DumpReferenceTables(std::ostream& os) {
-  locals.Dump(os);
-  monitors.Dump(os);
-}
-
-void JNIEnvExt::PushFrame(int capacity) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  UNUSED(capacity);  // cpplint gets confused with (int) and thinks its a cast.
-  // TODO: take 'capacity' into account.
-  stacked_local_ref_cookies.push_back(local_ref_cookie);
-  local_ref_cookie = locals.GetSegmentState();
-}
-
-void JNIEnvExt::PopFrame() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-  locals.SetSegmentState(local_ref_cookie);
-  local_ref_cookie = stacked_local_ref_cookies.back();
-  stacked_local_ref_cookies.pop_back();
-}
-
-Offset JNIEnvExt::SegmentStateOffset() {
-  return Offset(OFFSETOF_MEMBER(JNIEnvExt, locals) +
-                IndirectReferenceTable::SegmentStateOffset().Int32Value());
-}
-
-// JNI Invocation interface.
-
-extern "C" jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) {
-  const JavaVMInitArgs* args = static_cast<JavaVMInitArgs*>(vm_args);
-  if (IsBadJniVersion(args->version)) {
-    LOG(ERROR) << "Bad JNI version passed to CreateJavaVM: " << args->version;
-    return JNI_EVERSION;
-  }
-  RuntimeOptions options;
-  for (int i = 0; i < args->nOptions; ++i) {
-    JavaVMOption* option = &args->options[i];
-    options.push_back(std::make_pair(std::string(option->optionString), option->extraInfo));
-  }
-  bool ignore_unrecognized = args->ignoreUnrecognized;
-  if (!Runtime::Create(options, ignore_unrecognized)) {
-    return JNI_ERR;
-  }
-  Runtime* runtime = Runtime::Current();
-  bool started = runtime->Start();
-  if (!started) {
-    delete Thread::Current()->GetJniEnv();
-    delete runtime->GetJavaVM();
-    LOG(WARNING) << "CreateJavaVM failed";
-    return JNI_ERR;
-  }
-  *p_env = Thread::Current()->GetJniEnv();
-  *p_vm = runtime->GetJavaVM();
-  return JNI_OK;
-}
-
-extern "C" jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize, jsize* vm_count) {
-  Runtime* runtime = Runtime::Current();
-  if (runtime == nullptr) {
-    *vm_count = 0;
-  } else {
-    *vm_count = 1;
-    vms[0] = runtime->GetJavaVM();
-  }
-  return JNI_OK;
-}
-
-// Historically unsupported.
-extern "C" jint JNI_GetDefaultJavaVMInitArgs(void* /*vm_args*/) {
-  return JNI_ERR;
-}
-
-class JII {
- public:
-  static jint DestroyJavaVM(JavaVM* vm) {
-    if (vm == nullptr) {
-      return JNI_ERR;
-    }
-    JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
-    delete raw_vm->runtime;
-    return JNI_OK;
-  }
-
-  static jint AttachCurrentThread(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-    return JII_AttachCurrentThread(vm, p_env, thr_args, false);
-  }
-
-  static jint AttachCurrentThreadAsDaemon(JavaVM* vm, JNIEnv** p_env, void* thr_args) {
-    return JII_AttachCurrentThread(vm, p_env, thr_args, true);
-  }
-
-  static jint DetachCurrentThread(JavaVM* vm) {
-    if (vm == nullptr || Thread::Current() == nullptr) {
-      return JNI_ERR;
-    }
-    JavaVMExt* raw_vm = reinterpret_cast<JavaVMExt*>(vm);
-    Runtime* runtime = raw_vm->runtime;
-    runtime->DetachCurrentThread();
-    return JNI_OK;
-  }
-
-  static jint GetEnv(JavaVM* vm, void** env, jint version) {
-    // GetEnv always returns a JNIEnv* for the most current supported JNI version,
-    // and unlike other calls that take a JNI version doesn't care if you supply
-    // JNI_VERSION_1_1, which we don't otherwise support.
-    if (IsBadJniVersion(version) && version != JNI_VERSION_1_1) {
-      LOG(ERROR) << "Bad JNI version passed to GetEnv: " << version;
-      return JNI_EVERSION;
-    }
-    if (vm == nullptr || env == nullptr) {
-      return JNI_ERR;
-    }
-    Thread* thread = Thread::Current();
-    if (thread == nullptr) {
-      *env = nullptr;
-      return JNI_EDETACHED;
-    }
-    *env = thread->GetJniEnv();
-    return JNI_OK;
-  }
-};
-
-const JNIInvokeInterface gJniInvokeInterface = {
-  nullptr,  // reserved0
-  nullptr,  // reserved1
-  nullptr,  // reserved2
-  JII::DestroyJavaVM,
-  JII::AttachCurrentThread,
-  JII::DetachCurrentThread,
-  JII::GetEnv,
-  JII::AttachCurrentThreadAsDaemon
-};
-
-JavaVMExt::JavaVMExt(Runtime* runtime, ParsedOptions* options)
-    : runtime(runtime),
-      check_jni_abort_hook(nullptr),
-      check_jni_abort_hook_data(nullptr),
-      check_jni(false),
-      force_copy(false),  // TODO: add a way to enable this
-      trace(options->jni_trace_),
-      pins_lock("JNI pin table lock", kPinTableLock),
-      pin_table("pin table", kPinTableInitial, kPinTableMax),
-      globals_lock("JNI global reference table lock"),
-      globals(gGlobalsInitial, gGlobalsMax, kGlobal),
-      libraries_lock("JNI shared libraries map lock", kLoadLibraryLock),
-      libraries(new Libraries),
-      weak_globals_lock_("JNI weak global reference table lock"),
-      weak_globals_(kWeakGlobalsInitial, kWeakGlobalsMax, kWeakGlobal),
-      allow_new_weak_globals_(true),
-      weak_globals_add_condition_("weak globals add condition", weak_globals_lock_) {
-  functions = unchecked_functions = &gJniInvokeInterface;
-  if (options->check_jni_) {
-    SetCheckJniEnabled(true);
-  }
-}
-
-JavaVMExt::~JavaVMExt() {
-  delete libraries;
-}
-
-jweak JavaVMExt::AddWeakGlobalReference(Thread* self, mirror::Object* obj) {
-  if (obj == nullptr) {
-    return nullptr;
-  }
-  MutexLock mu(self, weak_globals_lock_);
-  while (UNLIKELY(!allow_new_weak_globals_)) {
-    weak_globals_add_condition_.WaitHoldingLocks(self);
-  }
-  IndirectRef ref = weak_globals_.Add(IRT_FIRST_SEGMENT, obj);
-  return reinterpret_cast<jweak>(ref);
-}
-
-void JavaVMExt::DeleteWeakGlobalRef(Thread* self, jweak obj) {
-  MutexLock mu(self, weak_globals_lock_);
-  if (!weak_globals_.Remove(IRT_FIRST_SEGMENT, obj)) {
-    LOG(WARNING) << "JNI WARNING: DeleteWeakGlobalRef(" << obj << ") "
-                 << "failed to find entry";
-  }
-}
-
-void JavaVMExt::SetCheckJniEnabled(bool enabled) {
-  check_jni = enabled;
-  functions = enabled ? GetCheckJniInvokeInterface() : &gJniInvokeInterface;
-}
-
-void JavaVMExt::DumpForSigQuit(std::ostream& os) {
-  os << "JNI: CheckJNI is " << (check_jni ? "on" : "off");
-  if (force_copy) {
-    os << " (with forcecopy)";
-  }
-  Thread* self = Thread::Current();
-  {
-    MutexLock mu(self, pins_lock);
-    os << "; pins=" << pin_table.Size();
-  }
-  {
-    ReaderMutexLock mu(self, globals_lock);
-    os << "; globals=" << globals.Capacity();
-  }
-  {
-    MutexLock mu(self, weak_globals_lock_);
-    if (weak_globals_.Capacity() > 0) {
-      os << " (plus " << weak_globals_.Capacity() << " weak)";
-    }
-  }
-  os << '\n';
-
-  {
-    MutexLock mu(self, libraries_lock);
-    os << "Libraries: " << Dumpable<Libraries>(*libraries) << " (" << libraries->size() << ")\n";
-  }
-}
-
-void JavaVMExt::DisallowNewWeakGlobals() {
-  MutexLock mu(Thread::Current(), weak_globals_lock_);
-  allow_new_weak_globals_ = false;
-}
-
-void JavaVMExt::AllowNewWeakGlobals() {
-  Thread* self = Thread::Current();
-  MutexLock mu(self, weak_globals_lock_);
-  allow_new_weak_globals_ = true;
-  weak_globals_add_condition_.Broadcast(self);
-}
-
-mirror::Object* JavaVMExt::DecodeWeakGlobal(Thread* self, IndirectRef ref) {
-  MutexLock mu(self, weak_globals_lock_);
-  while (UNLIKELY(!allow_new_weak_globals_)) {
-    weak_globals_add_condition_.WaitHoldingLocks(self);
-  }
-  return weak_globals_.Get(ref);
-}
-
-void JavaVMExt::DumpReferenceTables(std::ostream& os) {
-  Thread* self = Thread::Current();
-  {
-    ReaderMutexLock mu(self, globals_lock);
-    globals.Dump(os);
-  }
-  {
-    MutexLock mu(self, weak_globals_lock_);
-    weak_globals_.Dump(os);
-  }
-  {
-    MutexLock mu(self, pins_lock);
-    pin_table.Dump(os);
-  }
-}
-
-bool JavaVMExt::LoadNativeLibrary(const std::string& path,
-                                  Handle<mirror::ClassLoader> class_loader,
-                                  std::string* detail) {
-  detail->clear();
-
-  // See if we've already loaded this library.  If we have, and the class loader
-  // matches, return successfully without doing anything.
-  // TODO: for better results we should canonicalize the pathname (or even compare
-  // inodes). This implementation is fine if everybody is using System.loadLibrary.
-  SharedLibrary* library;
-  Thread* self = Thread::Current();
-  {
-    // TODO: move the locking (and more of this logic) into Libraries.
-    MutexLock mu(self, libraries_lock);
-    library = libraries->Get(path);
-  }
-  if (library != nullptr) {
-    if (library->GetClassLoader() != class_loader.Get()) {
-      // The library will be associated with class_loader. The JNI
-      // spec says we can't load the same library into more than one
-      // class loader.
-      StringAppendF(detail, "Shared library \"%s\" already opened by "
-          "ClassLoader %p; can't open in ClassLoader %p",
-          path.c_str(), library->GetClassLoader(), class_loader.Get());
-      LOG(WARNING) << detail;
-      return false;
-    }
-    VLOG(jni) << "[Shared library \"" << path << "\" already loaded in "
-              << "ClassLoader " << class_loader.Get() << "]";
-    if (!library->CheckOnLoadResult()) {
-      StringAppendF(detail, "JNI_OnLoad failed on a previous attempt "
-          "to load \"%s\"", path.c_str());
-      return false;
-    }
-    return true;
-  }
-
-  // Open the shared library.  Because we're using a full path, the system
-  // doesn't have to search through LD_LIBRARY_PATH.  (It may do so to
-  // resolve this library's dependencies though.)
-
-  // Failures here are expected when java.library.path has several entries
-  // and we have to hunt for the lib.
-
-  // Below we dlopen but there is no paired dlclose, this would be necessary if we supported
-  // class unloading. Libraries will only be unloaded when the reference count (incremented by
-  // dlopen) becomes zero from dlclose.
-
-  // This can execute slowly for a large library on a busy system, so we
-  // want to switch from kRunnable while it executes.  This allows the GC to ignore us.
-  self->TransitionFromRunnableToSuspended(kWaitingForJniOnLoad);
-  const char* path_str = path.empty() ? nullptr : path.c_str();
-  void* handle = dlopen(path_str, RTLD_LAZY);
-  bool needs_native_bridge = false;
-  if (handle == nullptr) {
-    if (NativeBridge::IsSupported(path_str)) {
-      handle = NativeBridge::LoadLibrary(path_str, RTLD_LAZY);
-      needs_native_bridge = true;
-    }
-  }
-  self->TransitionFromSuspendedToRunnable();
-
-  VLOG(jni) << "[Call to dlopen(\"" << path << "\", RTLD_LAZY) returned " << handle << "]";
-
-  if (handle == nullptr) {
-    *detail = dlerror();
-    LOG(ERROR) << "dlopen(\"" << path << "\", RTLD_LAZY) failed: " << *detail;
-    return false;
-  }
-
-  // Create a new entry.
-  // TODO: move the locking (and more of this logic) into Libraries.
-  bool created_library = false;
-  {
-    MutexLock mu(self, libraries_lock);
-    library = libraries->Get(path);
-    if (library == nullptr) {  // We won race to get libraries_lock
-      library = new SharedLibrary(path, handle, class_loader.Get());
-      libraries->Put(path, library);
-      created_library = true;
-    }
-  }
-  if (!created_library) {
-    LOG(INFO) << "WOW: we lost a race to add shared library: "
-        << "\"" << path << "\" ClassLoader=" << class_loader.Get();
-    return library->CheckOnLoadResult();
-  }
-
-  VLOG(jni) << "[Added shared library \"" << path << "\" for ClassLoader " << class_loader.Get()
-      << "]";
-
-  bool was_successful = false;
-  void* sym = nullptr;
-  if (UNLIKELY(needs_native_bridge)) {
-    library->SetNeedsNativeBridge();
-    sym = library->FindSymbolWithNativeBridge("JNI_OnLoad", nullptr);
-  } else {
-    sym = dlsym(handle, "JNI_OnLoad");
-  }
-
-  if (sym == nullptr) {
-    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";
-    was_successful = true;
-  } else {
-    // Call JNI_OnLoad.  We have to override the current class
-    // loader, which will always be "null" since the stuff at the
-    // top of the stack is around Runtime.loadLibrary().  (See
-    // the comments in the JNI FindClass function.)
-    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);
-    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
-    StackHandleScope<1> hs(self);
-    Handle<mirror::ClassLoader> old_class_loader(hs.NewHandle(self->GetClassLoaderOverride()));
-    self->SetClassLoaderOverride(class_loader.Get());
-
-    int version = 0;
-    {
-      ScopedThreadStateChange tsc(self, kNative);
-      VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";
-      version = (*jni_on_load)(this, nullptr);
-    }
-
-    self->SetClassLoaderOverride(old_class_loader.Get());
-
-    if (version == JNI_ERR) {
-      StringAppendF(detail, "JNI_ERR returned from JNI_OnLoad in \"%s\"", path.c_str());
-    } else if (IsBadJniVersion(version)) {
-      StringAppendF(detail, "Bad JNI version returned from JNI_OnLoad in \"%s\": %d",
-                    path.c_str(), version);
-      // It's unwise to call dlclose() here, but we can mark it
-      // as bad and ensure that future load attempts will fail.
-      // We don't know how far JNI_OnLoad got, so there could
-      // be some partially-initialized stuff accessible through
-      // newly-registered native method calls.  We could try to
-      // unregister them, but that doesn't seem worthwhile.
-    } else {
-      was_successful = true;
-    }
-    VLOG(jni) << "[Returned " << (was_successful ? "successfully" : "failure")
-              << " from JNI_OnLoad in \"" << path << "\"]";
-  }
-
-  library->SetResult(was_successful);
-  return was_successful;
-}
-
-void* JavaVMExt::FindCodeForNativeMethod(mirror::ArtMethod* m) {
-  CHECK(m->IsNative());
-  mirror::Class* c = m->GetDeclaringClass();
-  // If this is a static method, it could be called before the class has been initialized.
-  if (m->IsStatic()) {
-    c = EnsureInitialized(Thread::Current(), c);
-    if (c == nullptr) {
-      return nullptr;
-    }
-  } else {
-    CHECK(c->IsInitializing()) << c->GetStatus() << " " << PrettyMethod(m);
-  }
-  std::string detail;
-  void* native_method;
-  Thread* self = Thread::Current();
-  {
-    MutexLock mu(self, libraries_lock);
-    native_method = libraries->FindNativeMethod(m, detail);
-  }
-  // Throwing can cause libraries_lock to be reacquired.
-  if (native_method == nullptr) {
-    ThrowLocation throw_location = self->GetCurrentLocationForThrow();
-    self->ThrowNewException(throw_location, "Ljava/lang/UnsatisfiedLinkError;", detail.c_str());
-  }
-  return native_method;
-}
-
-void JavaVMExt::SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg) {
-  MutexLock mu(Thread::Current(), weak_globals_lock_);
-  for (mirror::Object** entry : weak_globals_) {
-    // Since this is called by the GC, we don't need a read barrier.
-    mirror::Object* obj = *entry;
-    mirror::Object* new_obj = callback(obj, arg);
-    if (new_obj == nullptr) {
-      new_obj = kClearedJniWeakGlobal;
-    }
-    *entry = new_obj;
-  }
-}
-
-void JavaVMExt::VisitRoots(RootCallback* callback, void* arg) {
-  Thread* self = Thread::Current();
-  {
-    ReaderMutexLock mu(self, globals_lock);
-    globals.VisitRoots(callback, arg, 0, kRootJNIGlobal);
-  }
-  {
-    MutexLock mu(self, pins_lock);
-    pin_table.VisitRoots(callback, arg, 0, kRootVMInternal);
-  }
-  {
-    MutexLock mu(self, libraries_lock);
-    // Libraries contains shared libraries which hold a pointer to a class loader.
-    libraries->VisitRoots(callback, arg);
-  }
-  // The weak_globals table is visited by the GC itself (because it mutates the table).
+const JNINativeInterface* GetJniNativeInterface() {
+  return &gJniNativeInterface;
 }
 
 void RegisterNativeMethods(JNIEnv* env, const char* jni_class_name, const JNINativeMethod* methods,
diff --git a/runtime/jni_internal.h b/runtime/jni_internal.h
index ac502e6..48b10f5 100644
--- a/runtime/jni_internal.h
+++ b/runtime/jni_internal.h
@@ -17,16 +17,8 @@
 #ifndef ART_RUNTIME_JNI_INTERNAL_H_
 #define ART_RUNTIME_JNI_INTERNAL_H_
 
-#include "jni.h"
-
-#include "base/macros.h"
-#include "base/mutex.h"
-#include "indirect_reference_table.h"
-#include "object_callbacks.h"
-#include "reference_table.h"
-
+#include <jni.h>
 #include <iosfwd>
-#include <string>
 
 #ifndef NATIVE_METHOD
 #define NATIVE_METHOD(className, functionName, signature) \
@@ -36,189 +28,18 @@
   RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))
 
 namespace art {
-namespace mirror {
-  class ArtField;
-  class ArtMethod;
-  class ClassLoader;
-}  // namespace mirror
-union JValue;
-class Libraries;
-class ParsedOptions;
-class Runtime;
-class ScopedObjectAccess;
-template<class T> class Handle;
-class Thread;
 
-void JniAbortF(const char* jni_function_name, const char* fmt, ...)
-    __attribute__((__format__(__printf__, 2, 3)));
+const JNINativeInterface* GetJniNativeInterface();
+
+// Similar to RegisterNatives except its passed a descriptor for a class name and failures are
+// fatal.
 void RegisterNativeMethods(JNIEnv* env, const char* jni_class_name, const JNINativeMethod* methods,
                            jint method_count);
 
 int ThrowNewException(JNIEnv* env, jclass exception_class, const char* msg, jobject cause);
 
-class JavaVMExt : public JavaVM {
- public:
-  JavaVMExt(Runtime* runtime, ParsedOptions* options);
-  ~JavaVMExt();
-
-  /**
-   * Loads the given shared library. 'path' is an absolute pathname.
-   *
-   * Returns 'true' on success. On failure, sets 'detail' to a
-   * human-readable description of the error.
-   */
-  bool LoadNativeLibrary(const std::string& path, Handle<mirror::ClassLoader> class_loader,
-                         std::string* detail)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  /**
-   * Returns a pointer to the code for the native method 'm', found
-   * using dlsym(3) on every native library that's been loaded so far.
-   */
-  void* FindCodeForNativeMethod(mirror::ArtMethod* m)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  void DumpForSigQuit(std::ostream& os);
-
-  void DumpReferenceTables(std::ostream& os)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  void SetCheckJniEnabled(bool enabled);
-
-  void VisitRoots(RootCallback* callback, void* arg)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  void DisallowNewWeakGlobals() EXCLUSIVE_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void AllowNewWeakGlobals() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  jweak AddWeakGlobalReference(Thread* self, mirror::Object* obj)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void DeleteWeakGlobalRef(Thread* self, jweak obj)
-    SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void SweepJniWeakGlobals(IsMarkedCallback* callback, void* arg)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  mirror::Object* DecodeWeakGlobal(Thread* self, IndirectRef ref)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  Runtime* runtime;
-
-  // Used for testing. By default, we'll LOG(FATAL) the reason.
-  void (*check_jni_abort_hook)(void* data, const std::string& reason);
-  void* check_jni_abort_hook_data;
-
-  // Extra checking.
-  bool check_jni;
-  bool force_copy;
-
-  // Extra diagnostics.
-  std::string trace;
-
-  // Used to hold references to pinned primitive arrays.
-  Mutex pins_lock DEFAULT_MUTEX_ACQUIRED_AFTER;
-  ReferenceTable pin_table GUARDED_BY(pins_lock);
-
-  // JNI global references.
-  ReaderWriterMutex globals_lock DEFAULT_MUTEX_ACQUIRED_AFTER;
-  // Not guarded by globals_lock since we sometimes use SynchronizedGet in Thread::DecodeJObject.
-  IndirectReferenceTable globals;
-
-  Mutex libraries_lock DEFAULT_MUTEX_ACQUIRED_AFTER;
-  Libraries* libraries GUARDED_BY(libraries_lock);
-
-  // Used by -Xcheck:jni.
-  const JNIInvokeInterface* unchecked_functions;
-
- private:
-  // TODO: Make the other members of this class also private.
-  // JNI weak global references.
-  Mutex weak_globals_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
-  // Since weak_globals_ contain weak roots, be careful not to
-  // directly access the object references in it. Use Get() with the
-  // read barrier enabled.
-  IndirectReferenceTable weak_globals_ GUARDED_BY(weak_globals_lock_);
-  bool allow_new_weak_globals_ GUARDED_BY(weak_globals_lock_);
-  ConditionVariable weak_globals_add_condition_ GUARDED_BY(weak_globals_lock_);
-};
-
-struct JNIEnvExt : public JNIEnv {
-  JNIEnvExt(Thread* self, JavaVMExt* vm);
-  ~JNIEnvExt();
-
-  void DumpReferenceTables(std::ostream& os)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  void SetCheckJniEnabled(bool enabled);
-
-  void PushFrame(int capacity);
-  void PopFrame();
-
-  template<typename T>
-  T AddLocalReference(mirror::Object* obj)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  static Offset SegmentStateOffset();
-
-  static Offset LocalRefCookieOffset() {
-    return Offset(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie));
-  }
-
-  static Offset SelfOffset() {
-    return Offset(OFFSETOF_MEMBER(JNIEnvExt, self));
-  }
-
-  jobject NewLocalRef(mirror::Object* obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-  void DeleteLocalRef(jobject obj) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
-
-  Thread* const self;
-  JavaVMExt* vm;
-
-  // Cookie used when using the local indirect reference table.
-  uint32_t local_ref_cookie;
-
-  // JNI local references.
-  IndirectReferenceTable locals GUARDED_BY(Locks::mutator_lock_);
-
-  // Stack of cookies corresponding to PushLocalFrame/PopLocalFrame calls.
-  // TODO: to avoid leaks (and bugs), we need to clear this vector on entry (or return)
-  // to a native method.
-  std::vector<uint32_t> stacked_local_ref_cookies;
-
-  // Frequently-accessed fields cached from JavaVM.
-  bool check_jni;
-
-  // How many nested "critical" JNI calls are we in?
-  int critical;
-
-  // Entered JNI monitors, for bulk exit on thread detach.
-  ReferenceTable monitors;
-
-  // Used by -Xcheck:jni.
-  const JNINativeInterface* unchecked_functions;
-};
-
-const JNINativeInterface* GetCheckJniNativeInterface();
-const JNIInvokeInterface* GetCheckJniInvokeInterface();
-
-// Used to save and restore the JNIEnvExt state when not going through code created by the JNI
-// compiler
-class ScopedJniEnvLocalRefState {
- public:
-  explicit ScopedJniEnvLocalRefState(JNIEnvExt* env) : env_(env) {
-    saved_local_ref_cookie_ = env->local_ref_cookie;
-    env->local_ref_cookie = env->locals.GetSegmentState();
-  }
-
-  ~ScopedJniEnvLocalRefState() {
-    env_->locals.SetSegmentState(env_->local_ref_cookie);
-    env_->local_ref_cookie = saved_local_ref_cookie_;
-  }
-
- private:
-  JNIEnvExt* env_;
-  uint32_t saved_local_ref_cookie_;
-  DISALLOW_COPY_AND_ASSIGN(ScopedJniEnvLocalRefState);
-};
-
 }  // namespace art
 
 std::ostream& operator<<(std::ostream& os, const jobjectRefType& rhs);
+
 #endif  // ART_RUNTIME_JNI_INTERNAL_H_
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index da3080f..844d14a 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -17,6 +17,7 @@
 #include "jni_internal.h"
 
 #include "common_compiler_test.h"
+#include "java_vm_ext.h"
 #include "mirror/art_method-inl.h"
 #include "mirror/string-inl.h"
 #include "scoped_thread_state_change.h"
@@ -53,24 +54,15 @@
   }
 
   void ExpectException(jclass exception_class) {
-    EXPECT_TRUE(env_->ExceptionCheck());
+    ScopedObjectAccess soa(env_);
+    EXPECT_TRUE(env_->ExceptionCheck())
+        << PrettyDescriptor(soa.Decode<mirror::Class*>(exception_class));
     jthrowable exception = env_->ExceptionOccurred();
     EXPECT_NE(nullptr, exception);
     env_->ExceptionClear();
     EXPECT_TRUE(env_->IsInstanceOf(exception, exception_class));
   }
 
-  void ExpectClassFound(const char* name) {
-    EXPECT_NE(env_->FindClass(name), nullptr) << name;
-    EXPECT_FALSE(env_->ExceptionCheck()) << name;
-  }
-
-  void ExpectClassNotFound(const char* name) {
-    EXPECT_EQ(env_->FindClass(name), nullptr) << name;
-    EXPECT_TRUE(env_->ExceptionCheck()) << name;
-    env_->ExceptionClear();
-  }
-
   void CleanUpJniEnv() {
     if (aioobe_ != nullptr) {
       env_->DeleteGlobalRef(aioobe_);
@@ -98,6 +90,510 @@
     return soa.AddLocalReference<jclass>(c);
   }
 
+  void ExpectClassFound(const char* name) {
+    EXPECT_NE(env_->FindClass(name), nullptr) << name;
+    EXPECT_FALSE(env_->ExceptionCheck()) << name;
+  }
+
+  void ExpectClassNotFound(const char* name, bool check_jni, const char* check_jni_msg,
+                           CheckJniAbortCatcher* abort_catcher) {
+    EXPECT_EQ(env_->FindClass(name), nullptr) << name;
+    if (!check_jni || check_jni_msg == nullptr) {
+      EXPECT_TRUE(env_->ExceptionCheck()) << name;
+      env_->ExceptionClear();
+    } else {
+      abort_catcher->Check(check_jni_msg);
+    }
+  }
+
+  void FindClassTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    // Null argument is always an abort.
+    env_->FindClass(nullptr);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "name == null");
+
+    // Reference types...
+    ExpectClassFound("java/lang/String");
+    // ...for arrays too, where you must include "L;".
+    ExpectClassFound("[Ljava/lang/String;");
+    // Primitive arrays are okay too, if the primitive type is valid.
+    ExpectClassFound("[C");
+
+    // But primitive types aren't allowed...
+    ExpectClassNotFound("C", check_jni, nullptr, &check_jni_abort_catcher);
+    ExpectClassNotFound("V", check_jni, nullptr, &check_jni_abort_catcher);
+    ExpectClassNotFound("K", check_jni, nullptr, &check_jni_abort_catcher);
+
+    if (check_jni) {
+      // Check JNI will reject invalid class names as aborts but without pending exceptions.
+      EXPECT_EQ(env_->FindClass("java.lang.String"), nullptr);
+      EXPECT_FALSE(env_->ExceptionCheck());
+      check_jni_abort_catcher.Check("illegal class name 'java.lang.String'");
+
+      EXPECT_EQ(env_->FindClass("[Ljava.lang.String;"), nullptr);
+      EXPECT_FALSE(env_->ExceptionCheck());
+      check_jni_abort_catcher.Check("illegal class name '[Ljava.lang.String;'");
+    } else {
+      // Without check JNI we're tolerant and replace '.' with '/'.
+      ExpectClassFound("java.lang.String");
+      ExpectClassFound("[Ljava.lang.String;");
+    }
+
+    ExpectClassNotFound("Ljava.lang.String;", check_jni, "illegal class name 'Ljava.lang.String;'",
+                        &check_jni_abort_catcher);
+    ExpectClassNotFound("[java.lang.String", check_jni, "illegal class name '[java.lang.String'",
+                        &check_jni_abort_catcher);
+
+    // You can't include the "L;" in a JNI class descriptor.
+    ExpectClassNotFound("Ljava/lang/String;", check_jni, "illegal class name 'Ljava/lang/String;'",
+                        &check_jni_abort_catcher);
+
+    // But you must include it for an array of any reference type.
+    ExpectClassNotFound("[java/lang/String", check_jni, "illegal class name '[java/lang/String'",
+                        &check_jni_abort_catcher);
+
+    ExpectClassNotFound("[K", check_jni, "illegal class name '[K'", &check_jni_abort_catcher);
+
+    // Void arrays aren't allowed.
+    ExpectClassNotFound("[V", check_jni, "illegal class name '[V'", &check_jni_abort_catcher);
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetFieldIdBadArgumentTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    jclass c = env_->FindClass("java/lang/String");
+    ASSERT_NE(c, nullptr);
+
+    jfieldID fid = env_->GetFieldID(nullptr, "count", "I");
+    EXPECT_EQ(nullptr, fid);
+    check_jni_abort_catcher.Check(check_jni ? "GetFieldID received NULL jclass"
+                                            : "java_class == null");
+    fid = env_->GetFieldID(c, nullptr, "I");
+    EXPECT_EQ(nullptr, fid);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "name == null");
+    fid = env_->GetFieldID(c, "count", nullptr);
+    EXPECT_EQ(nullptr, fid);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "sig == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetStaticFieldIdBadArgumentTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    jclass c = env_->FindClass("java/lang/String");
+    ASSERT_NE(c, nullptr);
+
+    jfieldID fid = env_->GetStaticFieldID(nullptr, "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
+    EXPECT_EQ(nullptr, fid);
+    check_jni_abort_catcher.Check(check_jni ? "GetStaticFieldID received NULL jclass"
+                                            : "java_class == null");
+    fid = env_->GetStaticFieldID(c, nullptr, "Ljava/util/Comparator;");
+    EXPECT_EQ(nullptr, fid);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "name == null");
+    fid = env_->GetStaticFieldID(c, "CASE_INSENSITIVE_ORDER", nullptr);
+    EXPECT_EQ(nullptr, fid);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "sig == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetMethodIdBadArgumentTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    jmethodID method = env_->GetMethodID(nullptr, "<init>", "(Ljava/lang/String;)V");
+    EXPECT_EQ(nullptr, method);
+    check_jni_abort_catcher.Check(check_jni ? "GetMethodID received NULL jclass"
+                                            : "java_class == null");
+    jclass jlnsme = env_->FindClass("java/lang/NoSuchMethodError");
+    ASSERT_TRUE(jlnsme != nullptr);
+    method = env_->GetMethodID(jlnsme, nullptr, "(Ljava/lang/String;)V");
+    EXPECT_EQ(nullptr, method);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "name == null");
+    method = env_->GetMethodID(jlnsme, "<init>", nullptr);
+    EXPECT_EQ(nullptr, method);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "sig == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetStaticMethodIdBadArgumentTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    jmethodID method = env_->GetStaticMethodID(nullptr, "valueOf", "(I)Ljava/lang/String;");
+    EXPECT_EQ(nullptr, method);
+    check_jni_abort_catcher.Check(check_jni ? "GetStaticMethodID received NULL jclass"
+                                            : "java_class == null");
+    jclass jlstring = env_->FindClass("java/lang/String");
+    method = env_->GetStaticMethodID(jlstring, nullptr, "(I)Ljava/lang/String;");
+    EXPECT_EQ(nullptr, method);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "name == null");
+    method = env_->GetStaticMethodID(jlstring, "valueOf", nullptr);
+    EXPECT_EQ(nullptr, method);
+    check_jni_abort_catcher.Check(check_jni ? "non-nullable const char* was NULL"
+                                            : "sig == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetFromReflectedField_ToReflectedFieldBadArgumentTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    jclass c = env_->FindClass("java/lang/String");
+    ASSERT_NE(c, nullptr);
+    jfieldID fid = env_->GetFieldID(c, "count", "I");
+    ASSERT_NE(fid, nullptr);
+
+    // Check class argument for null argument, not checked in non-check JNI.
+    jobject field = env_->ToReflectedField(nullptr, fid, JNI_FALSE);
+    if (check_jni) {
+      EXPECT_EQ(field, nullptr);
+      check_jni_abort_catcher.Check("ToReflectedField received NULL jclass");
+    } else {
+      EXPECT_NE(field, nullptr);
+    }
+
+    field = env_->ToReflectedField(c, nullptr, JNI_FALSE);
+    EXPECT_EQ(field, nullptr);
+    check_jni_abort_catcher.Check(check_jni ? "jfieldID was NULL"
+                                            : "fid == null");
+
+    fid = env_->FromReflectedField(nullptr);
+    ASSERT_EQ(fid, nullptr);
+    check_jni_abort_catcher.Check(check_jni ? "expected non-null java.lang.reflect.Field"
+                                            : "jlr_field == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetFromReflectedMethod_ToReflectedMethodBadArgumentTest(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher check_jni_abort_catcher;
+
+    jclass c = env_->FindClass("java/lang/String");
+    ASSERT_NE(c, nullptr);
+    jmethodID mid = env_->GetMethodID(c, "<init>", "()V");
+    ASSERT_NE(mid, nullptr);
+
+    // Check class argument for null argument, not checked in non-check JNI.
+    jobject method = env_->ToReflectedMethod(nullptr, mid, JNI_FALSE);
+    if (check_jni) {
+      EXPECT_EQ(method, nullptr);
+      check_jni_abort_catcher.Check("ToReflectedMethod received NULL jclass");
+    } else {
+      EXPECT_NE(method, nullptr);
+    }
+
+    method = env_->ToReflectedMethod(c, nullptr, JNI_FALSE);
+    EXPECT_EQ(method, nullptr);
+    check_jni_abort_catcher.Check(check_jni ? "jmethodID was NULL"
+                                            : "mid == null");
+    mid = env_->FromReflectedMethod(method);
+    ASSERT_EQ(mid, nullptr);
+    check_jni_abort_catcher.Check(check_jni ? "expected non-null method" : "jlr_method == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void RegisterAndUnregisterNativesBadArguments(bool check_jni,
+                                                CheckJniAbortCatcher* check_jni_abort_catcher) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    // Passing a class of null is a failure.
+    {
+      JNINativeMethod methods[] = { };
+      EXPECT_EQ(env_->RegisterNatives(nullptr, methods, 0), JNI_ERR);
+      check_jni_abort_catcher->Check(check_jni ? "RegisterNatives received NULL jclass"
+                                               : "java_class == null");
+    }
+
+    // Passing methods as null is a failure.
+    jclass jlobject = env_->FindClass("java/lang/Object");
+    EXPECT_EQ(env_->RegisterNatives(jlobject, nullptr, 1), JNI_ERR);
+    check_jni_abort_catcher->Check("methods == null");
+
+    // Unregisters null is a failure.
+    EXPECT_EQ(env_->UnregisterNatives(nullptr), JNI_ERR);
+    check_jni_abort_catcher->Check(check_jni ? "UnregisterNatives received NULL jclass"
+                                             : "java_class == null");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+
+  void GetPrimitiveArrayElementsOfWrongType(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher jni_abort_catcher;
+
+    jbooleanArray array = env_->NewBooleanArray(10);
+    jboolean is_copy;
+    EXPECT_EQ(env_->GetByteArrayElements(reinterpret_cast<jbyteArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected byte[]"
+            : "attempt to get byte primitive array elements with an object of type boolean[]");
+    EXPECT_EQ(env_->GetShortArrayElements(reinterpret_cast<jshortArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected short[]"
+            : "attempt to get short primitive array elements with an object of type boolean[]");
+    EXPECT_EQ(env_->GetCharArrayElements(reinterpret_cast<jcharArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected char[]"
+            : "attempt to get char primitive array elements with an object of type boolean[]");
+    EXPECT_EQ(env_->GetIntArrayElements(reinterpret_cast<jintArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected int[]"
+            : "attempt to get int primitive array elements with an object of type boolean[]");
+    EXPECT_EQ(env_->GetLongArrayElements(reinterpret_cast<jlongArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected long[]"
+            : "attempt to get long primitive array elements with an object of type boolean[]");
+    EXPECT_EQ(env_->GetFloatArrayElements(reinterpret_cast<jfloatArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected float[]"
+            : "attempt to get float primitive array elements with an object of type boolean[]");
+    EXPECT_EQ(env_->GetDoubleArrayElements(reinterpret_cast<jdoubleArray>(array), &is_copy), nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected double[]"
+            : "attempt to get double primitive array elements with an object of type boolean[]");
+    jbyteArray array2 = env_->NewByteArray(10);
+    EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), &is_copy),
+              nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type byte[] expected boolean[]"
+            : "attempt to get boolean primitive array elements with an object of type byte[]");
+    jobject object = env_->NewStringUTF("Test String");
+    EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), &is_copy),
+              nullptr);
+    jni_abort_catcher.Check(
+        check_jni ? "jarray argument has non-array type: java.lang.String"
+        : "attempt to get boolean primitive array elements with an object of type java.lang.String");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void ReleasePrimitiveArrayElementsOfWrongType(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher jni_abort_catcher;
+
+    jbooleanArray array = env_->NewBooleanArray(10);
+    ASSERT_TRUE(array != nullptr);
+    jboolean is_copy;
+    jboolean* elements = env_->GetBooleanArrayElements(array, &is_copy);
+    ASSERT_TRUE(elements != nullptr);
+    env_->ReleaseByteArrayElements(reinterpret_cast<jbyteArray>(array),
+                                   reinterpret_cast<jbyte*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected byte[]"
+            : "attempt to release byte primitive array elements with an object of type boolean[]");
+    env_->ReleaseShortArrayElements(reinterpret_cast<jshortArray>(array),
+                                    reinterpret_cast<jshort*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected short[]"
+            : "attempt to release short primitive array elements with an object of type boolean[]");
+    env_->ReleaseCharArrayElements(reinterpret_cast<jcharArray>(array),
+                                   reinterpret_cast<jchar*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected char[]"
+            : "attempt to release char primitive array elements with an object of type boolean[]");
+    env_->ReleaseIntArrayElements(reinterpret_cast<jintArray>(array),
+                                  reinterpret_cast<jint*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected int[]"
+            : "attempt to release int primitive array elements with an object of type boolean[]");
+    env_->ReleaseLongArrayElements(reinterpret_cast<jlongArray>(array),
+                                   reinterpret_cast<jlong*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected long[]"
+            : "attempt to release long primitive array elements with an object of type boolean[]");
+    env_->ReleaseFloatArrayElements(reinterpret_cast<jfloatArray>(array),
+                                    reinterpret_cast<jfloat*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected float[]"
+            : "attempt to release float primitive array elements with an object of type boolean[]");
+    env_->ReleaseDoubleArrayElements(reinterpret_cast<jdoubleArray>(array),
+                                     reinterpret_cast<jdouble*>(elements), 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected double[]"
+            : "attempt to release double primitive array elements with an object of type boolean[]");
+    jbyteArray array2 = env_->NewByteArray(10);
+    env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), elements, 0);
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type byte[] expected boolean[]"
+            : "attempt to release boolean primitive array elements with an object of type byte[]");
+    jobject object = env_->NewStringUTF("Test String");
+    env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), elements, 0);
+    jni_abort_catcher.Check(
+        check_jni ? "jarray argument has non-array type: java.lang.String"
+            : "attempt to release boolean primitive array elements with an object of type "
+              "java.lang.String");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetReleasePrimitiveArrayCriticalOfWrongType(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher jni_abort_catcher;
+
+    jobject object = env_->NewStringUTF("Test String");
+    jboolean is_copy;
+    void* elements = env_->GetPrimitiveArrayCritical(reinterpret_cast<jarray>(object), &is_copy);
+    jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String"
+        : "expected primitive array, given java.lang.String");
+    env_->ReleasePrimitiveArrayCritical(reinterpret_cast<jarray>(object), elements, 0);
+    jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String"
+        : "expected primitive array, given java.lang.String");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void GetPrimitiveArrayRegionElementsOfWrongType(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher jni_abort_catcher;
+    constexpr size_t kLength = 10;
+    jbooleanArray array = env_->NewBooleanArray(kLength);
+    ASSERT_TRUE(array != nullptr);
+    jboolean elements[kLength];
+    env_->GetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength,
+                             reinterpret_cast<jbyte*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected byte[]"
+            : "attempt to get region of byte primitive array elements with an object of type boolean[]");
+    env_->GetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength,
+                              reinterpret_cast<jshort*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected short[]"
+            : "attempt to get region of short primitive array elements with an object of type boolean[]");
+    env_->GetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength,
+                             reinterpret_cast<jchar*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected char[]"
+            : "attempt to get region of char primitive array elements with an object of type boolean[]");
+    env_->GetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength,
+                            reinterpret_cast<jint*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected int[]"
+            : "attempt to get region of int primitive array elements with an object of type boolean[]");
+    env_->GetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength,
+                             reinterpret_cast<jlong*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected long[]"
+            : "attempt to get region of long primitive array elements with an object of type boolean[]");
+    env_->GetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength,
+                              reinterpret_cast<jfloat*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected float[]"
+            : "attempt to get region of float primitive array elements with an object of type boolean[]");
+    env_->GetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength,
+                               reinterpret_cast<jdouble*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected double[]"
+            : "attempt to get region of double primitive array elements with an object of type boolean[]");
+    jbyteArray array2 = env_->NewByteArray(10);
+    env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength,
+                                reinterpret_cast<jboolean*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type byte[] expected boolean[]"
+            : "attempt to get region of boolean primitive array elements with an object of type byte[]");
+    jobject object = env_->NewStringUTF("Test String");
+    env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength,
+                                reinterpret_cast<jboolean*>(elements));
+    jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String"
+        : "attempt to get region of boolean primitive array elements with an object of type "
+          "java.lang.String");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void SetPrimitiveArrayRegionElementsOfWrongType(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher jni_abort_catcher;
+    constexpr size_t kLength = 10;
+    jbooleanArray array = env_->NewBooleanArray(kLength);
+    ASSERT_TRUE(array != nullptr);
+    jboolean elements[kLength];
+    env_->SetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength,
+                             reinterpret_cast<jbyte*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected byte[]"
+            : "attempt to set region of byte primitive array elements with an object of type boolean[]");
+    env_->SetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength,
+                              reinterpret_cast<jshort*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected short[]"
+            : "attempt to set region of short primitive array elements with an object of type boolean[]");
+    env_->SetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength,
+                             reinterpret_cast<jchar*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected char[]"
+            : "attempt to set region of char primitive array elements with an object of type boolean[]");
+    env_->SetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength,
+                            reinterpret_cast<jint*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected int[]"
+            : "attempt to set region of int primitive array elements with an object of type boolean[]");
+    env_->SetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength,
+                             reinterpret_cast<jlong*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected long[]"
+            : "attempt to set region of long primitive array elements with an object of type boolean[]");
+    env_->SetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength,
+                              reinterpret_cast<jfloat*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected float[]"
+            : "attempt to set region of float primitive array elements with an object of type boolean[]");
+    env_->SetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength,
+                               reinterpret_cast<jdouble*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type boolean[] expected double[]"
+            : "attempt to set region of double primitive array elements with an object of type boolean[]");
+    jbyteArray array2 = env_->NewByteArray(10);
+    env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength,
+                                reinterpret_cast<jboolean*>(elements));
+    jni_abort_catcher.Check(
+        check_jni ? "incompatible array type byte[] expected boolean[]"
+            : "attempt to set region of boolean primitive array elements with an object of type byte[]");
+    jobject object = env_->NewStringUTF("Test String");
+    env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength,
+                                reinterpret_cast<jboolean*>(elements));
+    jni_abort_catcher.Check(check_jni ? "jarray argument has non-array type: java.lang.String"
+        : "attempt to set region of boolean primitive array elements with an object of type "
+          "java.lang.String");
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
+  void NewObjectArrayBadArguments(bool check_jni) {
+    bool old_check_jni = vm_->SetCheckJniEnabled(check_jni);
+    CheckJniAbortCatcher jni_abort_catcher;
+
+    jclass element_class = env_->FindClass("java/lang/String");
+    ASSERT_NE(element_class, nullptr);
+
+    env_->NewObjectArray(-1, element_class, nullptr);
+    jni_abort_catcher.Check(check_jni ? "negative jsize: -1" : "negative array length: -1");
+
+    env_->NewObjectArray(std::numeric_limits<jint>::min(), element_class, nullptr);
+    jni_abort_catcher.Check(check_jni ? "negative jsize: -2147483648"
+        : "negative array length: -2147483648");
+
+    EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni));
+  }
+
   JavaVMExt* vm_;
   JNIEnv* env_;
   jclass aioobe_;
@@ -125,48 +621,8 @@
 }
 
 TEST_F(JniInternalTest, FindClass) {
-  // Reference types...
-  ExpectClassFound("java/lang/String");
-  // ...for arrays too, where you must include "L;".
-  ExpectClassFound("[Ljava/lang/String;");
-  // Primitive arrays are okay too, if the primitive type is valid.
-  ExpectClassFound("[C");
-
-  {
-    CheckJniAbortCatcher check_jni_abort_catcher;
-    env_->FindClass(nullptr);
-    check_jni_abort_catcher.Check("name == null");
-
-    // We support . as well as / for compatibility, if -Xcheck:jni is off.
-    ExpectClassFound("java.lang.String");
-    check_jni_abort_catcher.Check("illegal class name 'java.lang.String'");
-    ExpectClassNotFound("Ljava.lang.String;");
-    check_jni_abort_catcher.Check("illegal class name 'Ljava.lang.String;'");
-    ExpectClassFound("[Ljava.lang.String;");
-    check_jni_abort_catcher.Check("illegal class name '[Ljava.lang.String;'");
-    ExpectClassNotFound("[java.lang.String");
-    check_jni_abort_catcher.Check("illegal class name '[java.lang.String'");
-
-    // You can't include the "L;" in a JNI class descriptor.
-    ExpectClassNotFound("Ljava/lang/String;");
-    check_jni_abort_catcher.Check("illegal class name 'Ljava/lang/String;'");
-
-    // But you must include it for an array of any reference type.
-    ExpectClassNotFound("[java/lang/String");
-    check_jni_abort_catcher.Check("illegal class name '[java/lang/String'");
-
-    ExpectClassNotFound("[K");
-    check_jni_abort_catcher.Check("illegal class name '[K'");
-
-    // Void arrays aren't allowed.
-    ExpectClassNotFound("[V");
-    check_jni_abort_catcher.Check("illegal class name '[V'");
-  }
-
-  // But primitive types aren't allowed...
-  ExpectClassNotFound("C");
-  ExpectClassNotFound("V");
-  ExpectClassNotFound("K");
+  FindClassTest(false);
+  FindClassTest(true);
 }
 
 TEST_F(JniInternalTest, GetFieldID) {
@@ -208,16 +664,8 @@
   ExpectException(jlnsfe);
 
   // Bad arguments.
-  CheckJniAbortCatcher check_jni_abort_catcher;
-  fid = env_->GetFieldID(nullptr, "count", "I");
-  EXPECT_EQ(nullptr, fid);
-  check_jni_abort_catcher.Check("java_class == null");
-  fid = env_->GetFieldID(c, nullptr, "I");
-  EXPECT_EQ(nullptr, fid);
-  check_jni_abort_catcher.Check("name == null");
-  fid = env_->GetFieldID(c, "count", nullptr);
-  EXPECT_EQ(nullptr, fid);
-  check_jni_abort_catcher.Check("sig == null");
+  GetFieldIdBadArgumentTest(false);
+  GetFieldIdBadArgumentTest(true);
 }
 
 TEST_F(JniInternalTest, GetStaticFieldID) {
@@ -253,16 +701,8 @@
   ExpectException(jlnsfe);
 
   // Bad arguments.
-  CheckJniAbortCatcher check_jni_abort_catcher;
-  fid = env_->GetStaticFieldID(nullptr, "CASE_INSENSITIVE_ORDER", "Ljava/util/Comparator;");
-  EXPECT_EQ(nullptr, fid);
-  check_jni_abort_catcher.Check("java_class == null");
-  fid = env_->GetStaticFieldID(c, nullptr, "Ljava/util/Comparator;");
-  EXPECT_EQ(nullptr, fid);
-  check_jni_abort_catcher.Check("name == null");
-  fid = env_->GetStaticFieldID(c, "CASE_INSENSITIVE_ORDER", nullptr);
-  EXPECT_EQ(nullptr, fid);
-  check_jni_abort_catcher.Check("sig == null");
+  GetStaticFieldIdBadArgumentTest(false);
+  GetStaticFieldIdBadArgumentTest(true);
 }
 
 TEST_F(JniInternalTest, GetMethodID) {
@@ -302,16 +742,8 @@
   EXPECT_FALSE(env_->ExceptionCheck());
 
   // Bad arguments.
-  CheckJniAbortCatcher check_jni_abort_catcher;
-  method = env_->GetMethodID(nullptr, "<init>", "(Ljava/lang/String;)V");
-  EXPECT_EQ(nullptr, method);
-  check_jni_abort_catcher.Check("java_class == null");
-  method = env_->GetMethodID(jlnsme, nullptr, "(Ljava/lang/String;)V");
-  EXPECT_EQ(nullptr, method);
-  check_jni_abort_catcher.Check("name == null");
-  method = env_->GetMethodID(jlnsme, "<init>", nullptr);
-  EXPECT_EQ(nullptr, method);
-  check_jni_abort_catcher.Check("sig == null");
+  GetMethodIdBadArgumentTest(false);
+  GetMethodIdBadArgumentTest(true);
 }
 
 TEST_F(JniInternalTest, GetStaticMethodID) {
@@ -340,16 +772,8 @@
   EXPECT_FALSE(env_->ExceptionCheck());
 
   // Bad arguments.
-  CheckJniAbortCatcher check_jni_abort_catcher;
-  method = env_->GetStaticMethodID(nullptr, "valueOf", "(I)Ljava/lang/String;");
-  EXPECT_EQ(nullptr, method);
-  check_jni_abort_catcher.Check("java_class == null");
-  method = env_->GetStaticMethodID(jlstring, nullptr, "(I)Ljava/lang/String;");
-  EXPECT_EQ(nullptr, method);
-  check_jni_abort_catcher.Check("name == null");
-  method = env_->GetStaticMethodID(jlstring, "valueOf", nullptr);
-  EXPECT_EQ(nullptr, method);
-  check_jni_abort_catcher.Check("sig == null");
+  GetStaticMethodIdBadArgumentTest(false);
+  GetStaticMethodIdBadArgumentTest(true);
 }
 
 TEST_F(JniInternalTest, FromReflectedField_ToReflectedField) {
@@ -370,13 +794,8 @@
   ASSERT_EQ(4, env_->GetIntField(s, fid2));
 
   // Bad arguments.
-  CheckJniAbortCatcher check_jni_abort_catcher;
-  field = env_->ToReflectedField(c, nullptr, JNI_FALSE);
-  EXPECT_EQ(field, nullptr);
-  check_jni_abort_catcher.Check("fid == null");
-  fid2 = env_->FromReflectedField(nullptr);
-  ASSERT_EQ(fid2, nullptr);
-  check_jni_abort_catcher.Check("jlr_field == null");
+  GetFromReflectedField_ToReflectedFieldBadArgumentTest(false);
+  GetFromReflectedField_ToReflectedFieldBadArgumentTest(true);
 }
 
 TEST_F(JniInternalTest, FromReflectedMethod_ToReflectedMethod) {
@@ -417,13 +836,8 @@
   ASSERT_EQ(4, env_->CallIntMethod(s, mid2));
 
   // Bad arguments.
-  CheckJniAbortCatcher check_jni_abort_catcher;
-  method = env_->ToReflectedMethod(c, nullptr, JNI_FALSE);
-  EXPECT_EQ(method, nullptr);
-  check_jni_abort_catcher.Check("mid == null");
-  mid2 = env_->FromReflectedMethod(method);
-  ASSERT_EQ(mid2, nullptr);
-  check_jni_abort_catcher.Check("jlr_method == null");
+  GetFromReflectedMethod_ToReflectedMethodBadArgumentTest(false);
+  GetFromReflectedMethod_ToReflectedMethodBadArgumentTest(true);
 }
 
 static void BogusMethod() {
@@ -498,23 +912,11 @@
   }
   EXPECT_FALSE(env_->ExceptionCheck());
 
-  // Passing a class of null is a failure.
-  {
-    JNINativeMethod methods[] = { };
-    EXPECT_EQ(env_->RegisterNatives(nullptr, methods, 0), JNI_ERR);
-    check_jni_abort_catcher.Check("java_class == null");
-  }
-
-  // Passing methods as null is a failure.
-  EXPECT_EQ(env_->RegisterNatives(jlobject, nullptr, 1), JNI_ERR);
-  check_jni_abort_catcher.Check("methods == null");
-
-  // Unregisters null is a failure.
-  EXPECT_EQ(env_->UnregisterNatives(nullptr), JNI_ERR);
-  check_jni_abort_catcher.Check("java_class == null");
-
   // Unregistering a class with no natives is a warning.
   EXPECT_EQ(env_->UnregisterNatives(jlnsme), JNI_OK);
+
+  RegisterAndUnregisterNativesBadArguments(false, &check_jni_abort_catcher);
+  RegisterAndUnregisterNativesBadArguments(true, &check_jni_abort_catcher);
 }
 
 #define EXPECT_PRIMITIVE_ARRAY(new_fn, \
@@ -528,6 +930,7 @@
   \
   { \
     CheckJniAbortCatcher jni_abort_catcher; \
+    down_cast<JNIEnvExt*>(env_)->SetCheckJniEnabled(false); \
     /* Allocate an negative sized array and check it has the right failure type. */ \
     EXPECT_EQ(env_->new_fn(-1), nullptr); \
     jni_abort_catcher.Check("negative array length: -1"); \
@@ -550,6 +953,7 @@
     jni_abort_catcher.Check("buf == null"); \
     env_->set_region_fn(a, 0, size, nullptr); \
     jni_abort_catcher.Check("buf == null"); \
+    down_cast<JNIEnvExt*>(env_)->SetCheckJniEnabled(true); \
   } \
   /* Allocate an array and check it has the right type and length. */ \
   scalar_type ## Array a = env_->new_fn(size); \
@@ -654,189 +1058,28 @@
 }
 
 TEST_F(JniInternalTest, GetPrimitiveArrayElementsOfWrongType) {
-  CheckJniAbortCatcher jni_abort_catcher;
-  jbooleanArray array = env_->NewBooleanArray(10);
-  jboolean is_copy;
-  EXPECT_EQ(env_->GetByteArrayElements(reinterpret_cast<jbyteArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get byte primitive array elements with an object of type boolean[]");
-  EXPECT_EQ(env_->GetShortArrayElements(reinterpret_cast<jshortArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get short primitive array elements with an object of type boolean[]");
-  EXPECT_EQ(env_->GetCharArrayElements(reinterpret_cast<jcharArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get char primitive array elements with an object of type boolean[]");
-  EXPECT_EQ(env_->GetIntArrayElements(reinterpret_cast<jintArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get int primitive array elements with an object of type boolean[]");
-  EXPECT_EQ(env_->GetLongArrayElements(reinterpret_cast<jlongArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get long primitive array elements with an object of type boolean[]");
-  EXPECT_EQ(env_->GetFloatArrayElements(reinterpret_cast<jfloatArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get float primitive array elements with an object of type boolean[]");
-  EXPECT_EQ(env_->GetDoubleArrayElements(reinterpret_cast<jdoubleArray>(array), &is_copy), nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get double primitive array elements with an object of type boolean[]");
-  jbyteArray array2 = env_->NewByteArray(10);
-  EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), &is_copy),
-            nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get boolean primitive array elements with an object of type byte[]");
-  jobject object = env_->NewStringUTF("Test String");
-  EXPECT_EQ(env_->GetBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), &is_copy),
-            nullptr);
-  jni_abort_catcher.Check(
-      "attempt to get boolean primitive array elements with an object of type java.lang.String");
+  GetPrimitiveArrayElementsOfWrongType(false);
+  GetPrimitiveArrayElementsOfWrongType(true);
 }
 
 TEST_F(JniInternalTest, ReleasePrimitiveArrayElementsOfWrongType) {
-  CheckJniAbortCatcher jni_abort_catcher;
-  jbooleanArray array = env_->NewBooleanArray(10);
-  ASSERT_TRUE(array != nullptr);
-  jboolean is_copy;
-  jboolean* elements = env_->GetBooleanArrayElements(array, &is_copy);
-  ASSERT_TRUE(elements != nullptr);
-  env_->ReleaseByteArrayElements(reinterpret_cast<jbyteArray>(array),
-                                 reinterpret_cast<jbyte*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release byte primitive array elements with an object of type boolean[]");
-  env_->ReleaseShortArrayElements(reinterpret_cast<jshortArray>(array),
-                                  reinterpret_cast<jshort*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release short primitive array elements with an object of type boolean[]");
-  env_->ReleaseCharArrayElements(reinterpret_cast<jcharArray>(array),
-                                 reinterpret_cast<jchar*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release char primitive array elements with an object of type boolean[]");
-  env_->ReleaseIntArrayElements(reinterpret_cast<jintArray>(array),
-                                reinterpret_cast<jint*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release int primitive array elements with an object of type boolean[]");
-  env_->ReleaseLongArrayElements(reinterpret_cast<jlongArray>(array),
-                                 reinterpret_cast<jlong*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release long primitive array elements with an object of type boolean[]");
-  env_->ReleaseFloatArrayElements(reinterpret_cast<jfloatArray>(array),
-                                  reinterpret_cast<jfloat*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release float primitive array elements with an object of type boolean[]");
-  env_->ReleaseDoubleArrayElements(reinterpret_cast<jdoubleArray>(array),
-                                  reinterpret_cast<jdouble*>(elements), 0);
-  jni_abort_catcher.Check(
-      "attempt to release double primitive array elements with an object of type boolean[]");
-  jbyteArray array2 = env_->NewByteArray(10);
-  env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(array2), elements, 0);
-  jni_abort_catcher.Check(
-      "attempt to release boolean primitive array elements with an object of type byte[]");
-  jobject object = env_->NewStringUTF("Test String");
-  env_->ReleaseBooleanArrayElements(reinterpret_cast<jbooleanArray>(object), elements, 0);
-  jni_abort_catcher.Check(
-      "attempt to release boolean primitive array elements with an object of type "
-      "java.lang.String");
+  ReleasePrimitiveArrayElementsOfWrongType(false);
+  ReleasePrimitiveArrayElementsOfWrongType(true);
 }
+
 TEST_F(JniInternalTest, GetReleasePrimitiveArrayCriticalOfWrongType) {
-  CheckJniAbortCatcher jni_abort_catcher;
-  jobject object = env_->NewStringUTF("Test String");
-  jboolean is_copy;
-  void* elements = env_->GetPrimitiveArrayCritical(reinterpret_cast<jarray>(object), &is_copy);
-  jni_abort_catcher.Check("expected primitive array, given java.lang.String");
-  env_->ReleasePrimitiveArrayCritical(reinterpret_cast<jarray>(object), elements, 0);
-  jni_abort_catcher.Check("expected primitive array, given java.lang.String");
+  GetReleasePrimitiveArrayCriticalOfWrongType(false);
+  GetReleasePrimitiveArrayCriticalOfWrongType(true);
 }
 
 TEST_F(JniInternalTest, GetPrimitiveArrayRegionElementsOfWrongType) {
-  CheckJniAbortCatcher jni_abort_catcher;
-  constexpr size_t kLength = 10;
-  jbooleanArray array = env_->NewBooleanArray(kLength);
-  ASSERT_TRUE(array != nullptr);
-  jboolean elements[kLength];
-  env_->GetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength,
-                           reinterpret_cast<jbyte*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of byte primitive array elements with an object of type boolean[]");
-  env_->GetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength,
-                            reinterpret_cast<jshort*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of short primitive array elements with an object of type boolean[]");
-  env_->GetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength,
-                           reinterpret_cast<jchar*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of char primitive array elements with an object of type boolean[]");
-  env_->GetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength,
-                          reinterpret_cast<jint*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of int primitive array elements with an object of type boolean[]");
-  env_->GetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength,
-                           reinterpret_cast<jlong*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of long primitive array elements with an object of type boolean[]");
-  env_->GetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength,
-                            reinterpret_cast<jfloat*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of float primitive array elements with an object of type boolean[]");
-  env_->GetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength,
-                           reinterpret_cast<jdouble*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of double primitive array elements with an object of type boolean[]");
-  jbyteArray array2 = env_->NewByteArray(10);
-  env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength,
-                              reinterpret_cast<jboolean*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of boolean primitive array elements with an object of type byte[]");
-  jobject object = env_->NewStringUTF("Test String");
-  env_->GetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength,
-                              reinterpret_cast<jboolean*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to get region of boolean primitive array elements with an object of type "
-      "java.lang.String");
+  GetPrimitiveArrayRegionElementsOfWrongType(false);
+  GetPrimitiveArrayRegionElementsOfWrongType(true);
 }
 
 TEST_F(JniInternalTest, SetPrimitiveArrayRegionElementsOfWrongType) {
-  CheckJniAbortCatcher jni_abort_catcher;
-  constexpr size_t kLength = 10;
-  jbooleanArray array = env_->NewBooleanArray(kLength);
-  ASSERT_TRUE(array != nullptr);
-  jboolean elements[kLength];
-  env_->SetByteArrayRegion(reinterpret_cast<jbyteArray>(array), 0, kLength,
-                           reinterpret_cast<jbyte*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of byte primitive array elements with an object of type boolean[]");
-  env_->SetShortArrayRegion(reinterpret_cast<jshortArray>(array), 0, kLength,
-                            reinterpret_cast<jshort*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of short primitive array elements with an object of type boolean[]");
-  env_->SetCharArrayRegion(reinterpret_cast<jcharArray>(array), 0, kLength,
-                           reinterpret_cast<jchar*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of char primitive array elements with an object of type boolean[]");
-  env_->SetIntArrayRegion(reinterpret_cast<jintArray>(array), 0, kLength,
-                          reinterpret_cast<jint*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of int primitive array elements with an object of type boolean[]");
-  env_->SetLongArrayRegion(reinterpret_cast<jlongArray>(array), 0, kLength,
-                           reinterpret_cast<jlong*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of long primitive array elements with an object of type boolean[]");
-  env_->SetFloatArrayRegion(reinterpret_cast<jfloatArray>(array), 0, kLength,
-                            reinterpret_cast<jfloat*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of float primitive array elements with an object of type boolean[]");
-  env_->SetDoubleArrayRegion(reinterpret_cast<jdoubleArray>(array), 0, kLength,
-                           reinterpret_cast<jdouble*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of double primitive array elements with an object of type boolean[]");
-  jbyteArray array2 = env_->NewByteArray(10);
-  env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(array2), 0, kLength,
-                              reinterpret_cast<jboolean*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of boolean primitive array elements with an object of type byte[]");
-  jobject object = env_->NewStringUTF("Test String");
-  env_->SetBooleanArrayRegion(reinterpret_cast<jbooleanArray>(object), 0, kLength,
-                              reinterpret_cast<jboolean*>(elements));
-  jni_abort_catcher.Check(
-      "attempt to set region of boolean primitive array elements with an object of type "
-      "java.lang.String");
+  SetPrimitiveArrayRegionElementsOfWrongType(false);
+  SetPrimitiveArrayRegionElementsOfWrongType(true);
 }
 
 TEST_F(JniInternalTest, NewObjectArray) {
@@ -857,12 +1100,8 @@
   EXPECT_TRUE(env_->IsSameObject(env_->GetObjectArrayElement(a, 0), nullptr));
 
   // Negative array length checks.
-  CheckJniAbortCatcher jni_abort_catcher;
-  env_->NewObjectArray(-1, element_class, nullptr);
-  jni_abort_catcher.Check("negative array length: -1");
-
-  env_->NewObjectArray(std::numeric_limits<jint>::min(), element_class, nullptr);
-  jni_abort_catcher.Check("negative array length: -2147483648");
+  NewObjectArrayBadArguments(false);
+  NewObjectArrayBadArguments(true);
 }
 
 TEST_F(JniInternalTest, NewObjectArrayWithPrimitiveClasses) {
@@ -872,6 +1111,7 @@
   };
   ASSERT_EQ(strlen(primitive_descriptors), arraysize(primitive_names));
 
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
   CheckJniAbortCatcher jni_abort_catcher;
   for (size_t i = 0; i < strlen(primitive_descriptors); ++i) {
     env_->NewObjectArray(0, nullptr, nullptr);
@@ -881,6 +1121,16 @@
     std::string error_msg(StringPrintf("not an object type: %s", primitive_names[i]));
     jni_abort_catcher.Check(error_msg.c_str());
   }
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  for (size_t i = 0; i < strlen(primitive_descriptors); ++i) {
+    env_->NewObjectArray(0, nullptr, nullptr);
+    jni_abort_catcher.Check("NewObjectArray received NULL jclass");
+    jclass primitive_class = GetPrimitiveClass(primitive_descriptors[i]);
+    env_->NewObjectArray(1, primitive_class, nullptr);
+    std::string error_msg(StringPrintf("not an object type: %s", primitive_names[i]));
+    jni_abort_catcher.Check(error_msg.c_str());
+  }
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 TEST_F(JniInternalTest, NewObjectArrayWithInitialValue) {
@@ -940,8 +1190,13 @@
 
   // Null as class should fail.
   CheckJniAbortCatcher jni_abort_catcher;
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
   EXPECT_EQ(env_->GetSuperclass(nullptr), nullptr);
   jni_abort_catcher.Check("java_class == null");
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  EXPECT_EQ(env_->GetSuperclass(nullptr), nullptr);
+  jni_abort_catcher.Check("GetSuperclass received NULL jclass");
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 TEST_F(JniInternalTest, IsAssignableFrom) {
@@ -975,10 +1230,17 @@
 
   // Null as either class should fail.
   CheckJniAbortCatcher jni_abort_catcher;
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
   EXPECT_EQ(env_->IsAssignableFrom(nullptr, string_class), JNI_FALSE);
   jni_abort_catcher.Check("java_class1 == null");
   EXPECT_EQ(env_->IsAssignableFrom(object_class, nullptr), JNI_FALSE);
   jni_abort_catcher.Check("java_class2 == null");
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  EXPECT_EQ(env_->IsAssignableFrom(nullptr, string_class), JNI_FALSE);
+  jni_abort_catcher.Check("IsAssignableFrom received NULL jclass");
+  EXPECT_EQ(env_->IsAssignableFrom(object_class, nullptr), JNI_FALSE);
+  jni_abort_catcher.Check("IsAssignableFrom received NULL jclass");
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 TEST_F(JniInternalTest, GetObjectRefType) {
@@ -1063,10 +1325,17 @@
 
 TEST_F(JniInternalTest, NewStringNegativeLength) {
   CheckJniAbortCatcher jni_abort_catcher;
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
   env_->NewString(nullptr, -1);
   jni_abort_catcher.Check("char_count < 0: -1");
   env_->NewString(nullptr, std::numeric_limits<jint>::min());
   jni_abort_catcher.Check("char_count < 0: -2147483648");
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  env_->NewString(nullptr, -1);
+  jni_abort_catcher.Check("negative jsize: -1");
+  env_->NewString(nullptr, std::numeric_limits<jint>::min());
+  jni_abort_catcher.Check("negative jsize: -2147483648");
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 TEST_F(JniInternalTest, GetStringLength_GetStringUTFLength) {
@@ -1124,10 +1393,17 @@
 
 TEST_F(JniInternalTest, GetStringUTFChars_ReleaseStringUTFChars) {
   // Passing in a nullptr jstring is ignored normally, but caught by -Xcheck:jni.
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
   {
     CheckJniAbortCatcher check_jni_abort_catcher;
     EXPECT_EQ(env_->GetStringUTFChars(nullptr, nullptr), nullptr);
-    check_jni_abort_catcher.Check("GetStringUTFChars received null jstring");
+  }
+  {
+    CheckJniAbortCatcher check_jni_abort_catcher;
+    EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+    EXPECT_EQ(env_->GetStringUTFChars(nullptr, nullptr), nullptr);
+    check_jni_abort_catcher.Check("GetStringUTFChars received NULL jstring");
+    EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
   }
 
   jstring s = env_->NewStringUTF("hello");
@@ -1222,10 +1498,17 @@
 
   // Null as array should fail.
   CheckJniAbortCatcher jni_abort_catcher;
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
   EXPECT_EQ(nullptr, env_->GetObjectArrayElement(nullptr, 0));
   jni_abort_catcher.Check("java_array == null");
   env_->SetObjectArrayElement(nullptr, 0, nullptr);
   jni_abort_catcher.Check("java_array == null");
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  EXPECT_EQ(nullptr, env_->GetObjectArrayElement(nullptr, 0));
+  jni_abort_catcher.Check("jarray was NULL");
+  env_->SetObjectArrayElement(nullptr, 0, nullptr);
+  jni_abort_catcher.Check("jarray was NULL");
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 #define EXPECT_STATIC_PRIMITIVE_FIELD(type, field_name, sig, value1, value2) \
@@ -1237,15 +1520,28 @@
     env_->SetStatic ## type ## Field(c, fid, value2); \
     EXPECT_EQ(value2, env_->GetStatic ## type ## Field(c, fid)); \
     \
+    bool old_check_jni = vm_->SetCheckJniEnabled(false); \
+    { \
+      CheckJniAbortCatcher jni_abort_catcher; \
+      env_->GetStatic ## type ## Field(nullptr, fid); \
+      env_->SetStatic ## type ## Field(nullptr, fid, value1); \
+    } \
     CheckJniAbortCatcher jni_abort_catcher; \
-    env_->GetStatic ## type ## Field(nullptr, fid); \
-    jni_abort_catcher.Check("received null jclass"); \
-    env_->SetStatic ## type ## Field(nullptr, fid, value1); \
-    jni_abort_catcher.Check("received null jclass"); \
     env_->GetStatic ## type ## Field(c, nullptr); \
     jni_abort_catcher.Check("fid == null"); \
     env_->SetStatic ## type ## Field(c, nullptr, value1); \
     jni_abort_catcher.Check("fid == null"); \
+    \
+    EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); \
+    env_->GetStatic ## type ## Field(nullptr, fid); \
+    jni_abort_catcher.Check("received NULL jclass"); \
+    env_->SetStatic ## type ## Field(nullptr, fid, value1); \
+    jni_abort_catcher.Check("received NULL jclass"); \
+    env_->GetStatic ## type ## Field(c, nullptr); \
+    jni_abort_catcher.Check("jfieldID was NULL"); \
+    env_->SetStatic ## type ## Field(c, nullptr, value1); \
+    jni_abort_catcher.Check("jfieldID was NULL"); \
+    EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); \
   } while (false)
 
 #define EXPECT_PRIMITIVE_FIELD(instance, type, field_name, sig, value1, value2) \
@@ -1257,6 +1553,7 @@
     env_->Set ## type ## Field(instance, fid, value2); \
     EXPECT_EQ(value2, env_->Get ## type ## Field(instance, fid)); \
     \
+    bool old_check_jni = vm_->SetCheckJniEnabled(false); \
     CheckJniAbortCatcher jni_abort_catcher; \
     env_->Get ## type ## Field(nullptr, fid); \
     jni_abort_catcher.Check("obj == null"); \
@@ -1266,6 +1563,16 @@
     jni_abort_catcher.Check("fid == null"); \
     env_->Set ## type ## Field(instance, nullptr, value1); \
     jni_abort_catcher.Check("fid == null"); \
+    EXPECT_FALSE(vm_->SetCheckJniEnabled(true)); \
+    env_->Get ## type ## Field(nullptr, fid); \
+    jni_abort_catcher.Check("field operation on NULL object:"); \
+    env_->Set ## type ## Field(nullptr, fid, value1); \
+    jni_abort_catcher.Check("field operation on NULL object:"); \
+    env_->Get ## type ## Field(instance, nullptr); \
+    jni_abort_catcher.Check("jfieldID was NULL"); \
+    env_->Set ## type ## Field(instance, nullptr, value1); \
+    jni_abort_catcher.Check("jfieldID was NULL"); \
+    EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni)); \
   } while (false)
 
 
@@ -1357,12 +1664,17 @@
 
   // Currently, deleting an already-deleted reference is just a CheckJNI warning.
   {
+    bool old_check_jni = vm_->SetCheckJniEnabled(false);
+    {
+      CheckJniAbortCatcher check_jni_abort_catcher;
+      env_->DeleteLocalRef(s);
+    }
     CheckJniAbortCatcher check_jni_abort_catcher;
+    EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
     env_->DeleteLocalRef(s);
-
-    std::string expected(StringPrintf("native code passing in reference to "
-                                      "invalid local reference: %p", s));
+    std::string expected(StringPrintf("jobject is an invalid local reference: %p", s));
     check_jni_abort_catcher.Check(expected.c_str());
+    EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
   }
 
   s = env_->NewStringUTF("");
@@ -1453,12 +1765,17 @@
 
   // Currently, deleting an already-deleted reference is just a CheckJNI warning.
   {
+    bool old_check_jni = vm_->SetCheckJniEnabled(false);
+    {
+      CheckJniAbortCatcher check_jni_abort_catcher;
+      env_->DeleteGlobalRef(o);
+    }
     CheckJniAbortCatcher check_jni_abort_catcher;
+    EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
     env_->DeleteGlobalRef(o);
-
-    std::string expected(StringPrintf("native code passing in reference to "
-                                      "invalid global reference: %p", o));
+    std::string expected(StringPrintf("jobject is an invalid global reference: %p", o));
     check_jni_abort_catcher.Check(expected.c_str());
+    EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
   }
 
   jobject o1 = env_->NewGlobalRef(s);
@@ -1498,12 +1815,17 @@
 
   // Currently, deleting an already-deleted reference is just a CheckJNI warning.
   {
+    bool old_check_jni = vm_->SetCheckJniEnabled(false);
+    {
+      CheckJniAbortCatcher check_jni_abort_catcher;
+      env_->DeleteWeakGlobalRef(o);
+    }
     CheckJniAbortCatcher check_jni_abort_catcher;
+    EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
     env_->DeleteWeakGlobalRef(o);
-
-    std::string expected(StringPrintf("native code passing in reference to "
-                                      "invalid weak global reference: %p", o));
+    std::string expected(StringPrintf("jobject is an invalid weak global reference: %p", o));
     check_jni_abort_catcher.Check(expected.c_str());
+    EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
   }
 
   jobject o1 = env_->NewWeakGlobalRef(s);
@@ -1522,8 +1844,6 @@
 }
 
 TEST_F(JniInternalTest, Throw) {
-  EXPECT_EQ(JNI_ERR, env_->Throw(nullptr));
-
   jclass exception_class = env_->FindClass("java/lang/RuntimeException");
   ASSERT_TRUE(exception_class != nullptr);
   jthrowable exception = reinterpret_cast<jthrowable>(env_->AllocObject(exception_class));
@@ -1534,11 +1854,18 @@
   jthrowable thrown_exception = env_->ExceptionOccurred();
   env_->ExceptionClear();
   EXPECT_TRUE(env_->IsSameObject(exception, thrown_exception));
+
+  // Bad argument.
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
+  EXPECT_EQ(JNI_ERR, env_->Throw(nullptr));
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  CheckJniAbortCatcher check_jni_abort_catcher;
+  EXPECT_EQ(JNI_ERR, env_->Throw(nullptr));
+  check_jni_abort_catcher.Check("Throw received NULL jthrowable");
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 TEST_F(JniInternalTest, ThrowNew) {
-  EXPECT_EQ(JNI_ERR, env_->Throw(nullptr));
-
   jclass exception_class = env_->FindClass("java/lang/RuntimeException");
   ASSERT_TRUE(exception_class != nullptr);
 
@@ -1555,6 +1882,16 @@
   thrown_exception = env_->ExceptionOccurred();
   env_->ExceptionClear();
   EXPECT_TRUE(env_->IsInstanceOf(thrown_exception, exception_class));
+
+  // Bad argument.
+  bool old_check_jni = vm_->SetCheckJniEnabled(false);
+  CheckJniAbortCatcher check_jni_abort_catcher;
+  EXPECT_EQ(JNI_ERR, env_->ThrowNew(nullptr, nullptr));
+  check_jni_abort_catcher.Check("c == null");
+  EXPECT_FALSE(vm_->SetCheckJniEnabled(true));
+  EXPECT_EQ(JNI_ERR, env_->ThrowNew(nullptr, nullptr));
+  check_jni_abort_catcher.Check("ThrowNew received NULL jclass");
+  EXPECT_TRUE(vm_->SetCheckJniEnabled(old_check_jni));
 }
 
 TEST_F(JniInternalTest, NewDirectBuffer_GetDirectBufferAddress_GetDirectBufferCapacity) {
diff --git a/runtime/mirror/class.h b/runtime/mirror/class.h
index 1e254fa..519685a 100644
--- a/runtime/mirror/class.h
+++ b/runtime/mirror/class.h
@@ -449,8 +449,14 @@
   bool IsObjectClass() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
     return !IsPrimitive() && GetSuperClass() == NULL;
   }
+
+  bool IsInstantiableNonArray() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+    return !IsPrimitive() && !IsInterface() && !IsAbstract() && !IsArrayClass();
+  }
+
   bool IsInstantiable() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
-    return (!IsPrimitive() && !IsInterface() && !IsAbstract()) || ((IsAbstract()) && IsArrayClass());
+    return (!IsPrimitive() && !IsInterface() && !IsAbstract()) ||
+        ((IsAbstract()) && IsArrayClass());
   }
 
   template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
diff --git a/runtime/mirror/object_test.cc b/runtime/mirror/object_test.cc
index a7ea6c9..da3c36c 100644
--- a/runtime/mirror/object_test.cc
+++ b/runtime/mirror/object_test.cc
@@ -73,7 +73,12 @@
   }
 };
 
-// Keep the assembly code in sync
+// Keep constants in sync.
+TEST_F(ObjectTest, Constants) {
+  EXPECT_EQ(kObjectReferenceSize, sizeof(mirror::HeapReference<mirror::Object>));
+}
+
+// Keep the assembly code constats in sync.
 TEST_F(ObjectTest, AsmConstants) {
   EXPECT_EQ(CLASS_OFFSET, Object::ClassOffset().Int32Value());
   EXPECT_EQ(LOCK_WORD_OFFSET, Object::MonitorOffset().Int32Value());
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index b0b64aa..a9ef8fc 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -167,7 +167,7 @@
 }
 
 static jboolean VMRuntime_isCheckJniEnabled(JNIEnv* env, jobject) {
-  return Runtime::Current()->GetJavaVM()->check_jni ? JNI_TRUE : JNI_FALSE;
+  return Runtime::Current()->GetJavaVM()->IsCheckJniEnabled() ? JNI_TRUE : JNI_FALSE;
 }
 
 static void VMRuntime_setTargetSdkVersionNative(JNIEnv*, jobject, jint target_sdk_version) {
diff --git a/runtime/native/dalvik_system_ZygoteHooks.cc b/runtime/native/dalvik_system_ZygoteHooks.cc
index 820bd04..df6055d 100644
--- a/runtime/native/dalvik_system_ZygoteHooks.cc
+++ b/runtime/native/dalvik_system_ZygoteHooks.cc
@@ -17,6 +17,7 @@
 #include <stdlib.h>
 
 #include "debugger.h"
+#include "java_vm_ext.h"
 #include "jni_internal.h"
 #include "JNIHelp.h"
 #include "thread-inl.h"
@@ -47,7 +48,7 @@
 }
 
 static void EnableDebugFeatures(uint32_t debug_flags) {
-  // Must match values in dalvik.system.Zygote.
+  // Must match values in com.android.internal.os.Zygote.
   enum {
     DEBUG_ENABLE_DEBUGGER           = 1,
     DEBUG_ENABLE_CHECKJNI           = 1 << 1,
@@ -59,7 +60,7 @@
   if ((debug_flags & DEBUG_ENABLE_CHECKJNI) != 0) {
     Runtime* runtime = Runtime::Current();
     JavaVMExt* vm = runtime->GetJavaVM();
-    if (!vm->check_jni) {
+    if (!vm->IsCheckJniEnabled()) {
       LOG(INFO) << "Late-enabling -Xcheck:jni";
       vm->SetCheckJniEnabled(true);
       // There's only one thread running at this point, so only one JNIEnv to fix up.
diff --git a/runtime/native/java_lang_DexCache.cc b/runtime/native/java_lang_DexCache.cc
index 51cd5b8..c1c6c26 100644
--- a/runtime/native/java_lang_DexCache.cc
+++ b/runtime/native/java_lang_DexCache.cc
@@ -15,6 +15,7 @@
  */
 
 #include "dex_file.h"
+#include "jni_internal.h"
 #include "mirror/dex_cache.h"
 #include "mirror/object-inl.h"
 #include "scoped_fast_native_object_access.h"
diff --git a/runtime/native/java_lang_Runtime.cc b/runtime/native/java_lang_Runtime.cc
index fb708a2..a85eec7 100644
--- a/runtime/native/java_lang_Runtime.cc
+++ b/runtime/native/java_lang_Runtime.cc
@@ -43,7 +43,10 @@
   exit(status);
 }
 
-static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader, jstring javaLdLibraryPath) {
+static jstring Runtime_nativeLoad(JNIEnv* env, jclass, jstring javaFilename, jobject javaLoader,
+                                  jstring javaLdLibraryPath) {
+  // TODO: returns NULL on success or an error message describing the failure on failure. This
+  // should be refactored in terms of suppressed exceptions.
   ScopedUtfChars filename(env, javaFilename);
   if (filename.c_str() == NULL) {
     return NULL;
@@ -64,14 +67,10 @@
     }
   }
 
-  std::string detail;
+  std::string error_msg;
   {
-    ScopedObjectAccess soa(env);
-    StackHandleScope<1> hs(soa.Self());
-    Handle<mirror::ClassLoader> classLoader(
-        hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
     JavaVMExt* vm = Runtime::Current()->GetJavaVM();
-    bool success = vm->LoadNativeLibrary(filename.c_str(), classLoader, &detail);
+    bool success = vm->LoadNativeLibrary(env, filename.c_str(), javaLoader, &error_msg);
     if (success) {
       return nullptr;
     }
@@ -79,7 +78,7 @@
 
   // Don't let a pending exception from JNI_OnLoad cause a CheckJNI issue with NewStringUTF.
   env->ExceptionClear();
-  return env->NewStringUTF(detail.c_str());
+  return env->NewStringUTF(error_msg.c_str());
 }
 
 static jlong Runtime_maxMemory(JNIEnv*, jclass) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
index 163ae20..8b2aecb 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmServer.cc
@@ -16,6 +16,7 @@
 
 #include "base/logging.h"
 #include "debugger.h"
+#include "jni_internal.h"
 #include "scoped_fast_native_object_access.h"
 #include "ScopedPrimitiveArray.h"
 
diff --git a/runtime/native_bridge.cc b/runtime/native_bridge.cc
index ad26ee4..18532e2 100644
--- a/runtime/native_bridge.cc
+++ b/runtime/native_bridge.cc
@@ -53,6 +53,17 @@
 // The symbol name exposed by native-bridge with the type of NativeBridgeCallbacks.
 static constexpr const char* kNativeBridgeInterfaceSymbol = "NativeBridgeItf";
 
+// The library name we are supposed to load.
+static std::string native_bridge_library_string = "";
+
+// Whether a native bridge is available (loaded and ready).
+static bool available = false;
+// Whether we have already initialized (or tried to).
+static bool initialized = false;
+
+struct NativeBridgeCallbacks;
+static NativeBridgeCallbacks* callbacks = nullptr;
+
 // ART interfaces to native-bridge.
 struct NativeBridgeArtCallbacks {
   // Get shorty of a Java method. The shorty is supposed to be persistent in memory.
@@ -71,7 +82,7 @@
   //   clazz [IN] Java class object.
   // Returns:
   //   number of native methods.
-  int (*getNativeMethodCount)(JNIEnv* env, jclass clazz);
+  uint32_t (*getNativeMethodCount)(JNIEnv* env, jclass clazz);
 
   // Get at most 'method_count' native methods for specified class 'clazz'. Results are outputed
   // via 'methods' [OUT]. The signature pointer in JNINativeMethod is reused as the method shorty.
@@ -83,7 +94,8 @@
   //   method_count [IN] max number of elements in methods.
   // Returns:
   //   number of method it actually wrote to methods.
-  int (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods, uint32_t method_count);
+  uint32_t (*getNativeMethods)(JNIEnv* env, jclass clazz, JNINativeMethod* methods,
+                               uint32_t method_count);
 };
 
 // Native-bridge interfaces to ART
@@ -135,52 +147,62 @@
   return mh.GetShorty();
 }
 
-static int GetNativeMethodCount(JNIEnv* env, jclass clazz) {
+static uint32_t GetNativeMethodCount(JNIEnv* env, jclass clazz) {
   if (clazz == nullptr)
     return 0;
 
   ScopedObjectAccess soa(env);
   mirror::Class* c = soa.Decode<mirror::Class*>(clazz);
 
-  size_t method_count = 0;
-  for (size_t i = 0; i < c->NumDirectMethods(); ++i) {
+  uint32_t native_method_count = 0;
+  for (uint32_t i = 0; i < c->NumDirectMethods(); ++i) {
     mirror::ArtMethod* m = c->GetDirectMethod(i);
-    if (m->IsNative())
-      method_count++;
+    if (m->IsNative()) {
+      native_method_count++;
+    }
   }
-  for (size_t i = 0; i < c->NumVirtualMethods(); ++i) {
+  for (uint32_t i = 0; i < c->NumVirtualMethods(); ++i) {
     mirror::ArtMethod* m = c->GetVirtualMethod(i);
-    if (m->IsNative())
-      method_count++;
+    if (m->IsNative()) {
+      native_method_count++;
+    }
   }
-  return method_count;
+  return native_method_count;
 }
 
-static int GetNativeMethods(JNIEnv* env, jclass clazz, JNINativeMethod* methods,
-                            uint32_t method_count) {
-  if ((clazz == nullptr) || (methods == nullptr))
+static uint32_t GetNativeMethods(JNIEnv* env, jclass clazz, JNINativeMethod* methods,
+                               uint32_t method_count) {
+  if ((clazz == nullptr) || (methods == nullptr)) {
     return 0;
-
+  }
   ScopedObjectAccess soa(env);
   mirror::Class* c = soa.Decode<mirror::Class*>(clazz);
 
-  size_t count = 0;
-  for (size_t i = 0; i < c->NumDirectMethods(); ++i) {
+  uint32_t count = 0;
+  for (uint32_t i = 0; i < c->NumDirectMethods(); ++i) {
     mirror::ArtMethod* m = c->GetDirectMethod(i);
-    if (m->IsNative() && count < method_count) {
-      methods[count].name = m->GetName();
-      methods[count].signature = m->GetShorty();
-      methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod());
-      count++;
+    if (m->IsNative()) {
+      if (count < method_count) {
+        methods[count].name = m->GetName();
+        methods[count].signature = m->GetShorty();
+        methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod());
+        count++;
+      } else {
+        LOG(WARNING) << "Output native method array too small. Skipping " << PrettyMethod(m);
+      }
     }
   }
-  for (size_t i = 0; i < c->NumVirtualMethods(); ++i) {
+  for (uint32_t i = 0; i < c->NumVirtualMethods(); ++i) {
     mirror::ArtMethod* m = c->GetVirtualMethod(i);
-    if (m->IsNative() && count < method_count) {
-      methods[count].name = m->GetName();
-      methods[count].signature = m->GetShorty();
-      methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod());
-      count++;
+    if (m->IsNative()) {
+      if (count < method_count) {
+        methods[count].name = m->GetName();
+        methods[count].signature = m->GetShorty();
+        methods[count].fnPtr = const_cast<void*>(m->GetNativeMethod());
+        count++;
+      } else {
+        LOG(WARNING) << "Output native method array too small. Skipping " << PrettyMethod(m);
+      }
     }
   }
   return count;
@@ -192,30 +214,32 @@
   GetNativeMethods
 };
 
-void NativeBridge::SetNativeBridgeLibraryString(std::string& native_bridge_library_string) {
-  native_bridge_library_string_ = native_bridge_library_string;
+void SetNativeBridgeLibraryString(const std::string& nb_library_string) {
+  native_bridge_library_string = nb_library_string;
   // TODO: when given an empty string, set initialized_ to true and available_ to false. This
   //       change is dependent on the property removal in Initialize().
 }
 
-bool NativeBridge::Initialize() {
+bool NativeBridgeInitialize() {
   if (!kNativeBridgeEnabled) {
     return false;
   }
+  // TODO: Missing annotalysis static lock ordering of DEFAULT_MUTEX_ACQUIRED, place lock into
+  // global order or remove.
+  static Mutex lock("native bridge lock");
+  MutexLock mu(Thread::Current(), lock);
 
-  MutexLock mu(Thread::Current(), lock_);
-
-  if (initialized_) {
+  if (initialized) {
     // Somebody did it before.
-    return available_;
+    return available;
   }
 
-  available_ = false;
+  available = false;
 
   const char* libnb_path;
 
-  if (!native_bridge_library_string_.empty()) {
-    libnb_path = native_bridge_library_string_.c_str();
+  if (!native_bridge_library_string.empty()) {
+    libnb_path = native_bridge_library_string.c_str();
   } else {
     // TODO: Remove this once the frameworks side is completely implemented.
 
@@ -224,7 +248,7 @@
     char prop_buf[PROP_VALUE_MAX];
     property_get(kPropEnableNativeBridge, prop_buf, "false");
     if (strcmp(prop_buf, "true") != 0) {
-      initialized_ = true;
+      initialized = true;
       return false;
     }
 
@@ -237,46 +261,43 @@
 
   void* handle = dlopen(libnb_path, RTLD_LAZY);
   if (handle != nullptr) {
-    callbacks_ = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle,
-                                                                kNativeBridgeInterfaceSymbol));
+    callbacks = reinterpret_cast<NativeBridgeCallbacks*>(dlsym(handle,
+                                                               kNativeBridgeInterfaceSymbol));
 
-    if (callbacks_ != nullptr) {
-      available_ = callbacks_->initialize(&NativeBridgeArtItf);
+    if (callbacks != nullptr) {
+      available = callbacks->initialize(&NativeBridgeArtItf);
     }
 
-    if (!available_) {
+    if (!available) {
       dlclose(handle);
     }
   }
 
-  initialized_ = true;
+  initialized = true;
 
-  return available_;
+  return available;
 }
 
-void* NativeBridge::LoadLibrary(const char* libpath, int flag) {
-  if (Initialize())
-    return callbacks_->loadLibrary(libpath, flag);
+void* NativeBridgeLoadLibrary(const char* libpath, int flag) {
+  if (NativeBridgeInitialize()) {
+    return callbacks->loadLibrary(libpath, flag);
+  }
   return nullptr;
 }
 
-void* NativeBridge::GetTrampoline(void* handle, const char* name, const char* shorty,
+void* NativeBridgeGetTrampoline(void* handle, const char* name, const char* shorty,
                                   uint32_t len) {
-  if (Initialize())
-    return callbacks_->getTrampoline(handle, name, shorty, len);
+  if (NativeBridgeInitialize()) {
+    return callbacks->getTrampoline(handle, name, shorty, len);
+  }
   return nullptr;
 }
 
-bool NativeBridge::IsSupported(const char* libpath) {
-  if (Initialize())
-    return callbacks_->isSupported(libpath);
+bool NativeBridgeIsSupported(const char* libpath) {
+  if (NativeBridgeInitialize()) {
+    return callbacks->isSupported(libpath);
+  }
   return false;
 }
 
-bool NativeBridge::available_ = false;
-bool NativeBridge::initialized_ = false;
-Mutex NativeBridge::lock_("native bridge lock");
-std::string NativeBridge::native_bridge_library_string_ = "";
-NativeBridgeCallbacks* NativeBridge::callbacks_ = nullptr;
-
 };  // namespace art
diff --git a/runtime/native_bridge.h b/runtime/native_bridge.h
index 3d20fe4..be647fc 100644
--- a/runtime/native_bridge.h
+++ b/runtime/native_bridge.h
@@ -17,42 +17,22 @@
 #ifndef ART_RUNTIME_NATIVE_BRIDGE_H_
 #define ART_RUNTIME_NATIVE_BRIDGE_H_
 
-#include "base/mutex.h"
-
 #include <string>
 
 namespace art {
 
-struct NativeBridgeCallbacks;
+// Initialize the native bridge, if any. Should be called by Runtime::Init(). An empty string
+// signals that we do not want to load a native bridge.
+void SetNativeBridgeLibraryString(const std::string& native_bridge_library_string);
 
-class NativeBridge {
- public:
-  // Initialize the native bridge, if any. Should be called by Runtime::Init(). An empty string
-  // signals that we do not want to load a native bridge.
-  static void SetNativeBridgeLibraryString(std::string& native_bridge_library_string);
+// Load a shared library that is supported by the native-bridge.
+void* NativeBridgeLoadLibrary(const char* libpath, int flag);
 
-  // Load a shared library that is supported by the native-bridge.
-  static void* LoadLibrary(const char* libpath, int flag);
-  // Get a native-bridge trampoline for specified native method.
-  static void* GetTrampoline(void* handle, const char* name, const char* shorty, uint32_t len);
-  // True if native library is valid and is for an ABI that is supported by native-bridge.
-  static bool IsSupported(const char* libpath);
+// Get a native-bridge trampoline for specified native method.
+void* NativeBridgeGetTrampoline(void* handle, const char* name, const char* shorty, uint32_t len);
 
- private:
-  static bool Initialize();
-
-  // The library name we are supposed to load.
-  static std::string native_bridge_library_string_;
-
-  // Whether we have already initialized (or tried to).
-  static bool initialized_ GUARDED_BY(lock_);
-  static Mutex lock_;
-
-  // Whether a native bridge is available (loaded and ready).
-  static bool available_;
-
-  static NativeBridgeCallbacks* callbacks_;
-};
+// True if native library is valid and is for an ABI that is supported by native-bridge.
+bool NativeBridgeIsSupported(const char* libpath);
 
 };  // namespace art
 
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index 49f6585..12f9f33 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -177,6 +177,7 @@
   }
   // -Xcheck:jni is off by default for regular builds but on by default in debug builds.
   check_jni_ = kIsDebugBuild;
+  force_copy_ = false;
 
   heap_initial_size_ = gc::Heap::kDefaultInitialSize;
   heap_maximum_size_ = gc::Heap::kDefaultMaximumSize;
@@ -300,6 +301,8 @@
       }
     } else if (StartsWith(option, "-Xcheck:jni")) {
       check_jni_ = true;
+    } else if (StartsWith(option, "-Xjniopts:forcecopy")) {
+      force_copy_ = true;
     } else if (StartsWith(option, "-Xrunjdwp:") || StartsWith(option, "-agentlib:jdwp=")) {
       std::string tail(option.substr(option[1] == 'X' ? 10 : 15));
       // TODO: move parsing logic out of Dbg
@@ -613,7 +616,6 @@
                StartsWith(option, "-Xint:") ||
                StartsWith(option, "-Xdexopt:") ||
                (option == "-Xnoquithandler") ||
-               StartsWith(option, "-Xjniopts:") ||
                StartsWith(option, "-Xjnigreflimit:") ||
                (option == "-Xgenregmap") ||
                (option == "-Xnogenregmap") ||
diff --git a/runtime/parsed_options.h b/runtime/parsed_options.h
index 3dbe26f..c328ca7 100644
--- a/runtime/parsed_options.h
+++ b/runtime/parsed_options.h
@@ -44,6 +44,7 @@
   std::string class_path_string_;
   std::string image_;
   bool check_jni_;
+  bool force_copy_;
   std::string jni_trace_;
   std::string native_bridge_library_string_;
   CompilerCallbacks* compiler_callbacks_;
diff --git a/runtime/primitive.cc b/runtime/primitive.cc
index 16ca0fe..a639f93 100644
--- a/runtime/primitive.cc
+++ b/runtime/primitive.cc
@@ -30,6 +30,7 @@
   "PrimDouble",
   "PrimVoid",
 };
+
 std::ostream& operator<<(std::ostream& os, const Primitive::Type& type) {
   int32_t int_type = static_cast<int32_t>(type);
   if (type >= Primitive::kPrimNot && type <= Primitive::kPrimVoid) {
diff --git a/runtime/primitive.h b/runtime/primitive.h
index b436bd2..a36e9cb 100644
--- a/runtime/primitive.h
+++ b/runtime/primitive.h
@@ -21,12 +21,10 @@
 
 #include "base/logging.h"
 #include "base/macros.h"
-#include "mirror/object_reference.h"
 
 namespace art {
-namespace mirror {
-class Object;
-}  // namespace mirror
+
+static constexpr size_t kObjectReferenceSize = 4;
 
 class Primitive {
  public:
@@ -79,7 +77,7 @@
       case kPrimFloat:   return 4;
       case kPrimLong:
       case kPrimDouble:  return 8;
-      case kPrimNot:     return sizeof(mirror::HeapReference<mirror::Object>);
+      case kPrimNot:     return kObjectReferenceSize;
       default:
         LOG(FATAL) << "Invalid type " << static_cast<int>(type);
         return 0;
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 758c1bb..0169ccc 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -347,7 +347,7 @@
   std::unique_ptr<uint32_t[]> large_arg_array_;
 };
 
-static void CheckMethodArguments(mirror::ArtMethod* m, uint32_t* args)
+static void CheckMethodArguments(JavaVMExt* vm, mirror::ArtMethod* m, uint32_t* args)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   const DexFile::TypeList* params = m->GetParameterTypeList();
   if (params == nullptr) {
@@ -375,11 +375,11 @@
       self->ClearException();
       ++error_count;
     } else if (!param_type->IsPrimitive()) {
-      // TODO: check primitives are in range.
       // TODO: There is a compaction bug here since GetClassFromTypeIdx can cause thread suspension,
       // this is a hard to fix problem since the args can contain Object*, we need to save and
       // restore them by using a visitor similar to the ones used in the trampoline entrypoints.
-      mirror::Object* argument = reinterpret_cast<mirror::Object*>(args[i + offset]);
+      mirror::Object* argument =
+          (reinterpret_cast<StackReference<mirror::Object>*>(&args[i + offset]))->AsMirrorPtr();
       if (argument != nullptr && !argument->InstanceOf(param_type)) {
         LOG(ERROR) << "JNI ERROR (app bug): attempt to pass an instance of "
                    << PrettyTypeOf(argument) << " as argument " << (i + 1)
@@ -388,13 +388,40 @@
       }
     } else if (param_type->IsPrimitiveLong() || param_type->IsPrimitiveDouble()) {
       offset++;
+    } else {
+      int32_t arg = static_cast<int32_t>(args[i + offset]);
+      if (param_type->IsPrimitiveBoolean()) {
+        if (arg != JNI_TRUE && arg != JNI_FALSE) {
+          LOG(ERROR) << "JNI ERROR (app bug): expected jboolean (0/1) but got value of "
+              << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get());
+          ++error_count;
+        }
+      } else if (param_type->IsPrimitiveByte()) {
+        if (arg < -128 || arg > 127) {
+          LOG(ERROR) << "JNI ERROR (app bug): expected jbyte but got value of "
+              << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get());
+          ++error_count;
+        }
+      } else if (param_type->IsPrimitiveChar()) {
+        if (args[i + offset] > 0xFFFF) {
+          LOG(ERROR) << "JNI ERROR (app bug): expected jchar but got value of "
+              << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get());
+          ++error_count;
+        }
+      } else if (param_type->IsPrimitiveShort()) {
+        if (arg < -32768 || arg > 0x7FFF) {
+          LOG(ERROR) << "JNI ERROR (app bug): expected jshort but got value of "
+              << arg << " as argument " << (i + 1) << " to " << PrettyMethod(h_m.Get());
+          ++error_count;
+        }
+      }
     }
   }
-  if (error_count > 0) {
+  if (UNLIKELY(error_count > 0)) {
     // TODO: pass the JNI function name (such as "CallVoidMethodV") through so we can call JniAbort
     // with an argument.
-    JniAbortF(nullptr, "bad arguments passed to %s (see above for details)",
-              PrettyMethod(h_m.Get()).c_str());
+    vm->JniAbortF(nullptr, "bad arguments passed to %s (see above for details)",
+                  PrettyMethod(h_m.Get()).c_str());
   }
 }
 
@@ -411,7 +438,7 @@
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
   uint32_t* args = arg_array->GetArray();
   if (UNLIKELY(soa.Env()->check_jni)) {
-    CheckMethodArguments(method, args);
+    CheckMethodArguments(soa.Vm(), method, args);
   }
   method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
 }
@@ -567,11 +594,6 @@
   return true;
 }
 
-static std::string PrettyDescriptor(Primitive::Type type) {
-  std::string descriptor_string(Primitive::Descriptor(type));
-  return PrettyDescriptor(descriptor_string);
-}
-
 bool ConvertPrimitiveValue(const ThrowLocation* throw_location, bool unbox_for_result,
                            Primitive::Type srcType, Primitive::Type dstType,
                            const JValue& src, JValue* dst) {
diff --git a/runtime/reflection.h b/runtime/reflection.h
index 2c54c06..61370c6 100644
--- a/runtime/reflection.h
+++ b/runtime/reflection.h
@@ -17,6 +17,7 @@
 #ifndef ART_RUNTIME_REFLECTION_H_
 #define ART_RUNTIME_REFLECTION_H_
 
+#include "base/mutex.h"
 #include "jni.h"
 #include "primitive.h"
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index e0c0d63..d677729 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -339,7 +339,7 @@
   ScopedObjectAccess soa(Thread::Current());
   ClassLinker* cl = Runtime::Current()->GetClassLinker();
 
-  StackHandleScope<3> hs(soa.Self());
+  StackHandleScope<2> hs(soa.Self());
   Handle<mirror::Class> class_loader_class(
       hs.NewHandle(soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_ClassLoader)));
   CHECK(cl->EnsureInitialized(class_loader_class, true, true));
@@ -349,15 +349,12 @@
   CHECK(getSystemClassLoader != NULL);
 
   JValue result = InvokeWithJValues(soa, nullptr, soa.EncodeMethod(getSystemClassLoader), nullptr);
-  Handle<mirror::ClassLoader> class_loader(
-      hs.NewHandle(down_cast<mirror::ClassLoader*>(result.GetL())));
-  CHECK(class_loader.Get() != nullptr);
   JNIEnv* env = soa.Self()->GetJniEnv();
   ScopedLocalRef<jobject> system_class_loader(env,
-                                              soa.AddLocalReference<jobject>(class_loader.Get()));
+                                              soa.AddLocalReference<jobject>(result.GetL()));
   CHECK(system_class_loader.get() != nullptr);
 
-  soa.Self()->SetClassLoaderOverride(class_loader.Get());
+  soa.Self()->SetClassLoaderOverride(system_class_loader.get());
 
   Handle<mirror::Class> thread_class(
       hs.NewHandle(soa.Decode<mirror::Class*>(WellKnownClasses::java_lang_Thread)));
@@ -368,7 +365,8 @@
   CHECK(contextClassLoader != NULL);
 
   // We can't run in a transaction yet.
-  contextClassLoader->SetObject<false>(soa.Self()->GetPeer(), class_loader.Get());
+  contextClassLoader->SetObject<false>(soa.Self()->GetPeer(),
+                                       soa.Decode<mirror::ClassLoader*>(system_class_loader.get()));
 
   return env->NewGlobalRef(system_class_loader.get());
 }
@@ -708,7 +706,7 @@
   self->ClearException();
 
   // Look for a native bridge.
-  NativeBridge::SetNativeBridgeLibraryString(options->native_bridge_library_string_);
+  SetNativeBridgeLibraryString(options->native_bridge_library_string_);
 
   VLOG(startup) << "Runtime::Init exiting";
   return true;
@@ -736,13 +734,9 @@
   {
     std::string mapped_name(StringPrintf(OS_SHARED_LIB_FORMAT_STR, "javacore"));
     std::string reason;
-    self->TransitionFromSuspendedToRunnable();
-    StackHandleScope<1> hs(self);
-    auto class_loader(hs.NewHandle<mirror::ClassLoader>(nullptr));
-    if (!instance_->java_vm_->LoadNativeLibrary(mapped_name, class_loader, &reason)) {
+    if (!instance_->java_vm_->LoadNativeLibrary(env, mapped_name, nullptr, &reason)) {
       LOG(FATAL) << "LoadNativeLibrary failed for \"" << mapped_name << "\": " << reason;
     }
-    self->TransitionFromRunnableToSuspended(kNative);
   }
 
   // Initialize well known classes that may invoke runtime native methods.
diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h
index 23aca45..ae3eaf2 100644
--- a/runtime/scoped_thread_state_change.h
+++ b/runtime/scoped_thread_state_change.h
@@ -18,7 +18,8 @@
 #define ART_RUNTIME_SCOPED_THREAD_STATE_CHANGE_H_
 
 #include "base/casts.h"
-#include "jni_internal-inl.h"
+#include "java_vm_ext.h"
+#include "jni_env_ext-inl.h"
 #include "read_barrier.h"
 #include "thread-inl.h"
 #include "verify_object.h"
@@ -114,6 +115,10 @@
     return vm_;
   }
 
+  bool ForceCopy() const {
+    return vm_->ForceCopy();
+  }
+
   /*
    * Add a local reference for an object to the indirect reference table associated with the
    * current stack frame.  When the native function returns, the reference will be discarded.
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index a5caa07..bd399e7 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -24,7 +24,7 @@
 #include "base/casts.h"
 #include "base/mutex-inl.h"
 #include "gc/heap.h"
-#include "jni_internal.h"
+#include "jni_env_ext.h"
 
 namespace art {
 
diff --git a/runtime/thread.cc b/runtime/thread.cc
index 18e28ea..8e6da74 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -1163,6 +1163,21 @@
   Thread* self = this;
   DCHECK_EQ(self, Thread::Current());
 
+  if (tlsPtr_.jni_env != nullptr) {
+    // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited.
+    tlsPtr_.jni_env->monitors.VisitRoots(MonitorExitVisitor, self, 0, kRootVMInternal);
+    // Release locally held global references which releasing may require the mutator lock.
+    if (tlsPtr_.jpeer != nullptr) {
+      // If pthread_create fails we don't have a jni env here.
+      tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.jpeer);
+      tlsPtr_.jpeer = nullptr;
+    }
+    if (tlsPtr_.class_loader_override != nullptr) {
+      tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.class_loader_override);
+      tlsPtr_.class_loader_override = nullptr;
+    }
+  }
+
   if (tlsPtr_.opeer != nullptr) {
     ScopedObjectAccess soa(self);
     // We may need to call user-supplied managed code, do this before final clean-up.
@@ -1190,22 +1205,16 @@
       ObjectLock<mirror::Object> locker(self, h_obj);
       locker.NotifyAll();
     }
+    tlsPtr_.opeer = nullptr;
   }
 
-  // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited.
-  if (tlsPtr_.jni_env != nullptr) {
-    tlsPtr_.jni_env->monitors.VisitRoots(MonitorExitVisitor, self, 0, kRootVMInternal);
-  }
+  Runtime::Current()->GetHeap()->RevokeThreadLocalBuffers(this);
 }
 
 Thread::~Thread() {
-  if (tlsPtr_.jni_env != nullptr && tlsPtr_.jpeer != nullptr) {
-    // If pthread_create fails we don't have a jni env here.
-    tlsPtr_.jni_env->DeleteGlobalRef(tlsPtr_.jpeer);
-    tlsPtr_.jpeer = nullptr;
-  }
-  tlsPtr_.opeer = nullptr;
-
+  CHECK(tlsPtr_.class_loader_override == nullptr);
+  CHECK(tlsPtr_.jpeer == nullptr);
+  CHECK(tlsPtr_.opeer == nullptr);
   bool initialized = (tlsPtr_.jni_env != nullptr);  // Did Thread::Init run?
   if (initialized) {
     delete tlsPtr_.jni_env;
@@ -1237,7 +1246,7 @@
   delete tlsPtr_.name;
   delete tlsPtr_.stack_trace_sample;
 
-  Runtime::Current()->GetHeap()->RevokeThreadLocalBuffers(this);
+  Runtime::Current()->GetHeap()->AssertThreadLocalBuffersAreRevoked(this);
 
   TearDownAlternateSignalStack();
 }
@@ -1347,11 +1356,10 @@
       result = kInvalidIndirectRefObject;
     }
   } else if (kind == kGlobal) {
-    JavaVMExt* const vm = Runtime::Current()->GetJavaVM();
-    result = vm->globals.SynchronizedGet(const_cast<Thread*>(this), &vm->globals_lock, ref);
+    result = tlsPtr_.jni_env->vm->DecodeGlobal(const_cast<Thread*>(this), ref);
   } else {
     DCHECK_EQ(kind, kWeakGlobal);
-    result = Runtime::Current()->GetJavaVM()->DecodeWeakGlobal(const_cast<Thread*>(this), ref);
+    result = tlsPtr_.jni_env->vm->DecodeWeakGlobal(const_cast<Thread*>(this), ref);
     if (result == kClearedJniWeakGlobal) {
       // This is a special case where it's okay to return nullptr.
       return nullptr;
@@ -1359,7 +1367,8 @@
   }
 
   if (UNLIKELY(result == nullptr)) {
-    JniAbortF(nullptr, "use of deleted %s %p", ToStr<IndirectRefKind>(kind).c_str(), obj);
+    tlsPtr_.jni_env->vm->JniAbortF(nullptr, "use of deleted %s %p",
+                                   ToStr<IndirectRefKind>(kind).c_str(), obj);
   }
   return result;
 }
@@ -1399,6 +1408,13 @@
   }
 }
 
+void Thread::SetClassLoaderOverride(jobject class_loader_override) {
+  if (tlsPtr_.class_loader_override != nullptr) {
+    GetJniEnv()->DeleteGlobalRef(tlsPtr_.class_loader_override);
+  }
+  tlsPtr_.class_loader_override = GetJniEnv()->NewGlobalRef(class_loader_override);
+}
+
 class CountStackDepthVisitor : public StackVisitor {
  public:
   explicit CountStackDepthVisitor(Thread* thread)
@@ -2173,11 +2189,6 @@
   const uint32_t tid_;
 };
 
-void Thread::SetClassLoaderOverride(mirror::ClassLoader* class_loader_override) {
-  VerifyObject(class_loader_override);
-  tlsPtr_.class_loader_override = class_loader_override;
-}
-
 void Thread::VisitRoots(RootCallback* visitor, void* arg) {
   uint32_t thread_id = GetThreadId();
   if (tlsPtr_.opeer != nullptr) {
@@ -2187,10 +2198,6 @@
     visitor(reinterpret_cast<mirror::Object**>(&tlsPtr_.exception), arg, thread_id, kRootNativeStack);
   }
   tlsPtr_.throw_location.VisitRoots(visitor, arg);
-  if (tlsPtr_.class_loader_override != nullptr) {
-    visitor(reinterpret_cast<mirror::Object**>(&tlsPtr_.class_loader_override), arg, thread_id,
-            kRootNativeStack);
-  }
   if (tlsPtr_.monitor_enter_object != nullptr) {
     visitor(&tlsPtr_.monitor_enter_object, arg, thread_id, kRootNativeStack);
   }
diff --git a/runtime/thread.h b/runtime/thread.h
index 3e9372f..c2b200b 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -431,12 +431,11 @@
     tlsPtr_.wait_next = next;
   }
 
-  mirror::ClassLoader* GetClassLoaderOverride() SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
+  jobject GetClassLoaderOverride() {
     return tlsPtr_.class_loader_override;
   }
 
-  void SetClassLoaderOverride(mirror::ClassLoader* class_loader_override)
-      SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+  void SetClassLoaderOverride(jobject class_loader_override);
 
   // Create the internal representation of a stack trace, that is more time
   // and space efficient to compute than the StackTraceElement[].
@@ -1040,7 +1039,7 @@
 
     // Needed to get the right ClassLoader in JNI_OnLoad, but also
     // useful for testing.
-    mirror::ClassLoader* class_loader_override;
+    jobject class_loader_override;
 
     // Thread local, lazily allocated, long jump context. Used to deliver exceptions.
     Context* long_jump_context;
diff --git a/runtime/thread_list.cc b/runtime/thread_list.cc
index 7cf26e6..5077a89 100644
--- a/runtime/thread_list.cc
+++ b/runtime/thread_list.cc
@@ -801,6 +801,8 @@
 
 void ThreadList::Unregister(Thread* self) {
   DCHECK_EQ(self, Thread::Current());
+  CHECK_NE(self->GetState(), kRunnable);
+  Locks::mutator_lock_->AssertNotHeld(self);
 
   VLOG(threads) << "ThreadList::Unregister() " << *self;
 
@@ -815,14 +817,18 @@
     // Note: deliberately not using MutexLock that could hold a stale self pointer.
     Locks::thread_list_lock_->ExclusiveLock(self);
     CHECK(Contains(self));
-    // Note: we don't take the thread_suspend_count_lock_ here as to be suspending a thread other
-    // than yourself you need to hold the thread_list_lock_ (see Thread::ModifySuspendCount).
+    Locks::thread_suspend_count_lock_->ExclusiveLock(self);
+    bool removed = false;
     if (!self->IsSuspended()) {
       list_.remove(self);
+      removed = true;
+    }
+    Locks::thread_suspend_count_lock_->ExclusiveUnlock(self);
+    Locks::thread_list_lock_->ExclusiveUnlock(self);
+    if (removed) {
       delete self;
       self = nullptr;
     }
-    Locks::thread_list_lock_->ExclusiveUnlock(self);
   }
   // Release the thread ID after the thread is finished and deleted to avoid cases where we can
   // temporarily have multiple threads with the same thread id. When this occurs, it causes
diff --git a/runtime/utils.cc b/runtime/utils.cc
index f966fbd..48d6cdf 100644
--- a/runtime/utils.cc
+++ b/runtime/utils.cc
@@ -281,6 +281,11 @@
   return result;
 }
 
+std::string PrettyDescriptor(Primitive::Type type) {
+  std::string descriptor_string(Primitive::Descriptor(type));
+  return PrettyDescriptor(descriptor_string);
+}
+
 std::string PrettyField(mirror::ArtField* f, bool with_type) {
   if (f == NULL) {
     return "null";
diff --git a/runtime/utils.h b/runtime/utils.h
index 49bcbf9..f6773be 100644
--- a/runtime/utils.h
+++ b/runtime/utils.h
@@ -24,13 +24,10 @@
 #include <vector>
 
 #include "base/logging.h"
+#include "base/mutex.h"
 #include "globals.h"
 #include "instruction_set.h"
-#include "base/mutex.h"
-
-#ifdef HAVE_ANDROID_OS
-#include "cutils/properties.h"
-#endif
+#include "primitive.h"
 
 namespace art {
 
@@ -279,6 +276,7 @@
 std::string PrettyDescriptor(const std::string& descriptor);
 std::string PrettyDescriptor(mirror::Class* klass)
     SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
+std::string PrettyDescriptor(Primitive::Type type);
 
 // Returns a human-readable signature for 'f'. Something like "a.b.C.f" or
 // "int a.b.C.f" (depending on the value of 'with_type').
diff --git a/runtime/verifier/reg_type.h b/runtime/verifier/reg_type.h
index d508fb5..1682d4e 100644
--- a/runtime/verifier/reg_type.h
+++ b/runtime/verifier/reg_type.h
@@ -25,6 +25,7 @@
 #include "jni.h"
 
 #include "base/macros.h"
+#include "base/mutex.h"
 #include "gc_root.h"
 #include "globals.h"
 #include "object_callbacks.h"