Extensions to check JNI.

Ensure critical lock isn't held when returning from a down-call.
Log a warning if the critical lock is held for a significant period of
time.
Refactor JNIEnvExt to be a class rather than a struct.

Test: mma test-art-host

Change-Id: I4d149cb04d3a7308a22b92b196e51e2f1ae17ede
diff --git a/dex2oat/dex2oat.cc b/dex2oat/dex2oat.cc
index b3d9561..f5d09de 100644
--- a/dex2oat/dex2oat.cc
+++ b/dex2oat/dex2oat.cc
@@ -1695,10 +1695,10 @@
       CHECK(class_loader != nullptr);
       ScopedObjectAccess soa(Thread::Current());
       // Unload class loader to free RAM.
-      jweak weak_class_loader = soa.Env()->vm->AddWeakGlobalRef(
+      jweak weak_class_loader = soa.Env()->GetVm()->AddWeakGlobalRef(
           soa.Self(),
           soa.Decode<mirror::ClassLoader>(class_loader));
-      soa.Env()->vm->DeleteGlobalRef(soa.Self(), class_loader);
+      soa.Env()->GetVm()->DeleteGlobalRef(soa.Self(), class_loader);
       runtime_->GetHeap()->CollectGarbage(/*clear_soft_references*/ true);
       ObjPtr<mirror::ClassLoader> decoded_weak = soa.Decode<mirror::ClassLoader>(weak_class_loader);
       if (decoded_weak != nullptr) {
@@ -2898,7 +2898,7 @@
   ~ScopedGlobalRef() {
     if (obj_ != nullptr) {
       ScopedObjectAccess soa(Thread::Current());
-      soa.Env()->vm->DeleteGlobalRef(soa.Self(), obj_);
+      soa.Env()->GetVm()->DeleteGlobalRef(soa.Self(), obj_);
     }
   }
 
diff --git a/openjdkjvm/OpenjdkJvm.cc b/openjdkjvm/OpenjdkJvm.cc
index 1b8233a..ff839f5 100644
--- a/openjdkjvm/OpenjdkJvm.cc
+++ b/openjdkjvm/OpenjdkJvm.cc
@@ -387,7 +387,7 @@
 
 JNIEXPORT jboolean JVM_IsInterrupted(JNIEnv* env, jobject jthread, jboolean clearInterrupted) {
   if (clearInterrupted) {
-    return static_cast<art::JNIEnvExt*>(env)->self->Interrupted() ? JNI_TRUE : JNI_FALSE;
+    return static_cast<art::JNIEnvExt*>(env)->GetSelf()->Interrupted() ? JNI_TRUE : JNI_FALSE;
   } else {
     art::ScopedFastNativeObjectAccess soa(env);
     art::MutexLock mu(soa.Self(), *art::Locks::thread_list_lock_);
diff --git a/openjdkjvmti/ti_class.cc b/openjdkjvmti/ti_class.cc
index e69c78b..60ab0a5 100644
--- a/openjdkjvmti/ti_class.cc
+++ b/openjdkjvmti/ti_class.cc
@@ -490,7 +490,7 @@
 
         // Fix up the local table with a root visitor.
         RootUpdater local_update(local->input_, local->output_);
-        t->GetJniEnv()->locals.VisitRoots(
+        t->GetJniEnv()->VisitJniLocalRoots(
             &local_update, art::RootInfo(art::kRootJNILocal, t->GetThreadId()));
       }
 
diff --git a/openjdkjvmti/ti_stack.cc b/openjdkjvmti/ti_stack.cc
index b43eaa0..8ee150e 100644
--- a/openjdkjvmti/ti_stack.cc
+++ b/openjdkjvmti/ti_stack.cc
@@ -891,7 +891,7 @@
     visitor.WalkStack(/* include_transitions */ false);
     // Find any other monitors, including ones acquired in native code.
     art::RootInfo root_info(art::kRootVMInternal);
-    target->GetJniEnv()->monitors.VisitRoots(&visitor, root_info);
+    target->GetJniEnv()->VisitMonitorRoots(&visitor, root_info);
     err_ = handle_results_(soa_, visitor);
   }
 
diff --git a/runtime/base/mutex.cc b/runtime/base/mutex.cc
index 7324dff..e88ed68 100644
--- a/runtime/base/mutex.cc
+++ b/runtime/base/mutex.cc
@@ -959,7 +959,7 @@
   }
   if (self != nullptr) {
     JNIEnvExt* const env = self->GetJniEnv();
-    if (UNLIKELY(env != nullptr && env->runtime_deleted)) {
+    if (UNLIKELY(env != nullptr && env->IsRuntimeDeleted())) {
       CHECK(self->IsDaemon());
       // If the runtime has been deleted, then we cannot proceed. Just sleep forever. This may
       // occur for user daemon threads that get a spurious wakeup. This occurs for test 132 with
diff --git a/runtime/base/time_utils.h b/runtime/base/time_utils.h
index 919937f..7648a10 100644
--- a/runtime/base/time_utils.h
+++ b/runtime/base/time_utils.h
@@ -76,6 +76,15 @@
   return ms * 1000 * 1000;
 }
 
+// Converts the given number of milliseconds to microseconds
+static constexpr inline uint64_t MsToUs(uint64_t ms) {
+  return ms * 1000;
+}
+
+static constexpr inline uint64_t UsToNs(uint64_t us) {
+  return us * 1000;
+}
+
 #if defined(__APPLE__)
 #ifndef CLOCK_REALTIME
 // No clocks to specify on OS/X < 10.12, fake value to pass to routines that require a clock.
diff --git a/runtime/check_jni.cc b/runtime/check_jni.cc
index 90f478f..ce4cee8 100644
--- a/runtime/check_jni.cc
+++ b/runtime/check_jni.cc
@@ -28,6 +28,7 @@
 #include "art_method-inl.h"
 #include "base/macros.h"
 #include "base/to_str.h"
+#include "base/time_utils.h"
 #include "class_linker-inl.h"
 #include "class_linker.h"
 #include "dex_file-inl.h"
@@ -55,24 +56,37 @@
  * ===========================================================================
  */
 
+// Warn if a JNI critical is held for longer than 16ms.
+static constexpr uint64_t kCriticalWarnTimeUs = MsToUs(16);
+static_assert(kCriticalWarnTimeUs > 0, "No JNI critical warn time set");
+
 // Flags passed into ScopedCheck.
-#define kFlag_Default       0x0000
+static constexpr uint16_t kFlag_Default = 0x0000;
 
-#define kFlag_CritBad       0x0000      // Calling while in critical is not allowed.
-#define kFlag_CritOkay      0x0001      // Calling while in critical is allowed.
-#define kFlag_CritGet       0x0002      // This is a critical "get".
-#define kFlag_CritRelease   0x0003      // This is a critical "release".
-#define kFlag_CritMask      0x0003      // Bit mask to get "crit" value.
+// Calling while in critical is not allowed.
+static constexpr uint16_t kFlag_CritBad = 0x0000;
+// Calling while in critical is allowed.
+static constexpr uint16_t kFlag_CritOkay = 0x0001;
+// This is a critical "get".
+static constexpr uint16_t kFlag_CritGet = 0x0002;
+// This is a critical "release".
+static constexpr uint16_t kFlag_CritRelease = 0x0003;
+// Bit mask to get "crit" value.
+static constexpr uint16_t kFlag_CritMask = 0x0003;
 
-#define kFlag_ExcepBad      0x0000      // Raised exceptions are not allowed.
-#define kFlag_ExcepOkay     0x0004      // Raised exceptions are allowed.
+// Raised exceptions are allowed.
+static constexpr uint16_t kFlag_ExcepOkay = 0x0004;
 
-#define kFlag_Release       0x0010      // Are we in a non-critical release function?
-#define kFlag_NullableUtf   0x0020      // Are our UTF parameters nullable?
+// Are we in a non-critical release function?
+static constexpr uint16_t kFlag_Release = 0x0010;
+// Are our UTF parameters nullable?
+static constexpr uint16_t kFlag_NullableUtf = 0x0020;
 
-#define kFlag_Invocation    0x8000      // Part of the invocation interface (JavaVM*).
+// Part of the invocation interface (JavaVM*).
+static constexpr uint16_t kFlag_Invocation = 0x0100;
 
-#define kFlag_ForceTrace    0x80000000  // Add this to a JNI function's flags if you want to trace every call.
+// Add this to a JNI function's flags if you want to trace every call.
+static constexpr uint16_t kFlag_ForceTrace = 0x8000;
 
 class VarArgs;
 /*
@@ -249,8 +263,8 @@
 
 class ScopedCheck {
  public:
-  ScopedCheck(int flags, const char* functionName, bool has_method = true)
-      : function_name_(functionName), flags_(flags), indent_(0), has_method_(has_method) {
+  ScopedCheck(uint16_t flags, const char* functionName, bool has_method = true)
+      : function_name_(functionName), indent_(0), flags_(flags), has_method_(has_method) {
   }
 
   ~ScopedCheck() {}
@@ -1197,7 +1211,7 @@
     // this particular instance of JNIEnv.
     if (env != threadEnv) {
       // Get the thread owning the JNIEnv that's being used.
-      Thread* envThread = reinterpret_cast<JNIEnvExt*>(env)->self;
+      Thread* envThread = reinterpret_cast<JNIEnvExt*>(env)->self_;
       AbortF("thread %s using JNIEnv* from thread %s",
              ToStr<Thread>(*self).c_str(), ToStr<Thread>(*envThread).c_str());
       return false;
@@ -1209,7 +1223,7 @@
     case kFlag_CritOkay:    // okay to call this method
       break;
     case kFlag_CritBad:     // not okay to call
-      if (threadEnv->critical) {
+      if (threadEnv->critical_ > 0) {
         AbortF("thread %s using JNI after critical get",
                ToStr<Thread>(*self).c_str());
         return false;
@@ -1217,15 +1231,25 @@
       break;
     case kFlag_CritGet:     // this is a "get" call
       // Don't check here; we allow nested gets.
-      threadEnv->critical++;
+      if (threadEnv->critical_ == 0) {
+        threadEnv->critical_start_us_ = self->GetCpuMicroTime();
+      }
+      threadEnv->critical_++;
       break;
     case kFlag_CritRelease:  // this is a "release" call
-      threadEnv->critical--;
-      if (threadEnv->critical < 0) {
+      if (threadEnv->critical_ == 0) {
         AbortF("thread %s called too many critical releases",
                ToStr<Thread>(*self).c_str());
         return false;
+      } else if (threadEnv->critical_ == 1) {
+        // Leaving the critical region, possibly warn about long critical regions.
+        uint64_t critical_duration_us = self->GetCpuMicroTime() - threadEnv->critical_start_us_;
+        if (critical_duration_us > kCriticalWarnTimeUs) {
+          LOG(WARNING) << "JNI critical lock held for "
+                       << PrettyDuration(UsToNs(critical_duration_us)) << " on " << *self;
+        }
       }
+      threadEnv->critical_--;
       break;
     default:
       LOG(FATAL) << "Bad flags (internal error): " << flags_;
@@ -1357,9 +1381,10 @@
   // The name of the JNI function being checked.
   const char* const function_name_;
 
-  const int flags_;
   int indent_;
 
+  const uint16_t flags_;
+
   const bool has_method_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedCheck);
@@ -2592,11 +2617,11 @@
 
  private:
   static JavaVMExt* GetJavaVMExt(JNIEnv* env) {
-    return reinterpret_cast<JNIEnvExt*>(env)->vm;
+    return reinterpret_cast<JNIEnvExt*>(env)->GetVm();
   }
 
   static const JNINativeInterface* baseEnv(JNIEnv* env) {
-    return reinterpret_cast<JNIEnvExt*>(env)->unchecked_functions;
+    return reinterpret_cast<JNIEnvExt*>(env)->unchecked_functions_;
   }
 
   static jobject NewRef(const char* function_name, JNIEnv* env, jobject obj, IndirectRefKind kind) {
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index 55fa632..ae285c7 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -3391,7 +3391,7 @@
   // Clean up pass to remove null dex caches. Also check if we need to initialize OatFile .bss.
   // Null dex caches can occur due to class unloading and we are lazily removing null entries.
   bool initialize_oat_file_bss = (oat_file != nullptr);
-  JavaVMExt* const vm = self->GetJniEnv()->vm;
+  JavaVMExt* const vm = self->GetJniEnv()->GetVm();
   for (auto it = dex_caches_.begin(); it != dex_caches_.end(); ) {
     DexCacheData data = *it;
     if (self->IsJWeakCleared(data.weak_root)) {
@@ -5269,7 +5269,7 @@
   CHECK(class_loader->GetClassTable() == nullptr);
   Thread* const self = Thread::Current();
   ClassLoaderData data;
-  data.weak_root = self->GetJniEnv()->vm->AddWeakGlobalRef(self, class_loader);
+  data.weak_root = self->GetJniEnv()->GetVm()->AddWeakGlobalRef(self, class_loader);
   // Create and set the class table.
   data.class_table = new ClassTable;
   class_loader->SetClassTable(data.class_table);
diff --git a/runtime/entrypoints/quick/quick_jni_entrypoints.cc b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
index b13b6fb..3c41a8c 100644
--- a/runtime/entrypoints/quick/quick_jni_entrypoints.cc
+++ b/runtime/entrypoints/quick/quick_jni_entrypoints.cc
@@ -51,8 +51,8 @@
 extern uint32_t JniMethodFastStart(Thread* self) {
   JNIEnvExt* env = self->GetJniEnv();
   DCHECK(env != nullptr);
-  uint32_t saved_local_ref_cookie = bit_cast<uint32_t>(env->local_ref_cookie);
-  env->local_ref_cookie = env->locals.GetSegmentState();
+  uint32_t saved_local_ref_cookie = bit_cast<uint32_t>(env->GetLocalRefCookie());
+  env->SetLocalRefCookie(env->GetLocalsSegmentState());
 
   if (kIsDebugBuild) {
     ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame();
@@ -66,8 +66,8 @@
 extern uint32_t JniMethodStart(Thread* self) {
   JNIEnvExt* env = self->GetJniEnv();
   DCHECK(env != nullptr);
-  uint32_t saved_local_ref_cookie = bit_cast<uint32_t>(env->local_ref_cookie);
-  env->local_ref_cookie = env->locals.GetSegmentState();
+  uint32_t saved_local_ref_cookie = bit_cast<uint32_t>(env->GetLocalRefCookie());
+  env->SetLocalRefCookie(env->GetLocalsSegmentState());
   ArtMethod* native_method = *self->GetManagedStack()->GetTopQuickFrame();
   // TODO: Introduce special entrypoint for synchronized @FastNative methods?
   //       Or ban synchronized @FastNative outright to avoid the extra check here?
@@ -115,11 +115,11 @@
 static void PopLocalReferences(uint32_t saved_local_ref_cookie, Thread* self)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   JNIEnvExt* env = self->GetJniEnv();
-  if (UNLIKELY(env->check_jni)) {
+  if (UNLIKELY(env->IsCheckJniEnabled())) {
     env->CheckNoHeldMonitors();
   }
-  env->locals.SetSegmentState(env->local_ref_cookie);
-  env->local_ref_cookie = bit_cast<IRTSegmentState>(saved_local_ref_cookie);
+  env->SetLocalSegmentState(env->GetLocalRefCookie());
+  env->SetLocalRefCookie(bit_cast<IRTSegmentState>(saved_local_ref_cookie));
   self->PopHandleScope();
 }
 
@@ -156,7 +156,7 @@
   }
   PopLocalReferences(saved_local_ref_cookie, self);
   // Process result.
-  if (UNLIKELY(self->GetJniEnv()->check_jni)) {
+  if (UNLIKELY(self->GetJniEnv()->IsCheckJniEnabled())) {
     // CheckReferenceResult can resolve types.
     StackHandleScope<1> hs(self);
     HandleWrapperObjPtr<mirror::Object> h_obj(hs.NewHandleWrapper(&o));
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index f7be4c8..3ba12ca 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -1299,7 +1299,7 @@
   explicit TrimIndirectReferenceTableClosure(Barrier* barrier) : barrier_(barrier) {
   }
   virtual void Run(Thread* thread) OVERRIDE NO_THREAD_SAFETY_ANALYSIS {
-    thread->GetJniEnv()->locals.Trim();
+    thread->GetJniEnv()->TrimLocals();
     // If thread is a running mutator, then act on behalf of the trim thread.
     // See the code in ThreadList::RunCheckpoint.
     barrier_->Pass(Thread::Current());
diff --git a/runtime/gc/reference_processor.cc b/runtime/gc/reference_processor.cc
index d58d09c..c59642f 100644
--- a/runtime/gc/reference_processor.cc
+++ b/runtime/gc/reference_processor.cc
@@ -273,7 +273,7 @@
       jobject cleared_references;
       {
         ReaderMutexLock mu(self, *Locks::mutator_lock_);
-        cleared_references = self->GetJniEnv()->vm->AddGlobalRef(
+        cleared_references = self->GetJniEnv()->GetVm()->AddGlobalRef(
             self, cleared_references_.GetList());
       }
       if (kAsyncReferenceQueueAdd) {
diff --git a/runtime/indirect_reference_table.cc b/runtime/indirect_reference_table.cc
index 2c8ec47..5187831 100644
--- a/runtime/indirect_reference_table.cc
+++ b/runtime/indirect_reference_table.cc
@@ -357,7 +357,7 @@
     if (self->HandleScopeContains(reinterpret_cast<jobject>(iref))) {
       auto* env = self->GetJniEnv();
       DCHECK(env != nullptr);
-      if (env->check_jni) {
+      if (env->IsCheckJniEnabled()) {
         ScopedObjectAccess soa(self);
         LOG(WARNING) << "Attempt to remove non-JNI local reference, dumping thread";
         if (kDumpStackOnNonLocalReference) {
diff --git a/runtime/java_vm_ext.cc b/runtime/java_vm_ext.cc
index f8b82ed..104fb66 100644
--- a/runtime/java_vm_ext.cc
+++ b/runtime/java_vm_ext.cc
@@ -337,7 +337,7 @@
       } else {
         VLOG(jni) << "[JNI_OnUnload found for \"" << library->GetPath() << "\"]: Calling...";
         JNI_OnUnloadFn jni_on_unload = reinterpret_cast<JNI_OnUnloadFn>(sym);
-        jni_on_unload(self->GetJniEnv()->vm, nullptr);
+        jni_on_unload(self->GetJniEnv()->GetVm(), nullptr);
       }
       delete library;
     }
diff --git a/runtime/jni_env_ext-inl.h b/runtime/jni_env_ext-inl.h
index d66df08..14f708b 100644
--- a/runtime/jni_env_ext-inl.h
+++ b/runtime/jni_env_ext-inl.h
@@ -26,7 +26,7 @@
 template<typename T>
 inline T JNIEnvExt::AddLocalReference(ObjPtr<mirror::Object> obj) {
   std::string error_msg;
-  IndirectRef ref = locals.Add(local_ref_cookie, obj, &error_msg);
+  IndirectRef ref = locals_.Add(local_ref_cookie_, obj, &error_msg);
   if (UNLIKELY(ref == nullptr)) {
     // This is really unexpected if we allow resizing local IRTs...
     LOG(FATAL) << error_msg;
@@ -35,10 +35,10 @@
 
   // TODO: fix this to understand PushLocalFrame, so we can turn it on.
   if (false) {
-    if (check_jni) {
-      size_t entry_count = locals.Capacity();
+    if (check_jni_) {
+      size_t entry_count = locals_.Capacity();
       if (entry_count > 16) {
-        locals.Dump(LOG_STREAM(WARNING) << "Warning: more than 16 JNI local references: "
+        locals_.Dump(LOG_STREAM(WARNING) << "Warning: more than 16 JNI local references: "
                                         << entry_count << " (most recent was a "
                                         << mirror::Object::PrettyTypeOf(obj) << ")\n");
       // TODO: LOG(FATAL) in a later release?
diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc
index 8352657..efe43ee 100644
--- a/runtime/jni_env_ext.cc
+++ b/runtime/jni_env_ext.cc
@@ -21,6 +21,7 @@
 
 #include "android-base/stringprintf.h"
 
+#include "base/to_str.h"
 #include "check_jni.h"
 #include "indirect_reference_table.h"
 #include "java_vm_ext.h"
@@ -28,6 +29,7 @@
 #include "lock_word.h"
 #include "mirror/object-inl.h"
 #include "nth_caller_visitor.h"
+#include "scoped_thread_state_change.h"
 #include "thread-current-inl.h"
 #include "thread_list.h"
 
@@ -40,14 +42,11 @@
 
 const JNINativeInterface* JNIEnvExt::table_override_ = nullptr;
 
-// Checking "locals" requires the mutator lock, but at creation time we're really only interested
-// in validity, which isn't changing. To avoid grabbing the mutator lock, factored out and tagged
-// with NO_THREAD_SAFETY_ANALYSIS.
-static bool CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS {
+bool JNIEnvExt::CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS {
   if (in == nullptr) {
     return false;
   }
-  return in->locals.IsValid();
+  return in->locals_.IsValid();
 }
 
 jint JNIEnvExt::GetEnvHandler(JavaVMExt* vm, /*out*/void** env, jint version) {
@@ -73,23 +72,23 @@
 }
 
 JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in, std::string* error_msg)
-    : self(self_in),
-      vm(vm_in),
-      local_ref_cookie(kIRTFirstSegment),
-      locals(kLocalsInitial, kLocal, IndirectReferenceTable::ResizableCapacity::kYes, error_msg),
-      check_jni(false),
-      runtime_deleted(false),
-      critical(0),
-      monitors("monitors", kMonitorsInitial, kMonitorsMax) {
+    : self_(self_in),
+      vm_(vm_in),
+      local_ref_cookie_(kIRTFirstSegment),
+      locals_(kLocalsInitial, kLocal, IndirectReferenceTable::ResizableCapacity::kYes, error_msg),
+      monitors_("monitors", kMonitorsInitial, kMonitorsMax),
+      critical_(0),
+      check_jni_(false),
+      runtime_deleted_(false) {
   MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_);
-  check_jni = vm->IsCheckJniEnabled();
-  functions = GetFunctionTable(check_jni);
-  unchecked_functions = GetJniNativeInterface();
+  check_jni_ = vm_in->IsCheckJniEnabled();
+  functions = GetFunctionTable(check_jni_);
+  unchecked_functions_ = GetJniNativeInterface();
 }
 
 void JNIEnvExt::SetFunctionsToRuntimeShutdownFunctions() {
   functions = GetRuntimeShutdownNativeInterface();
-  runtime_deleted = true;
+  runtime_deleted_ = true;
 }
 
 JNIEnvExt::~JNIEnvExt() {
@@ -100,7 +99,7 @@
     return nullptr;
   }
   std::string error_msg;
-  jobject ref = reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj, &error_msg));
+  jobject ref = reinterpret_cast<jobject>(locals_.Add(local_ref_cookie_, obj, &error_msg));
   if (UNLIKELY(ref == nullptr)) {
     // This is really unexpected if we allow resizing local IRTs...
     LOG(FATAL) << error_msg;
@@ -111,12 +110,12 @@
 
 void JNIEnvExt::DeleteLocalRef(jobject obj) {
   if (obj != nullptr) {
-    locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj));
+    locals_.Remove(local_ref_cookie_, reinterpret_cast<IndirectRef>(obj));
   }
 }
 
 void JNIEnvExt::SetCheckJniEnabled(bool enabled) {
-  check_jni = enabled;
+  check_jni_ = enabled;
   MutexLock mu(Thread::Current(), *Locks::jni_function_table_lock_);
   functions = GetFunctionTable(enabled);
   // Check whether this is a no-op because of override.
@@ -126,20 +125,20 @@
 }
 
 void JNIEnvExt::DumpReferenceTables(std::ostream& os) {
-  locals.Dump(os);
-  monitors.Dump(os);
+  locals_.Dump(os);
+  monitors_.Dump(os);
 }
 
 void JNIEnvExt::PushFrame(int capacity) {
-  DCHECK_GE(locals.FreeCapacity(), static_cast<size_t>(capacity));
-  stacked_local_ref_cookies.push_back(local_ref_cookie);
-  local_ref_cookie = locals.GetSegmentState();
+  DCHECK_GE(locals_.FreeCapacity(), static_cast<size_t>(capacity));
+  stacked_local_ref_cookies_.push_back(local_ref_cookie_);
+  local_ref_cookie_ = locals_.GetSegmentState();
 }
 
 void JNIEnvExt::PopFrame() {
-  locals.SetSegmentState(local_ref_cookie);
-  local_ref_cookie = stacked_local_ref_cookies.back();
-  stacked_local_ref_cookies.pop_back();
+  locals_.SetSegmentState(local_ref_cookie_);
+  local_ref_cookie_ = stacked_local_ref_cookies_.back();
+  stacked_local_ref_cookies_.pop_back();
 }
 
 // Note: the offset code is brittle, as we can't use OFFSETOF_MEMBER or offsetof easily. Thus, there
@@ -188,7 +187,7 @@
 }
 
 void JNIEnvExt::RecordMonitorEnter(jobject obj) {
-  locked_objects_.push_back(std::make_pair(GetJavaCallFrame(self), obj));
+  locked_objects_.push_back(std::make_pair(GetJavaCallFrame(self_), obj));
 }
 
 static std::string ComputeMonitorDescription(Thread* self,
@@ -230,7 +229,7 @@
 }
 
 void JNIEnvExt::CheckMonitorRelease(jobject obj) {
-  uintptr_t current_frame = GetJavaCallFrame(self);
+  uintptr_t current_frame = GetJavaCallFrame(self_);
   std::pair<uintptr_t, jobject> exact_pair = std::make_pair(current_frame, obj);
   auto it = std::find(locked_objects_.begin(), locked_objects_.end(), exact_pair);
   bool will_abort = false;
@@ -238,11 +237,11 @@
     locked_objects_.erase(it);
   } else {
     // Check whether this monitor was locked in another JNI "session."
-    ObjPtr<mirror::Object> mirror_obj = self->DecodeJObject(obj);
+    ObjPtr<mirror::Object> mirror_obj = self_->DecodeJObject(obj);
     for (std::pair<uintptr_t, jobject>& pair : locked_objects_) {
-      if (self->DecodeJObject(pair.second) == mirror_obj) {
-        std::string monitor_descr = ComputeMonitorDescription(self, pair.second);
-        vm->JniAbortF("<JNI MonitorExit>",
+      if (self_->DecodeJObject(pair.second) == mirror_obj) {
+        std::string monitor_descr = ComputeMonitorDescription(self_, pair.second);
+        vm_->JniAbortF("<JNI MonitorExit>",
                       "Unlocking monitor that wasn't locked here: %s",
                       monitor_descr.c_str());
         will_abort = true;
@@ -255,26 +254,26 @@
   // the monitors table, otherwise we may visit local objects in GC during abort (which won't be
   // valid anymore).
   if (will_abort) {
-    RemoveMonitors(self, current_frame, &monitors, &locked_objects_);
+    RemoveMonitors(self_, current_frame, &monitors_, &locked_objects_);
   }
 }
 
 void JNIEnvExt::CheckNoHeldMonitors() {
-  uintptr_t current_frame = GetJavaCallFrame(self);
   // The locked_objects_ are grouped by their stack frame component, as this enforces structured
   // locking, and the groups form a stack. So the current frame entries are at the end. Check
   // whether the vector is empty, and when there are elements, whether the last element belongs
   // to this call - this signals that there are unlocked monitors.
   if (!locked_objects_.empty()) {
+    uintptr_t current_frame = GetJavaCallFrame(self_);
     std::pair<uintptr_t, jobject>& pair = locked_objects_[locked_objects_.size() - 1];
     if (pair.first == current_frame) {
-      std::string monitor_descr = ComputeMonitorDescription(self, pair.second);
-      vm->JniAbortF("<JNI End>",
+      std::string monitor_descr = ComputeMonitorDescription(self_, pair.second);
+      vm_->JniAbortF("<JNI End>",
                     "Still holding a locked object on JNI end: %s",
                     monitor_descr.c_str());
       // When we abort, also make sure that any locks from the current "session" are removed from
       // the monitors table, otherwise we may visit local objects in GC during abort.
-      RemoveMonitors(self, current_frame, &monitors, &locked_objects_);
+      RemoveMonitors(self_, current_frame, &monitors_, &locked_objects_);
     } else if (kIsDebugBuild) {
       // Make sure there are really no other entries and our checking worked as expected.
       for (std::pair<uintptr_t, jobject>& check_pair : locked_objects_) {
@@ -282,12 +281,18 @@
       }
     }
   }
+  // Ensure critical locks aren't held when returning to Java.
+  if (critical_ > 0) {
+    vm_->JniAbortF("<JNI End>",
+                  "Critical lock held when returning to Java on thread %s",
+                  ToStr<Thread>(*self_).c_str());
+  }
 }
 
 static void ThreadResetFunctionTable(Thread* thread, void* arg ATTRIBUTE_UNUSED)
     REQUIRES(Locks::jni_function_table_lock_) {
   JNIEnvExt* env = thread->GetJniEnv();
-  bool check_jni = env->check_jni;
+  bool check_jni = env->IsCheckJniEnabled();
   env->functions = JNIEnvExt::GetFunctionTable(check_jni);
 }
 
diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h
index 2f6c5dc..0e8fd03 100644
--- a/runtime/jni_env_ext.h
+++ b/runtime/jni_env_ext.h
@@ -37,10 +37,15 @@
 // low enough that it forces sanity checks.
 static constexpr size_t kLocalsInitial = 512;
 
-struct JNIEnvExt : public JNIEnv {
+class JNIEnvExt : public JNIEnv {
+ public:
   // Creates a new JNIEnvExt. Returns null on error, in which case error_msg
   // will contain a description of the error.
   static JNIEnvExt* Create(Thread* self, JavaVMExt* vm, std::string* error_msg);
+  static Offset SegmentStateOffset(size_t pointer_size);
+  static Offset LocalRefCookieOffset(size_t pointer_size);
+  static Offset SelfOffset(size_t pointer_size);
+  static jint GetEnvHandler(JavaVMExt* vm, /*out*/void** out, jint version);
 
   ~JNIEnvExt();
 
@@ -58,43 +63,44 @@
       REQUIRES_SHARED(Locks::mutator_lock_)
       REQUIRES(!Locks::alloc_tracker_lock_);
 
-  static Offset SegmentStateOffset(size_t pointer_size);
-  static Offset LocalRefCookieOffset(size_t pointer_size);
-  static Offset SelfOffset(size_t pointer_size);
-
-  static jint GetEnvHandler(JavaVMExt* vm, /*out*/void** out, jint version);
+  void UpdateLocal(IndirectRef iref, ObjPtr<mirror::Object> obj) REQUIRES_SHARED(Locks::mutator_lock_) {
+    locals_.Update(iref, obj);
+  }
 
   jobject NewLocalRef(mirror::Object* obj) REQUIRES_SHARED(Locks::mutator_lock_);
   void DeleteLocalRef(jobject obj) REQUIRES_SHARED(Locks::mutator_lock_);
 
-  Thread* const self;
-  JavaVMExt* const vm;
+  void TrimLocals() REQUIRES_SHARED(Locks::mutator_lock_) {
+    locals_.Trim();
+  }
+  void AssertLocalsEmpty() REQUIRES_SHARED(Locks::mutator_lock_) {
+    locals_.AssertEmpty();
+  }
+  size_t GetLocalsCapacity() REQUIRES_SHARED(Locks::mutator_lock_) {
+    return locals_.Capacity();
+  }
 
-  // Cookie used when using the local indirect reference table.
-  IRTSegmentState local_ref_cookie;
+  IRTSegmentState GetLocalRefCookie() const { return local_ref_cookie_; }
+  void SetLocalRefCookie(IRTSegmentState new_cookie) { local_ref_cookie_ = new_cookie; }
 
-  // JNI local references.
-  IndirectReferenceTable locals GUARDED_BY(Locks::mutator_lock_);
+  IRTSegmentState GetLocalsSegmentState() const REQUIRES_SHARED(Locks::mutator_lock_) {
+    return locals_.GetSegmentState();
+  }
+  void SetLocalSegmentState(IRTSegmentState new_state) REQUIRES_SHARED(Locks::mutator_lock_) {
+    locals_.SetSegmentState(new_state);
+  }
 
-  // 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<IRTSegmentState> stacked_local_ref_cookies;
+  void VisitJniLocalRoots(RootVisitor* visitor, const RootInfo& root_info)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    locals_.VisitRoots(visitor, root_info);
+  }
 
-  // Frequently-accessed fields cached from JavaVM.
-  bool check_jni;
+  Thread* GetSelf() const { return self_; }
+  JavaVMExt* GetVm() const { return vm_; }
 
-  // If we are a JNI env for a daemon thread with a deleted runtime.
-  bool runtime_deleted;
+  bool IsRuntimeDeleted() const { return runtime_deleted_; }
+  bool IsCheckJniEnabled() const { return 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;
 
   // Functions to keep track of monitor lock and unlock operations. Used to ensure proper locking
   // rules in CheckJNI mode.
@@ -108,6 +114,11 @@
   // Check that no monitors are held that have been acquired in this JNI "segment."
   void CheckNoHeldMonitors() REQUIRES_SHARED(Locks::mutator_lock_);
 
+  void VisitMonitorRoots(RootVisitor* visitor, const RootInfo& root_info)
+      REQUIRES_SHARED(Locks::mutator_lock_) {
+    monitors_.VisitRoots(visitor, root_info);
+  }
+
   // Set the functions to the runtime shutdown functions.
   void SetFunctionsToRuntimeShutdownFunctions();
 
@@ -124,6 +135,11 @@
       REQUIRES(Locks::jni_function_table_lock_);
 
  private:
+  // Checking "locals" requires the mutator lock, but at creation time we're
+  // really only interested in validity, which isn't changing. To avoid grabbing
+  // the mutator lock, factored out and tagged with NO_THREAD_SAFETY_ANALYSIS.
+  static bool CheckLocalsValid(JNIEnvExt* in) NO_THREAD_SAFETY_ANALYSIS;
+
   // Override of function tables. This applies to both default as well as instrumented (CheckJNI)
   // function tables.
   static const JNINativeInterface* table_override_ GUARDED_BY(Locks::jni_function_table_lock_);
@@ -133,29 +149,73 @@
   JNIEnvExt(Thread* self, JavaVMExt* vm, std::string* error_msg)
       REQUIRES(!Locks::jni_function_table_lock_);
 
+  // Link to Thread::Current().
+  Thread* const self_;
+
+  // The invocation interface JavaVM.
+  JavaVMExt* const vm_;
+
+  // Cookie used when using the local indirect reference table.
+  IRTSegmentState 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<IRTSegmentState> stacked_local_ref_cookies_;
+
+  // Entered JNI monitors, for bulk exit on thread detach.
+  ReferenceTable monitors_;
+
+  // Used by -Xcheck:jni.
+  const JNINativeInterface* unchecked_functions_;
+
   // All locked objects, with the (Java caller) stack frame that locked them. Used in CheckJNI
   // to ensure that only monitors locked in this native frame are being unlocked, and that at
   // the end all are unlocked.
   std::vector<std::pair<uintptr_t, jobject>> locked_objects_;
+
+  // Start time of "critical" JNI calls to ensure that their use doesn't
+  // excessively block the VM with CheckJNI.
+  uint64_t critical_start_us_;
+
+  // How many nested "critical" JNI calls are we in? Used by CheckJNI to ensure that criticals are
+  uint32_t critical_;
+
+  // Frequently-accessed fields cached from JavaVM.
+  bool check_jni_;
+
+  // If we are a JNI env for a daemon thread with a deleted runtime.
+  bool runtime_deleted_;
+
+  friend class CheckJNI;
+  friend class JNI;
+  friend class ScopedCheck;
+  friend class ScopedJniEnvLocalRefState;
+  friend class Thread;
+  ART_FRIEND_TEST(JniInternalTest, JNIEnvExtOffsets);
 };
 
 // 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();
+  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_;
+    env_->locals_.SetSegmentState(env_->local_ref_cookie_);
+    env_->local_ref_cookie_ = saved_local_ref_cookie_;
   }
 
  private:
   JNIEnvExt* const env_;
-  IRTSegmentState saved_local_ref_cookie_;
+  const IRTSegmentState saved_local_ref_cookie_;
 
   DISALLOW_COPY_AND_ASSIGN(ScopedJniEnvLocalRefState);
 };
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 48fc5f7..53ae628 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -383,7 +383,7 @@
 }
 
 static JavaVMExt* JavaVmExtFromEnv(JNIEnv* env) {
-  return reinterpret_cast<JNIEnvExt*>(env)->vm;
+  return reinterpret_cast<JNIEnvExt*>(env)->GetVm();
 }
 
 #define CHECK_NON_NULL_ARGUMENT(value) \
@@ -545,7 +545,7 @@
   }
 
   static jboolean ExceptionCheck(JNIEnv* env) {
-    return static_cast<JNIEnvExt*>(env)->self->IsExceptionPending() ? JNI_TRUE : JNI_FALSE;
+    return static_cast<JNIEnvExt*>(env)->self_->IsExceptionPending() ? JNI_TRUE : JNI_FALSE;
   }
 
   static void ExceptionClear(JNIEnv* env) {
@@ -623,8 +623,8 @@
   }
 
   static void DeleteGlobalRef(JNIEnv* env, jobject obj) {
-    JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm;
-    Thread* self = down_cast<JNIEnvExt*>(env)->self;
+    JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->GetVm();
+    Thread* self = down_cast<JNIEnvExt*>(env)->self_;
     vm->DeleteGlobalRef(self, obj);
   }
 
@@ -635,8 +635,8 @@
   }
 
   static void DeleteWeakGlobalRef(JNIEnv* env, jweak obj) {
-    JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->vm;
-    Thread* self = down_cast<JNIEnvExt*>(env)->self;
+    JavaVMExt* vm = down_cast<JNIEnvExt*>(env)->GetVm();
+    Thread* self = down_cast<JNIEnvExt*>(env)->self_;
     vm->DeleteWeakGlobalRef(self, obj);
   }
 
@@ -659,7 +659,7 @@
     // it. b/22119403
     ScopedObjectAccess soa(env);
     auto* ext_env = down_cast<JNIEnvExt*>(env);
-    if (!ext_env->locals.Remove(ext_env->local_ref_cookie, obj)) {
+    if (!ext_env->locals_.Remove(ext_env->local_ref_cookie_, obj)) {
       // Attempting to delete a local reference that is not in the
       // topmost local reference frame is a no-op.  DeleteLocalRef returns
       // void and doesn't throw any exceptions, but we should probably
@@ -2310,7 +2310,7 @@
       // first, either as a direct or a virtual method. Then move to
       // the parent.
       ArtMethod* m = nullptr;
-      bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->vm->IsCheckJniEnabled();
+      bool warn_on_going_to_parent = down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled();
       for (ObjPtr<mirror::Class> current_class = c.Get();
            current_class != nullptr;
            current_class = current_class->GetSuperClass()) {
@@ -2399,7 +2399,7 @@
     ObjPtr<mirror::Object> o = soa.Decode<mirror::Object>(java_object);
     o = o->MonitorEnter(soa.Self());
     if (soa.Self()->HoldsLock(o)) {
-      soa.Env()->monitors.Add(o);
+      soa.Env()->monitors_.Add(o);
     }
     if (soa.Self()->IsExceptionPending()) {
       return JNI_ERR;
@@ -2414,7 +2414,7 @@
     bool remove_mon = soa.Self()->HoldsLock(o);
     o->MonitorExit(soa.Self());
     if (remove_mon) {
-      soa.Env()->monitors.Remove(o);
+      soa.Env()->monitors_.Remove(o);
     }
     if (soa.Self()->IsExceptionPending()) {
       return JNI_ERR;
@@ -2458,7 +2458,7 @@
     jobject result = env->NewObject(WellKnownClasses::java_nio_DirectByteBuffer,
                                     WellKnownClasses::java_nio_DirectByteBuffer_init,
                                     address_arg, capacity_arg);
-    return static_cast<JNIEnvExt*>(env)->self->IsExceptionPending() ? nullptr : result;
+    return static_cast<JNIEnvExt*>(env)->self_->IsExceptionPending() ? nullptr : result;
   }
 
   static void* GetDirectBufferAddress(JNIEnv* env, jobject java_buffer) {
@@ -2504,7 +2504,7 @@
     }
 
     std::string error_msg;
-    if (!soa.Env()->locals.EnsureFreeCapacity(static_cast<size_t>(desired_capacity), &error_msg)) {
+    if (!soa.Env()->locals_.EnsureFreeCapacity(static_cast<size_t>(desired_capacity), &error_msg)) {
       std::string caller_error = android::base::StringPrintf("%s: %s", caller, error_msg.c_str());
       soa.Self()->ThrowOutOfMemoryError(caller_error.c_str());
       return JNI_ERR;
diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc
index efeff0a..ad24c94 100644
--- a/runtime/jni_internal_test.cc
+++ b/runtime/jni_internal_test.cc
@@ -867,7 +867,7 @@
 
 static size_t GetLocalsCapacity(JNIEnv* env) {
   ScopedObjectAccess soa(Thread::Current());
-  return reinterpret_cast<JNIEnvExt*>(env)->locals.Capacity();
+  return reinterpret_cast<JNIEnvExt*>(env)->GetLocalsCapacity();
 }
 
 TEST_F(JniInternalTest, FromReflectedField_ToReflectedField) {
@@ -2400,15 +2400,15 @@
 
 // Test the offset computation of JNIEnvExt offsets. b/26071368.
 TEST_F(JniInternalTest, JNIEnvExtOffsets) {
-  EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie),
+  EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, local_ref_cookie_),
             JNIEnvExt::LocalRefCookieOffset(sizeof(void*)).Uint32Value());
 
-  EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, self), JNIEnvExt::SelfOffset(sizeof(void*)).Uint32Value());
+  EXPECT_EQ(OFFSETOF_MEMBER(JNIEnvExt, self_), JNIEnvExt::SelfOffset(sizeof(void*)).Uint32Value());
 
   // segment_state_ is private in the IndirectReferenceTable. So this test isn't as good as we'd
   // hope it to be.
   uint32_t segment_state_now =
-      OFFSETOF_MEMBER(JNIEnvExt, locals) +
+      OFFSETOF_MEMBER(JNIEnvExt, locals_) +
       IndirectReferenceTable::SegmentStateOffset(sizeof(void*)).Uint32Value();
   uint32_t segment_state_computed = JNIEnvExt::SegmentStateOffset(sizeof(void*)).Uint32Value();
   EXPECT_EQ(segment_state_now, segment_state_computed);
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index 1b5c535..6446e1b 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -223,7 +223,7 @@
 }
 
 static jboolean VMRuntime_isCheckJniEnabled(JNIEnv* env, jobject) {
-  return down_cast<JNIEnvExt*>(env)->vm->IsCheckJniEnabled() ? JNI_TRUE : JNI_FALSE;
+  return down_cast<JNIEnvExt*>(env)->GetVm()->IsCheckJniEnabled() ? JNI_TRUE : JNI_FALSE;
 }
 
 static void VMRuntime_setTargetSdkVersionNative(JNIEnv*, jobject, jint target_sdk_version) {
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index a717264..9a52f70 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -37,7 +37,7 @@
 }
 
 static jboolean Thread_interrupted(JNIEnv* env, jclass) {
-  return static_cast<JNIEnvExt*>(env)->self->Interrupted() ? JNI_TRUE : JNI_FALSE;
+  return static_cast<JNIEnvExt*>(env)->GetSelf()->Interrupted() ? JNI_TRUE : JNI_FALSE;
 }
 
 static jboolean Thread_isInterrupted(JNIEnv* env, jobject java_thread) {
diff --git a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
index 7b73382..836637f 100644
--- a/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
+++ b/runtime/native/org_apache_harmony_dalvik_ddmc_DdmVmInternal.cc
@@ -32,6 +32,10 @@
 
 namespace art {
 
+static Thread* GetSelf(JNIEnv* env) {
+  return static_cast<JNIEnvExt*>(env)->GetSelf();
+}
+
 static void DdmVmInternal_enableRecentAllocations(JNIEnv*, jclass, jboolean enable) {
   Dbg::SetAllocTrackingEnabled(enable);
 }
@@ -51,7 +55,7 @@
  */
 static jobjectArray DdmVmInternal_getStackTraceById(JNIEnv* env, jclass, jint thin_lock_id) {
   jobjectArray trace = nullptr;
-  Thread* const self = Thread::Current();
+  Thread* const self = GetSelf(env);
   if (static_cast<uint32_t>(thin_lock_id) == self->GetThreadId()) {
     // No need to suspend ourself to build stacktrace.
     ScopedObjectAccess soa(env);
@@ -136,7 +140,7 @@
 
 static jbyteArray DdmVmInternal_getThreadStats(JNIEnv* env, jclass) {
   std::vector<uint8_t> bytes;
-  Thread* self = static_cast<JNIEnvExt*>(env)->self;
+  Thread* self = GetSelf(env);
   {
     MutexLock mu(self, *Locks::thread_list_lock_);
     ThreadList* thread_list = Runtime::Current()->GetThreadList();
diff --git a/runtime/reflection.cc b/runtime/reflection.cc
index 9683ced..cacbe87 100644
--- a/runtime/reflection.cc
+++ b/runtime/reflection.cc
@@ -447,7 +447,7 @@
                                const char* shorty)
     REQUIRES_SHARED(Locks::mutator_lock_) {
   uint32_t* args = arg_array->GetArray();
-  if (UNLIKELY(soa.Env()->check_jni)) {
+  if (UNLIKELY(soa.Env()->IsCheckJniEnabled())) {
     CheckMethodArguments(soa.Vm(), method->GetInterfaceMethodIfProxy(kRuntimePointerSize), args);
   }
   method->Invoke(soa.Self(), args, arg_array->GetNumBytes(), result, shorty);
@@ -927,14 +927,14 @@
   IndirectRef ref = reinterpret_cast<IndirectRef>(obj);
   IndirectRefKind kind = IndirectReferenceTable::GetIndirectRefKind(ref);
   if (kind == kLocal) {
-    self->GetJniEnv()->locals.Update(obj, result);
+    self->GetJniEnv()->UpdateLocal(obj, result);
   } else if (kind == kHandleScopeOrInvalid) {
     LOG(FATAL) << "Unsupported UpdateReference for kind kHandleScopeOrInvalid";
   } else if (kind == kGlobal) {
-    self->GetJniEnv()->vm->UpdateGlobal(self, ref, result);
+    self->GetJniEnv()->GetVm()->UpdateGlobal(self, ref, result);
   } else {
     DCHECK_EQ(kind, kWeakGlobal);
-    self->GetJniEnv()->vm->UpdateWeakGlobal(self, ref, result);
+    self->GetJniEnv()->GetVm()->UpdateWeakGlobal(self, ref, result);
   }
 }
 
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index a172392..54aa9e5 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -807,7 +807,7 @@
 
   {
     ScopedObjectAccess soa(self);
-    self->GetJniEnv()->locals.AssertEmpty();
+    self->GetJniEnv()->AssertLocalsEmpty();
   }
 
   VLOG(startup) << "Runtime::Start exiting";
diff --git a/runtime/scoped_thread_state_change-inl.h b/runtime/scoped_thread_state_change-inl.h
index a9702a7..f955932 100644
--- a/runtime/scoped_thread_state_change-inl.h
+++ b/runtime/scoped_thread_state_change-inl.h
@@ -97,12 +97,12 @@
 }
 
 inline ScopedObjectAccessAlreadyRunnable::ScopedObjectAccessAlreadyRunnable(JNIEnv* env)
-    : self_(ThreadForEnv(env)), env_(down_cast<JNIEnvExt*>(env)), vm_(env_->vm) {}
+    : self_(ThreadForEnv(env)), env_(down_cast<JNIEnvExt*>(env)), vm_(env_->GetVm()) {}
 
 inline ScopedObjectAccessAlreadyRunnable::ScopedObjectAccessAlreadyRunnable(Thread* self)
     : self_(self),
       env_(down_cast<JNIEnvExt*>(self->GetJniEnv())),
-      vm_(env_ != nullptr ? env_->vm : nullptr) {}
+      vm_(env_ != nullptr ? env_->GetVm() : nullptr) {}
 
 inline ScopedObjectAccessUnchecked::ScopedObjectAccessUnchecked(JNIEnv* env)
     : ScopedObjectAccessAlreadyRunnable(env), tsc_(Self(), kRunnable) {
diff --git a/runtime/scoped_thread_state_change.h b/runtime/scoped_thread_state_change.h
index 02b6124..0c42c5a 100644
--- a/runtime/scoped_thread_state_change.h
+++ b/runtime/scoped_thread_state_change.h
@@ -27,7 +27,7 @@
 namespace art {
 
 class JavaVMExt;
-struct JNIEnvExt;
+class JNIEnvExt;
 template<class MirrorType> class ObjPtr;
 class Thread;
 
diff --git a/runtime/thread-inl.h b/runtime/thread-inl.h
index 62b0789..7392dcf 100644
--- a/runtime/thread-inl.h
+++ b/runtime/thread-inl.h
@@ -34,7 +34,7 @@
 // Quickly access the current thread from a JNIEnv.
 static inline Thread* ThreadForEnv(JNIEnv* env) {
   JNIEnvExt* full_env(down_cast<JNIEnvExt*>(env));
-  return full_env->self;
+  return full_env->GetSelf();
 }
 
 inline void Thread::AllowThreadSuspension() {
diff --git a/runtime/thread.cc b/runtime/thread.cc
index b86e56b..5777d14 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -621,7 +621,7 @@
 
 void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) {
   CHECK(java_peer != nullptr);
-  Thread* self = static_cast<JNIEnvExt*>(env)->self;
+  Thread* self = static_cast<JNIEnvExt*>(env)->GetSelf();
 
   if (VLOG_IS_ON(threads)) {
     ScopedObjectAccess soa(env);
@@ -754,8 +754,8 @@
   tls32_.thin_lock_thread_id = thread_list->AllocThreadId(this);
 
   if (jni_env_ext != nullptr) {
-    DCHECK_EQ(jni_env_ext->vm, java_vm);
-    DCHECK_EQ(jni_env_ext->self, this);
+    DCHECK_EQ(jni_env_ext->GetVm(), java_vm);
+    DCHECK_EQ(jni_env_ext->GetSelf(), this);
     tlsPtr_.jni_env = jni_env_ext;
   } else {
     std::string error_msg;
@@ -853,7 +853,7 @@
       if (thread_name != nullptr) {
         self->tlsPtr_.name->assign(thread_name);
         ::art::SetThreadName(thread_name);
-      } else if (self->GetJniEnv()->check_jni) {
+      } else if (self->GetJniEnv()->IsCheckJniEnabled()) {
         LOG(WARNING) << *Thread::Current() << " attached without supplying a name";
       }
     }
@@ -2170,7 +2170,7 @@
       ScopedObjectAccess soa(self);
       MonitorExitVisitor visitor(self);
       // On thread detach, all monitors entered with JNI MonitorEnter are automatically exited.
-      tlsPtr_.jni_env->monitors.VisitRoots(&visitor, RootInfo(kRootVMInternal));
+      tlsPtr_.jni_env->monitors_.VisitRoots(&visitor, RootInfo(kRootVMInternal));
     }
     // Release locally held global references which releasing may require the mutator lock.
     if (tlsPtr_.jpeer != nullptr) {
@@ -2339,7 +2339,7 @@
   bool expect_null = false;
   // The "kinds" below are sorted by the frequency we expect to encounter them.
   if (kind == kLocal) {
-    IndirectReferenceTable& locals = tlsPtr_.jni_env->locals;
+    IndirectReferenceTable& locals = tlsPtr_.jni_env->locals_;
     // Local references do not need a read barrier.
     result = locals.Get<kWithoutReadBarrier>(ref);
   } else if (kind == kHandleScopeOrInvalid) {
@@ -2350,15 +2350,15 @@
       result = reinterpret_cast<StackReference<mirror::Object>*>(obj)->AsMirrorPtr();
       VerifyObject(result);
     } else {
-      tlsPtr_.jni_env->vm->JniAbortF(nullptr, "use of invalid jobject %p", obj);
+      tlsPtr_.jni_env->vm_->JniAbortF(nullptr, "use of invalid jobject %p", obj);
       expect_null = true;
       result = nullptr;
     }
   } else if (kind == kGlobal) {
-    result = tlsPtr_.jni_env->vm->DecodeGlobal(ref);
+    result = tlsPtr_.jni_env->vm_->DecodeGlobal(ref);
   } else {
     DCHECK_EQ(kind, kWeakGlobal);
-    result = tlsPtr_.jni_env->vm->DecodeWeakGlobal(const_cast<Thread*>(this), ref);
+    result = tlsPtr_.jni_env->vm_->DecodeWeakGlobal(const_cast<Thread*>(this), ref);
     if (Runtime::Current()->IsClearedJniWeakGlobal(result)) {
       // This is a special case where it's okay to return null.
       expect_null = true;
@@ -2367,7 +2367,7 @@
   }
 
   if (UNLIKELY(!expect_null && result == nullptr)) {
-    tlsPtr_.jni_env->vm->JniAbortF(nullptr, "use of deleted %s %p",
+    tlsPtr_.jni_env->vm_->JniAbortF(nullptr, "use of deleted %s %p",
                                    ToStr<IndirectRefKind>(kind).c_str(), obj);
   }
   return result;
@@ -2378,7 +2378,7 @@
   IndirectRef ref = reinterpret_cast<IndirectRef>(obj);
   IndirectRefKind kind = IndirectReferenceTable::GetIndirectRefKind(ref);
   CHECK_EQ(kind, kWeakGlobal);
-  return tlsPtr_.jni_env->vm->IsWeakGlobalCleared(const_cast<Thread*>(this), ref);
+  return tlsPtr_.jni_env->vm_->IsWeakGlobalCleared(const_cast<Thread*>(this), ref);
 }
 
 // Implements java.lang.Thread.interrupted.
@@ -3516,8 +3516,8 @@
                        RootInfo(kRootNativeStack, thread_id));
   }
   visitor->VisitRootIfNonNull(&tlsPtr_.monitor_enter_object, RootInfo(kRootNativeStack, thread_id));
-  tlsPtr_.jni_env->locals.VisitRoots(visitor, RootInfo(kRootJNILocal, thread_id));
-  tlsPtr_.jni_env->monitors.VisitRoots(visitor, RootInfo(kRootJNIMonitor, thread_id));
+  tlsPtr_.jni_env->VisitJniLocalRoots(visitor, RootInfo(kRootJNILocal, thread_id));
+  tlsPtr_.jni_env->VisitMonitorRoots(visitor, RootInfo(kRootJNIMonitor, thread_id));
   HandleScopeVisitRoots(visitor, thread_id);
   if (tlsPtr_.debug_invoke_req != nullptr) {
     tlsPtr_.debug_invoke_req->VisitRoots(visitor, RootInfo(kRootDebugger, thread_id));
diff --git a/runtime/thread.h b/runtime/thread.h
index 0803975..0f1d7a4 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -86,7 +86,7 @@
 class DexFile;
 class FrameIdToShadowFrame;
 class JavaVMExt;
-struct JNIEnvExt;
+class JNIEnvExt;
 class Monitor;
 class RootVisitor;
 class ScopedObjectAccessAlreadyRunnable;
diff --git a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc
index 7d40f57..f01b825 100644
--- a/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc
+++ b/test/136-daemon-jni-shutdown/daemon_jni_shutdown.cc
@@ -58,7 +58,7 @@
   Thread* const self = Thread::Current();
   self->SetTopOfStack(nullptr);
   self->SetTopOfShadowStack(nullptr);
-  JavaVM* vm = down_cast<JNIEnvExt*>(env)->vm;
+  JavaVM* vm = down_cast<JNIEnvExt*>(env)->GetVm();
   vm->DetachCurrentThread();
   // Open ourself again to make sure the native library does not get unloaded from
   // underneath us due to DestroyJavaVM. b/28406866