Avoid JVMTI global deoptimization when possible

This changes the openjdkjvmti plugin to be more controlled about the
situations that it will deoptimize everything. Most notably this makes
the plugin deoptimize only individual methods for breakpoints instead
of doing a full deoptimization. It also doesn't deoptimize for the
JVMTI_EVENT_EXCEPTION method, since our throwing code will always send
the appropriate event.

Impact:
    Exoplayer benchmark with breakpointlogger setting a breakpoint on
    a method that is never called.

    The agent is the tools/breakpoint-logger agent.

    'art' options are for all runs were:
        --64
        -Xusejit:true
        -Xcompiler-option --debuggable
    'art' options for 'Pre change' and 'Post change' runs included:
        -Xplugin:libopenjdkjvmti.so
        '-agentpath:libbreakpointlogger.so=Lbenchmarks/common/java/BenchmarkBase;->run()V@0'

    Clean run (no agent loaded):
        Running FMP4 x 1 : 53
        Running TS x 1 : 144
        Running FMP4 x 2500 : 3309
        Running TS x 100 : 3584
        ExoPlayerBench(RunTime): 6977000.0 us.
    Pre change:
        Running FMP4 x 1 : 159
        Running TS x 1 : 9395
        Running FMP4 x 2500 : 298591
        Running TS x 100 : 944447
        ExoPlayerBench(RunTime): 1.243226E9 us.
    Post change:
        Running FMP4 x 1 : 87
        Running TS x 1 : 495
        Running FMP4 x 2500 : 2939
        Running TS x 100 : 3947
        ExoPlayerBench(RunTime): 6979000.0 us.

    Post change vs clean run is well within margin of error for this
    benchmark.

Test: ./test.py --host -j50
Test: ./art/tools/run-prebuild-libjdwp-tests.sh

Bug: 62821960
Bug: 67958496

Change-Id: I63ef04f71c36c34d8534651d0c075921a836ec08
diff --git a/openjdkjvmti/Android.bp b/openjdkjvmti/Android.bp
index eebfec4..c6090ef 100644
--- a/openjdkjvmti/Android.bp
+++ b/openjdkjvmti/Android.bp
@@ -24,6 +24,7 @@
     defaults: ["art_defaults"],
     host_supported: true,
     srcs: [
+        "deopt_manager.cc",
         "events.cc",
         "fixed_up_dex_file.cc",
         "object_tagging.cc",
diff --git a/openjdkjvmti/OpenjdkJvmTi.cc b/openjdkjvmti/OpenjdkJvmTi.cc
index c2584e6..5f726b1 100644
--- a/openjdkjvmti/OpenjdkJvmTi.cc
+++ b/openjdkjvmti/OpenjdkJvmTi.cc
@@ -71,6 +71,7 @@
 namespace openjdkjvmti {
 
 EventHandler gEventHandler;
+DeoptManager gDeoptManager;
 
 #define ENSURE_NON_NULL(n)      \
   do {                          \
@@ -1711,6 +1712,7 @@
 extern "C" bool ArtPlugin_Initialize() {
   art::Runtime* runtime = art::Runtime::Current();
 
+  gDeoptManager.Setup();
   if (runtime->IsStarted()) {
     PhaseUtil::SetToLive();
   } else {
@@ -1731,6 +1733,7 @@
 
 extern "C" bool ArtPlugin_Deinitialize() {
   gEventHandler.Shutdown();
+  gDeoptManager.Shutdown();
   PhaseUtil::Unregister();
   ThreadUtil::Unregister();
   ClassUtil::Unregister();
diff --git a/openjdkjvmti/art_jvmti.h b/openjdkjvmti/art_jvmti.h
index 3edefaf..1263460 100644
--- a/openjdkjvmti/art_jvmti.h
+++ b/openjdkjvmti/art_jvmti.h
@@ -39,6 +39,7 @@
 
 #include <jni.h>
 
+#include "deopt_manager.h"
 #include "base/casts.h"
 #include "base/logging.h"
 #include "base/macros.h"
@@ -60,9 +61,6 @@
 
 class ObjectTagTable;
 
-// Make sure that the DEFAULT_MUTEX_ACQUIRED_AFTER macro works.
-using art::Locks;
-
 // A structure that is a jvmtiEnv with additional information for the runtime.
 struct ArtJvmTiEnv : public jvmtiEnv {
   art::JavaVMExt* art_vm;
diff --git a/openjdkjvmti/deopt_manager.cc b/openjdkjvmti/deopt_manager.cc
new file mode 100644
index 0000000..f843054
--- /dev/null
+++ b/openjdkjvmti/deopt_manager.cc
@@ -0,0 +1,322 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include <functional>
+
+#include "deopt_manager.h"
+
+#include "art_jvmti.h"
+#include "art_method-inl.h"
+#include "base/enums.h"
+#include "base/mutex-inl.h"
+#include "dex_file_annotations.h"
+#include "events-inl.h"
+#include "jni_internal.h"
+#include "mirror/class-inl.h"
+#include "mirror/object_array-inl.h"
+#include "modifiers.h"
+#include "nativehelper/scoped_local_ref.h"
+#include "runtime_callbacks.h"
+#include "scoped_thread_state_change-inl.h"
+#include "thread-current-inl.h"
+#include "thread_list.h"
+#include "ti_phase.h"
+
+namespace openjdkjvmti {
+
+// TODO We should make this much more selective in the future so we only return true when we
+// actually care about the method (i.e. had locals changed, have breakpoints, etc.). For now though
+// we can just assume that we care we are loaded at all.
+//
+// Even if we don't keep track of this at the method level we might want to keep track of it at the
+// level of enabled capabilities.
+bool JvmtiMethodInspectionCallback::IsMethodBeingInspected(
+    art::ArtMethod* method ATTRIBUTE_UNUSED) {
+  return true;
+}
+
+bool JvmtiMethodInspectionCallback::IsMethodSafeToJit(art::ArtMethod* method) {
+  return !manager_->MethodHasBreakpoints(method);
+}
+
+DeoptManager::DeoptManager()
+  : deoptimization_status_lock_("JVMTI_DeoptimizationStatusLock"),
+    deoptimization_condition_("JVMTI_DeoptimizationCondition", deoptimization_status_lock_),
+    performing_deoptimization_(false),
+    global_deopt_count_(0),
+    deopter_count_(0),
+    inspection_callback_(this) { }
+
+void DeoptManager::Setup() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("Add method Inspection Callback");
+  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
+  callbacks->AddMethodInspectionCallback(&inspection_callback_);
+}
+
+void DeoptManager::Shutdown() {
+  art::ScopedThreadStateChange stsc(art::Thread::Current(),
+                                    art::ThreadState::kWaitingForDebuggerToAttach);
+  art::ScopedSuspendAll ssa("remove method Inspection Callback");
+  art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
+  callbacks->RemoveMethodInspectionCallback(&inspection_callback_);
+}
+
+bool DeoptManager::MethodHasBreakpoints(art::ArtMethod* method) {
+  art::MutexLock lk(art::Thread::Current(), deoptimization_status_lock_);
+  return MethodHasBreakpointsLocked(method);
+}
+
+bool DeoptManager::MethodHasBreakpointsLocked(art::ArtMethod* method) {
+  if (deopter_count_ == 0) {
+    return false;
+  }
+  auto elem = breakpoint_status_.find(method);
+  return elem != breakpoint_status_.end() && elem->second != 0;
+}
+
+void DeoptManager::RemoveDeoptimizeAllMethods() {
+  art::Thread* self = art::Thread::Current();
+  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  deoptimization_status_lock_.ExclusiveLock(self);
+  RemoveDeoptimizeAllMethodsLocked(self);
+}
+
+void DeoptManager::AddDeoptimizeAllMethods() {
+  art::Thread* self = art::Thread::Current();
+  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  deoptimization_status_lock_.ExclusiveLock(self);
+  AddDeoptimizeAllMethodsLocked(self);
+}
+
+void DeoptManager::AddMethodBreakpoint(art::ArtMethod* method) {
+  DCHECK(method->IsInvokable());
+  DCHECK(!method->IsProxyMethod()) << method->PrettyMethod();
+  DCHECK(!method->IsNative()) << method->PrettyMethod();
+
+  art::Thread* self = art::Thread::Current();
+  method = method->GetCanonicalMethod();
+  bool is_default = method->IsDefault();
+
+  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  deoptimization_status_lock_.ExclusiveLock(self);
+
+  DCHECK_GT(deopter_count_, 0u) << "unexpected deotpimization request";
+
+  if (MethodHasBreakpointsLocked(method)) {
+    // Don't need to do anything extra.
+    breakpoint_status_[method]++;
+    // Another thread might be deoptimizing the very method we just added new breakpoints for. Wait
+    // for any deopts to finish before moving on.
+    WaitForDeoptimizationToFinish(self);
+    return;
+  }
+  breakpoint_status_[method] = 1;
+  auto instrumentation = art::Runtime::Current()->GetInstrumentation();
+  if (instrumentation->IsForcedInterpretOnly()) {
+    // We are already interpreting everything so no need to do anything.
+    deoptimization_status_lock_.ExclusiveUnlock(self);
+    return;
+  } else if (is_default) {
+    AddDeoptimizeAllMethodsLocked(self);
+  } else {
+    PerformLimitedDeoptimization(self, method);
+  }
+}
+
+void DeoptManager::RemoveMethodBreakpoint(art::ArtMethod* method) {
+  DCHECK(method->IsInvokable()) << method->PrettyMethod();
+  DCHECK(!method->IsProxyMethod()) << method->PrettyMethod();
+  DCHECK(!method->IsNative()) << method->PrettyMethod();
+
+  art::Thread* self = art::Thread::Current();
+  method = method->GetCanonicalMethod();
+  bool is_default = method->IsDefault();
+
+  art::ScopedThreadSuspension sts(self, art::kSuspended);
+  // Ideally we should do a ScopedSuspendAll right here to get the full mutator_lock_ that we might
+  // need but since that is very heavy we will instead just use a condition variable to make sure we
+  // don't race with ourselves.
+  deoptimization_status_lock_.ExclusiveLock(self);
+
+  DCHECK_GT(deopter_count_, 0u) << "unexpected deotpimization request";
+  DCHECK(MethodHasBreakpointsLocked(method)) << "Breakpoint on a method was removed without "
+                                             << "breakpoints present!";
+  auto instrumentation = art::Runtime::Current()->GetInstrumentation();
+  breakpoint_status_[method] -= 1;
+  if (UNLIKELY(instrumentation->IsForcedInterpretOnly())) {
+    // We don't need to do anything since we are interpreting everything anyway.
+    deoptimization_status_lock_.ExclusiveUnlock(self);
+    return;
+  } else if (breakpoint_status_[method] == 0) {
+    if (UNLIKELY(is_default)) {
+      RemoveDeoptimizeAllMethodsLocked(self);
+    } else {
+      PerformLimitedUndeoptimization(self, method);
+    }
+  } else {
+    // Another thread might be deoptimizing the very methods we just removed breakpoints from. Wait
+    // for any deopts to finish before moving on.
+    WaitForDeoptimizationToFinish(self);
+  }
+}
+
+void DeoptManager::WaitForDeoptimizationToFinishLocked(art::Thread* self) {
+  while (performing_deoptimization_) {
+    deoptimization_condition_.Wait(self);
+  }
+}
+
+void DeoptManager::WaitForDeoptimizationToFinish(art::Thread* self) {
+  WaitForDeoptimizationToFinishLocked(self);
+  deoptimization_status_lock_.ExclusiveUnlock(self);
+}
+
+class ScopedDeoptimizationContext : public art::ValueObject {
+ public:
+  ScopedDeoptimizationContext(art::Thread* self, DeoptManager* deopt)
+      RELEASE(deopt->deoptimization_status_lock_)
+      ACQUIRE(art::Locks::mutator_lock_)
+      ACQUIRE(art::Roles::uninterruptible_)
+      : self_(self), deopt_(deopt), uninterruptible_cause_(nullptr) {
+    deopt_->WaitForDeoptimizationToFinishLocked(self_);
+    DCHECK(!deopt->performing_deoptimization_)
+        << "Already performing deoptimization on another thread!";
+    // Use performing_deoptimization_ to keep track of the lock.
+    deopt_->performing_deoptimization_ = true;
+    deopt_->deoptimization_status_lock_.Unlock(self_);
+    art::Runtime::Current()->GetThreadList()->SuspendAll("JMVTI Deoptimizing methods",
+                                                         /*long_suspend*/ false);
+    uninterruptible_cause_ = self_->StartAssertNoThreadSuspension("JVMTI deoptimizing methods");
+  }
+
+  ~ScopedDeoptimizationContext()
+      RELEASE(art::Locks::mutator_lock_)
+      RELEASE(art::Roles::uninterruptible_) {
+    // Can be suspended again.
+    self_->EndAssertNoThreadSuspension(uninterruptible_cause_);
+    // Release the mutator lock.
+    art::Runtime::Current()->GetThreadList()->ResumeAll();
+    // Let other threads know it's fine to proceed.
+    art::MutexLock lk(self_, deopt_->deoptimization_status_lock_);
+    deopt_->performing_deoptimization_ = false;
+    deopt_->deoptimization_condition_.Broadcast(self_);
+  }
+
+ private:
+  art::Thread* self_;
+  DeoptManager* deopt_;
+  const char* uninterruptible_cause_;
+};
+
+void DeoptManager::AddDeoptimizeAllMethodsLocked(art::Thread* self) {
+  global_deopt_count_++;
+  if (global_deopt_count_ == 1) {
+    PerformGlobalDeoptimization(self);
+  } else {
+    WaitForDeoptimizationToFinish(self);
+  }
+}
+
+void DeoptManager::RemoveDeoptimizeAllMethodsLocked(art::Thread* self) {
+  DCHECK_GT(global_deopt_count_, 0u) << "Request to remove non-existant global deoptimization!";
+  global_deopt_count_--;
+  if (global_deopt_count_ == 0) {
+    PerformGlobalUndeoptimization(self);
+  } else {
+    WaitForDeoptimizationToFinish(self);
+  }
+}
+
+void DeoptManager::PerformLimitedDeoptimization(art::Thread* self, art::ArtMethod* method) {
+  ScopedDeoptimizationContext sdc(self, this);
+  art::Runtime::Current()->GetInstrumentation()->Deoptimize(method);
+}
+
+void DeoptManager::PerformLimitedUndeoptimization(art::Thread* self, art::ArtMethod* method) {
+  ScopedDeoptimizationContext sdc(self, this);
+  art::Runtime::Current()->GetInstrumentation()->Undeoptimize(method);
+}
+
+void DeoptManager::PerformGlobalDeoptimization(art::Thread* self) {
+  ScopedDeoptimizationContext sdc(self, this);
+  art::Runtime::Current()->GetInstrumentation()->DeoptimizeEverything(
+      kDeoptManagerInstrumentationKey);
+}
+
+void DeoptManager::PerformGlobalUndeoptimization(art::Thread* self) {
+  ScopedDeoptimizationContext sdc(self, this);
+  art::Runtime::Current()->GetInstrumentation()->UndeoptimizeEverything(
+      kDeoptManagerInstrumentationKey);
+}
+
+
+void DeoptManager::RemoveDeoptimizationRequester() {
+  art::Thread* self = art::Thread::Current();
+  art::ScopedThreadStateChange sts(self, art::kSuspended);
+  deoptimization_status_lock_.ExclusiveLock(self);
+  DCHECK_GT(deopter_count_, 0u) << "Removing deoptimization requester without any being present";
+  deopter_count_--;
+  if (deopter_count_ == 0) {
+    ScopedDeoptimizationContext sdc(self, this);
+    // TODO Give this a real key.
+    art::Runtime::Current()->GetInstrumentation()->DisableDeoptimization("");
+    return;
+  } else {
+    deoptimization_status_lock_.ExclusiveUnlock(self);
+  }
+}
+
+void DeoptManager::AddDeoptimizationRequester() {
+  art::Thread* self = art::Thread::Current();
+  art::ScopedThreadStateChange stsc(self, art::kSuspended);
+  deoptimization_status_lock_.ExclusiveLock(self);
+  deopter_count_++;
+  if (deopter_count_ == 1) {
+    ScopedDeoptimizationContext sdc(self, this);
+    art::Runtime::Current()->GetInstrumentation()->EnableDeoptimization();
+    return;
+  } else {
+    deoptimization_status_lock_.ExclusiveUnlock(self);
+  }
+}
+
+void DeoptManager::DeoptimizeThread(art::Thread* target) {
+  art::Runtime::Current()->GetInstrumentation()->InstrumentThreadStack(target);
+}
+
+extern DeoptManager gDeoptManager;
+DeoptManager* DeoptManager::Get() {
+  return &gDeoptManager;
+}
+
+}  // namespace openjdkjvmti
diff --git a/openjdkjvmti/deopt_manager.h b/openjdkjvmti/deopt_manager.h
new file mode 100644
index 0000000..b265fa8
--- /dev/null
+++ b/openjdkjvmti/deopt_manager.h
@@ -0,0 +1,168 @@
+/* Copyright (C) 2017 The Android Open Source Project
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This file implements interfaces from the file jvmti.h. This implementation
+ * is licensed under the same terms as the file jvmti.h.  The
+ * copyright and license information for the file jvmti.h follows.
+ *
+ * Copyright (c) 2003, 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#ifndef ART_OPENJDKJVMTI_DEOPT_MANAGER_H_
+#define ART_OPENJDKJVMTI_DEOPT_MANAGER_H_
+
+#include <unordered_map>
+
+#include "jni.h"
+#include "jvmti.h"
+
+#include "base/mutex.h"
+#include "runtime_callbacks.h"
+#include "ti_breakpoint.h"
+
+namespace art {
+class ArtMethod;
+namespace mirror {
+class Class;
+}  // namespace mirror
+}  // namespace art
+
+namespace openjdkjvmti {
+
+class DeoptManager;
+
+struct JvmtiMethodInspectionCallback : public art::MethodInspectionCallback {
+ public:
+  explicit JvmtiMethodInspectionCallback(DeoptManager* manager) : manager_(manager) {}
+
+  bool IsMethodBeingInspected(art::ArtMethod* method)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  bool IsMethodSafeToJit(art::ArtMethod* method)
+      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+ private:
+  DeoptManager* manager_;
+};
+
+class ScopedDeoptimizationContext;
+
+class DeoptManager {
+ public:
+  DeoptManager();
+
+  void Setup();
+  void Shutdown();
+
+  void RemoveDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_,
+                                                !art::Roles::uninterruptible_);
+  void AddDeoptimizationRequester() REQUIRES(!deoptimization_status_lock_,
+                                             !art::Roles::uninterruptible_);
+  bool MethodHasBreakpoints(art::ArtMethod* method)
+      REQUIRES(!deoptimization_status_lock_);
+
+  void RemoveMethodBreakpoint(art::ArtMethod* method)
+      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  void AddMethodBreakpoint(art::ArtMethod* method)
+      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  void AddDeoptimizeAllMethods()
+      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  void RemoveDeoptimizeAllMethods()
+      REQUIRES(!deoptimization_status_lock_, !art::Roles::uninterruptible_)
+      REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  void DeoptimizeThread(art::Thread* target) REQUIRES_SHARED(art::Locks::mutator_lock_);
+  void DeoptimizeAllThreads() REQUIRES_SHARED(art::Locks::mutator_lock_);
+
+  static DeoptManager* Get();
+
+ private:
+  bool MethodHasBreakpointsLocked(art::ArtMethod* method)
+      REQUIRES(deoptimization_status_lock_);
+
+  // Wait until nothing is currently in the middle of deoptimizing/undeoptimizing something. This is
+  // needed to ensure that everything is synchronized since threads need to drop the
+  // deoptimization_status_lock_ while deoptimizing methods.
+  void WaitForDeoptimizationToFinish(art::Thread* self)
+      RELEASE(deoptimization_status_lock_) REQUIRES(!art::Locks::mutator_lock_);
+
+  void WaitForDeoptimizationToFinishLocked(art::Thread* self)
+      REQUIRES(deoptimization_status_lock_, !art::Locks::mutator_lock_);
+
+  void AddDeoptimizeAllMethodsLocked(art::Thread* self)
+      RELEASE(deoptimization_status_lock_)
+      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);
+
+  void RemoveDeoptimizeAllMethodsLocked(art::Thread* self)
+      RELEASE(deoptimization_status_lock_)
+      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);
+
+  void PerformGlobalDeoptimization(art::Thread* self)
+      RELEASE(deoptimization_status_lock_)
+      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);
+
+  void PerformGlobalUndeoptimization(art::Thread* self)
+      RELEASE(deoptimization_status_lock_)
+      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);
+
+  void PerformLimitedDeoptimization(art::Thread* self, art::ArtMethod* method)
+      RELEASE(deoptimization_status_lock_)
+      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);
+
+  void PerformLimitedUndeoptimization(art::Thread* self, art::ArtMethod* method)
+      RELEASE(deoptimization_status_lock_)
+      REQUIRES(!art::Roles::uninterruptible_, !art::Locks::mutator_lock_);
+
+  static constexpr const char* kDeoptManagerInstrumentationKey = "JVMTI_DeoptManager";
+  // static constexpr const char* kDeoptManagerThreadName = "JVMTI_DeoptManagerWorkerThread";
+
+  art::Mutex deoptimization_status_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+  art::ConditionVariable deoptimization_condition_ GUARDED_BY(deoptimization_status_lock_);
+  bool performing_deoptimization_ GUARDED_BY(deoptimization_status_lock_);
+
+  // Number of times we have gotten requests to deopt everything.
+  uint32_t global_deopt_count_ GUARDED_BY(deoptimization_status_lock_);
+
+  // Number of users of deoptimization there currently are.
+  uint32_t deopter_count_ GUARDED_BY(deoptimization_status_lock_);
+
+  // A map from methods to the number of breakpoints in them from all envs.
+  std::unordered_map<art::ArtMethod*, uint32_t> breakpoint_status_
+      GUARDED_BY(deoptimization_status_lock_);
+
+  // The MethodInspectionCallback we use to tell the runtime if we care about particular methods.
+  JvmtiMethodInspectionCallback inspection_callback_;
+
+  // Helper for setting up/tearing-down for deoptimization.
+  friend class ScopedDeoptimizationContext;
+};
+
+}  // namespace openjdkjvmti
+#endif  // ART_OPENJDKJVMTI_DEOPT_MANAGER_H_
diff --git a/openjdkjvmti/events-inl.h b/openjdkjvmti/events-inl.h
index d97916f..7f77f90 100644
--- a/openjdkjvmti/events-inl.h
+++ b/openjdkjvmti/events-inl.h
@@ -480,6 +480,7 @@
   ArtJvmtiEvent event = added ? ArtJvmtiEvent::kClassFileLoadHookNonRetransformable
                               : ArtJvmtiEvent::kClassFileLoadHookRetransformable;
   return (added && caps.can_access_local_variables == 1) ||
+      caps.can_generate_breakpoint_events == 1 ||
       (caps.can_retransform_classes == 1 &&
        IsEventEnabledAnywhere(event) &&
        env->event_masks.IsEnabledAnywhere(event));
@@ -497,6 +498,9 @@
     if (added && caps.can_access_local_variables == 1) {
       HandleLocalAccessCapabilityAdded();
     }
+    if (caps.can_generate_breakpoint_events == 1) {
+      HandleBreakpointEventsChanged(added);
+    }
   }
 }
 
diff --git a/openjdkjvmti/events.cc b/openjdkjvmti/events.cc
index 381dc1f..6a64441 100644
--- a/openjdkjvmti/events.cc
+++ b/openjdkjvmti/events.cc
@@ -37,6 +37,7 @@
 #include "art_jvmti.h"
 #include "art_method-inl.h"
 #include "base/logging.h"
+#include "deopt_manager.h"
 #include "dex_file_types.h"
 #include "gc/allocation_listener.h"
 #include "gc/gc_pause_listener.h"
@@ -810,9 +811,49 @@
   }
 }
 
+static bool EventNeedsFullDeopt(ArtJvmtiEvent event) {
+  switch (event) {
+    case ArtJvmtiEvent::kBreakpoint:
+    case ArtJvmtiEvent::kException:
+      return false;
+    // TODO We should support more of these or at least do something to make them discriminate by
+    // thread.
+    case ArtJvmtiEvent::kMethodEntry:
+    case ArtJvmtiEvent::kExceptionCatch:
+    case ArtJvmtiEvent::kMethodExit:
+    case ArtJvmtiEvent::kFieldModification:
+    case ArtJvmtiEvent::kFieldAccess:
+    case ArtJvmtiEvent::kSingleStep:
+    case ArtJvmtiEvent::kFramePop:
+      return true;
+    default:
+      LOG(FATAL) << "Unexpected event type!";
+      UNREACHABLE();
+  }
+}
+
 static void SetupTraceListener(JvmtiMethodTraceListener* listener,
                                ArtJvmtiEvent event,
                                bool enable) {
+  bool needs_full_deopt = EventNeedsFullDeopt(event);
+  // Make sure we can deopt.
+  {
+    art::ScopedObjectAccess soa(art::Thread::Current());
+    DeoptManager* deopt_manager = DeoptManager::Get();
+    if (enable) {
+      deopt_manager->AddDeoptimizationRequester();
+      if (needs_full_deopt) {
+        deopt_manager->AddDeoptimizeAllMethods();
+      }
+    } else {
+      if (needs_full_deopt) {
+        deopt_manager->RemoveDeoptimizeAllMethods();
+      }
+      deopt_manager->RemoveDeoptimizationRequester();
+    }
+  }
+
+  // Add the actual listeners.
   art::ScopedThreadStateChange stsc(art::Thread::Current(), art::ThreadState::kNative);
   uint32_t new_events = GetInstrumentationEventsFor(event);
   art::instrumentation::Instrumentation* instr = art::Runtime::Current()->GetInstrumentation();
@@ -821,11 +862,6 @@
                                        art::gc::kCollectorTypeInstrumentation);
   art::ScopedSuspendAll ssa("jvmti method tracing installation");
   if (enable) {
-    // TODO Depending on the features being used we should be able to avoid deoptimizing everything
-    // like we do here.
-    if (!instr->AreAllMethodsDeoptimized()) {
-      instr->EnableMethodTracing("jvmti-tracing", /*needs_interpreter*/true);
-    }
     instr->AddListener(listener, new_events);
   } else {
     instr->RemoveListener(listener, new_events);
@@ -910,6 +946,7 @@
     }
     // FramePop can never be disabled once it's been turned on since we would either need to deal
     // with dangling pointers or have missed events.
+    // TODO We really need to make this not the case anymore.
     case ArtJvmtiEvent::kFramePop:
       if (!enable || (enable && frame_pop_enabled)) {
         break;
@@ -1046,6 +1083,14 @@
   return ERR(NONE);
 }
 
+void EventHandler::HandleBreakpointEventsChanged(bool added) {
+  if (added) {
+    DeoptManager::Get()->AddDeoptimizationRequester();
+  } else {
+    DeoptManager::Get()->RemoveDeoptimizationRequester();
+  }
+}
+
 void EventHandler::Shutdown() {
   // Need to remove the method_trace_listener_ if it's there.
   art::Thread* self = art::Thread::Current();
diff --git a/openjdkjvmti/events.h b/openjdkjvmti/events.h
index a062e15..aed24e5 100644
--- a/openjdkjvmti/events.h
+++ b/openjdkjvmti/events.h
@@ -232,6 +232,7 @@
 
   void HandleEventType(ArtJvmtiEvent event, bool enable);
   void HandleLocalAccessCapabilityAdded();
+  void HandleBreakpointEventsChanged(bool enable);
 
   bool OtherMonitorEventsEnabledAnywhere(ArtJvmtiEvent event);
 
diff --git a/openjdkjvmti/ti_breakpoint.cc b/openjdkjvmti/ti_breakpoint.cc
index 9c3687f..8e5b56e 100644
--- a/openjdkjvmti/ti_breakpoint.cc
+++ b/openjdkjvmti/ti_breakpoint.cc
@@ -37,6 +37,7 @@
 #include "art_method-inl.h"
 #include "base/enums.h"
 #include "base/mutex-inl.h"
+#include "deopt_manager.h"
 #include "dex_file_annotations.h"
 #include "events-inl.h"
 #include "jni_internal.h"
@@ -63,18 +64,30 @@
 }
 
 void BreakpointUtil::RemoveBreakpointsInClass(ArtJvmTiEnv* env, art::mirror::Class* klass) {
-  art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_);
   std::vector<Breakpoint> to_remove;
-  for (const Breakpoint& b : env->breakpoints) {
-    if (b.GetMethod()->GetDeclaringClass() == klass) {
-      to_remove.push_back(b);
+  {
+    art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_);
+    for (const Breakpoint& b : env->breakpoints) {
+      if (b.GetMethod()->GetDeclaringClass() == klass) {
+        to_remove.push_back(b);
+      }
+    }
+    for (const Breakpoint& b : to_remove) {
+      auto it = env->breakpoints.find(b);
+      DCHECK(it != env->breakpoints.end());
+      env->breakpoints.erase(it);
     }
   }
-  for (const Breakpoint& b : to_remove) {
-    auto it = env->breakpoints.find(b);
-    DCHECK(it != env->breakpoints.end());
-    env->breakpoints.erase(it);
+  if (!to_remove.empty()) {
+    LOG(WARNING) << "Methods with breakpoints potentially not being un-deoptimized.";
   }
+  // TODO Figure out how to do this.
+  // DeoptManager* deopt = DeoptManager::Get();
+  // for (const Breakpoint& b : to_remove) {
+  //   // TODO It might be good to send these all at once instead.
+  //   // deopt->RemoveMethodBreakpointSuspended(b.GetMethod());
+  //   LOG(WARNING) << "not un-deopting methods! :-0";
+  // }
 }
 
 jvmtiError BreakpointUtil::SetBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
@@ -82,20 +95,23 @@
   if (method == nullptr) {
     return ERR(INVALID_METHODID);
   }
-  // Need to get mutator_lock_ so we can find the interface version of any default methods.
   art::ScopedObjectAccess soa(art::Thread::Current());
   art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod();
-  art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_);
   if (location < 0 || static_cast<uint32_t>(location) >=
       art_method->GetCodeItem()->insns_size_in_code_units_) {
     return ERR(INVALID_LOCATION);
   }
-  auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location});
-  if (!res_pair.second) {
-    // Didn't get inserted because it's already present!
-    return ERR(DUPLICATE);
+  DeoptManager::Get()->AddMethodBreakpoint(art_method);
+  {
+    art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_);
+    auto res_pair = env->breakpoints.insert(/* Breakpoint */ {art_method, location});
+    if (LIKELY(res_pair.second)) {
+      return OK;
+    }
   }
-  return OK;
+  // Didn't get inserted because it's already present!
+  DeoptManager::Get()->RemoveMethodBreakpoint(art_method);
+  return ERR(DUPLICATE);
 }
 
 jvmtiError BreakpointUtil::ClearBreakpoint(jvmtiEnv* jenv, jmethodID method, jlocation location) {
@@ -103,15 +119,17 @@
   if (method == nullptr) {
     return ERR(INVALID_METHODID);
   }
-  // Need to get mutator_lock_ so we can find the interface version of any default methods.
   art::ScopedObjectAccess soa(art::Thread::Current());
   art::ArtMethod* art_method = art::jni::DecodeArtMethod(method)->GetCanonicalMethod();
-  art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_);
-  auto pos = env->breakpoints.find(/* Breakpoint */ {art_method, location});
-  if (pos == env->breakpoints.end()) {
-    return ERR(NOT_FOUND);
+  {
+    art::WriterMutexLock lk(art::Thread::Current(), env->event_info_mutex_);
+    auto pos = env->breakpoints.find(/* Breakpoint */ {art_method, location});
+    if (pos == env->breakpoints.end()) {
+      return ERR(NOT_FOUND);
+    }
+    env->breakpoints.erase(pos);
   }
-  env->breakpoints.erase(pos);
+  DeoptManager::Get()->RemoveMethodBreakpoint(art_method);
   return OK;
 }
 
diff --git a/openjdkjvmti/ti_method.cc b/openjdkjvmti/ti_method.cc
index 41679da..5d63285 100644
--- a/openjdkjvmti/ti_method.cc
+++ b/openjdkjvmti/ti_method.cc
@@ -86,21 +86,6 @@
 
 TiMethodCallback gMethodCallback;
 
-// TODO We should make this much more selective in the future so we only return true when we
-// actually care about the method (i.e. had locals changed, have breakpoints, etc.). For now though
-// we can just assume that we care we are loaded at all.
-//
-// Even if we don't keep track of this at the method level we might want to keep track of it at the
-// level of enabled capabilities.
-struct TiMethodInspectionCallback : public art::MethodInspectionCallback {
-  bool IsMethodBeingInspected(art::ArtMethod* method ATTRIBUTE_UNUSED)
-      OVERRIDE REQUIRES_SHARED(art::Locks::mutator_lock_) {
-    return true;
-  }
-};
-
-TiMethodInspectionCallback gMethodInspectionCallback;
-
 void MethodUtil::Register(EventHandler* handler) {
   gMethodCallback.event_handler = handler;
   art::ScopedThreadStateChange stsc(art::Thread::Current(),
@@ -108,7 +93,6 @@
   art::ScopedSuspendAll ssa("Add method callback");
   art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
   callbacks->AddMethodCallback(&gMethodCallback);
-  callbacks->AddMethodInspectionCallback(&gMethodInspectionCallback);
 }
 
 void MethodUtil::Unregister() {
@@ -117,7 +101,6 @@
   art::ScopedSuspendAll ssa("Remove method callback");
   art::RuntimeCallbacks* callbacks = art::Runtime::Current()->GetRuntimeCallbacks();
   callbacks->RemoveMethodCallback(&gMethodCallback);
-  callbacks->AddMethodInspectionCallback(&gMethodInspectionCallback);
 }
 
 jvmtiError MethodUtil::GetBytecodes(jvmtiEnv* env,
diff --git a/runtime/base/mutex.h b/runtime/base/mutex.h
index 189c0d0..4b56d3b 100644
--- a/runtime/base/mutex.h
+++ b/runtime/base/mutex.h
@@ -664,7 +664,7 @@
 
   // When declaring any Mutex add DEFAULT_MUTEX_ACQUIRED_AFTER to use annotalysis to check the code
   // doesn't try to hold a higher level Mutex.
-  #define DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_AFTER(Locks::classlinker_classes_lock_)
+  #define DEFAULT_MUTEX_ACQUIRED_AFTER ACQUIRED_AFTER(art::Locks::classlinker_classes_lock_)
 
   static Mutex* allocated_monitor_ids_lock_ ACQUIRED_AFTER(classlinker_classes_lock_);
 
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 85df14a..8898afe 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -346,6 +346,10 @@
   return Dbg::IsDebuggerActive();
 }
 
+bool DebuggerActiveMethodInspectionCallback::IsMethodSafeToJit(ArtMethod* m) {
+  return !Dbg::MethodHasAnyBreakpoints(m);
+}
+
 // Breakpoints.
 static std::vector<Breakpoint> gBreakpoints GUARDED_BY(Locks::breakpoint_lock_);
 
diff --git a/runtime/debugger.h b/runtime/debugger.h
index 18126b1..ec37833 100644
--- a/runtime/debugger.h
+++ b/runtime/debugger.h
@@ -55,6 +55,7 @@
 struct DebuggerActiveMethodInspectionCallback : public MethodInspectionCallback {
   bool IsMethodBeingInspected(ArtMethod* m ATTRIBUTE_UNUSED)
       OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
+  bool IsMethodSafeToJit(ArtMethod* m) OVERRIDE REQUIRES_SHARED(Locks::mutator_lock_);
 };
 
 
diff --git a/runtime/instrumentation.cc b/runtime/instrumentation.cc
index 2c82cb1..49f2021 100644
--- a/runtime/instrumentation.cc
+++ b/runtime/instrumentation.cc
@@ -137,11 +137,12 @@
   method->SetEntryPointFromQuickCompiledCode(quick_code);
 }
 
-bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const REQUIRES_SHARED(Locks::mutator_lock_) {
-  return Dbg::IsDebuggerActive() &&
-         Runtime::Current()->IsJavaDebuggable() &&
+bool Instrumentation::NeedDebugVersionFor(ArtMethod* method) const
+    REQUIRES_SHARED(Locks::mutator_lock_) {
+  return Runtime::Current()->IsJavaDebuggable() &&
          !method->IsNative() &&
-         !method->IsProxyMethod();
+         !method->IsProxyMethod() &&
+         Runtime::Current()->GetRuntimeCallbacks()->IsMethodBeingInspected(method);
 }
 
 void Instrumentation::InstallStubsForMethod(ArtMethod* method) {
diff --git a/runtime/jit/jit.cc b/runtime/jit/jit.cc
index 97a3b71..72b5a94 100644
--- a/runtime/jit/jit.cc
+++ b/runtime/jit/jit.cc
@@ -272,9 +272,12 @@
   DCHECK(Runtime::Current()->UseJitCompilation());
   DCHECK(!method->IsRuntimeMethod());
 
+  RuntimeCallbacks* cb = Runtime::Current()->GetRuntimeCallbacks();
   // Don't compile the method if it has breakpoints.
-  if (Dbg::IsDebuggerActive() && Dbg::MethodHasAnyBreakpoints(method)) {
-    VLOG(jit) << "JIT not compiling " << method->PrettyMethod() << " due to breakpoint";
+  if (cb->IsMethodBeingInspected(method) && !cb->IsMethodSafeToJit(method)) {
+    VLOG(jit) << "JIT not compiling " << method->PrettyMethod()
+              << " due to not being safe to jit according to runtime-callbacks. For example, there"
+              << " could be breakpoints in this method.";
     return false;
   }
 
diff --git a/runtime/runtime_callbacks.cc b/runtime/runtime_callbacks.cc
index f164f7c..339fe82 100644
--- a/runtime/runtime_callbacks.cc
+++ b/runtime/runtime_callbacks.cc
@@ -43,6 +43,17 @@
   Remove(cb, &method_inspection_callbacks_);
 }
 
+bool RuntimeCallbacks::IsMethodSafeToJit(ArtMethod* m) {
+  for (MethodInspectionCallback* cb : method_inspection_callbacks_) {
+    if (!cb->IsMethodSafeToJit(m)) {
+      DCHECK(cb->IsMethodBeingInspected(m))
+          << "Contract requires that !IsMethodSafeToJit(m) -> IsMethodBeingInspected(m)";
+      return false;
+    }
+  }
+  return true;
+}
+
 bool RuntimeCallbacks::IsMethodBeingInspected(ArtMethod* m) {
   for (MethodInspectionCallback* cb : method_inspection_callbacks_) {
     if (cb->IsMethodBeingInspected(m)) {
diff --git a/runtime/runtime_callbacks.h b/runtime/runtime_callbacks.h
index c936049..c1ba964 100644
--- a/runtime/runtime_callbacks.h
+++ b/runtime/runtime_callbacks.h
@@ -104,6 +104,11 @@
   // Returns true if the method is being inspected currently and the runtime should not modify it in
   // potentially dangerous ways (i.e. replace with compiled version, JIT it, etc).
   virtual bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
+
+  // Returns true if the method is safe to Jit, false otherwise.
+  // Note that '!IsMethodSafeToJit(m) implies IsMethodBeingInspected(m)'. That is that if this
+  // method returns false IsMethodBeingInspected must return true.
+  virtual bool IsMethodSafeToJit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_) = 0;
 };
 
 class RuntimeCallbacks {
@@ -167,6 +172,11 @@
   // on by some code.
   bool IsMethodBeingInspected(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
 
+  // Returns false if some MethodInspectionCallback indicates the method cannot be safetly jitted
+  // (which implies that it is being Inspected). Returns true otherwise. If it returns false the
+  // entrypoint should not be changed to JITed code.
+  bool IsMethodSafeToJit(ArtMethod* method) REQUIRES_SHARED(Locks::mutator_lock_);
+
   void AddMethodInspectionCallback(MethodInspectionCallback* cb)
       REQUIRES_SHARED(Locks::mutator_lock_);
   void RemoveMethodInspectionCallback(MethodInspectionCallback* cb)
diff --git a/test/993-breakpoints/breakpoints.cc b/test/993-breakpoints/breakpoints.cc
index 3734ce8..e9cf3b3 100644
--- a/test/993-breakpoints/breakpoints.cc
+++ b/test/993-breakpoints/breakpoints.cc
@@ -49,6 +49,57 @@
 }
 
 extern "C" JNIEXPORT
+void JNICALL Java_art_Test993_invokeNativeObject(JNIEnv* env,
+                                                 jclass klass ATTRIBUTE_UNUSED,
+                                                 jobject target,
+                                                 jclass clazz,
+                                                 jobject thizz) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  if (thizz == nullptr) {
+    env->CallStaticObjectMethod(clazz, method);
+  } else {
+    env->CallObjectMethod(thizz, method);
+  }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test993_invokeNativeBool(JNIEnv* env,
+                                               jclass klass ATTRIBUTE_UNUSED,
+                                               jobject target,
+                                               jclass clazz,
+                                               jobject thizz) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  if (thizz == nullptr) {
+    env->CallStaticBooleanMethod(clazz, method);
+  } else {
+    env->CallBooleanMethod(thizz, method);
+  }
+}
+
+extern "C" JNIEXPORT
+void JNICALL Java_art_Test993_invokeNativeLong(JNIEnv* env,
+                                               jclass klass ATTRIBUTE_UNUSED,
+                                               jobject target,
+                                               jclass clazz,
+                                               jobject thizz) {
+  jmethodID method = env->FromReflectedMethod(target);
+  if (env->ExceptionCheck()) {
+    return;
+  }
+  if (thizz == nullptr) {
+    env->CallStaticLongMethod(clazz, method);
+  } else {
+    env->CallLongMethod(thizz, method);
+  }
+}
+
+extern "C" JNIEXPORT
 void JNICALL Java_art_Test993_invokeNative(JNIEnv* env,
                                            jclass klass ATTRIBUTE_UNUSED,
                                            jobject target,
diff --git a/test/993-breakpoints/expected.txt b/test/993-breakpoints/expected.txt
index 9621547..1749a77 100644
--- a/test/993-breakpoints/expected.txt
+++ b/test/993-breakpoints/expected.txt
@@ -552,6 +552,107 @@
 			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
 		Invoking "new TestClass4().callPrivateMethod()"
 			Breakpoint: private void art.Test993$TestClass4.privateMethod() @ line=118
+Running Vector constructor
+	Breaking on []
+		Native constructor: public java.util.Vector(), type: class java.util.Vector
+			Created: []
+		Reflective constructor: public java.util.Vector()
+			Created: []
+		Constructing: new Vector()
+			Created: []
+	Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Vector(), type: class java.util.Vector
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Vector()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Vector()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+Running Stack constructor
+	Breaking on []
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Created: []
+		Constructing: new Stack()
+			Created: []
+	Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Created: []
+	Breaking on [public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+	Breaking on [public java.util.Stack() @ <NON-DETERMINISTIC>, public java.util.Vector() @ <NON-DETERMINISTIC>]
+		Native constructor: public java.util.Stack(), type: class java.util.Stack
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Reflective constructor: public java.util.Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+		Constructing: new Stack()
+			Breakpoint: public java.util.Stack() @ line=<NON-DETERMINISTIC>
+			Breakpoint: public java.util.Vector() @ line=<NON-DETERMINISTIC>
+			Created: []
+Running bcp static invoke
+	Breaking on []
+		Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+		Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+		Invoking "Optional::empty"
+	Breaking on [public static java.util.Optional java.util.Optional.empty() @ <NON-DETERMINISTIC>]
+		Native invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+		Reflective invoking: public static java.util.Optional java.util.Optional.empty() args: [this: null]
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+		Invoking "Optional::empty"
+			Breakpoint: public static java.util.Optional java.util.Optional.empty() @ line=<NON-DETERMINISTIC>
+Running bcp private static invoke
+	Breaking on []
+		Native invoking: private static long java.util.Random.seedUniquifier() args: [this: null]
+		Invoking "Random::seedUniquifier"
+	Breaking on [private static long java.util.Random.seedUniquifier() @ <NON-DETERMINISTIC>]
+		Native invoking: private static long java.util.Random.seedUniquifier() args: [this: null]
+			Breakpoint: private static long java.util.Random.seedUniquifier() @ line=<NON-DETERMINISTIC>
+		Invoking "Random::seedUniquifier"
+			Breakpoint: private static long java.util.Random.seedUniquifier() @ line=<NON-DETERMINISTIC>
+Running bcp private invoke
+	Breaking on []
+		Native invoking: private java.math.BigDecimal java.time.Duration.toSeconds() args: [this: PT336H]
+		Invoking "Duration::toSeconds"
+	Breaking on [private java.math.BigDecimal java.time.Duration.toSeconds() @ <NON-DETERMINISTIC>]
+		Native invoking: private java.math.BigDecimal java.time.Duration.toSeconds() args: [this: PT336H]
+			Breakpoint: private java.math.BigDecimal java.time.Duration.toSeconds() @ line=<NON-DETERMINISTIC>
+		Invoking "Duration::toSeconds"
+			Breakpoint: private java.math.BigDecimal java.time.Duration.toSeconds() @ line=<NON-DETERMINISTIC>
+Running bcp invoke
+	Breaking on []
+		Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]]
+		Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]]
+		Invoking "Optional::isPresent"
+	Breaking on [public boolean java.util.Optional.isPresent() @ <NON-DETERMINISTIC>]
+		Native invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test]]
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+		Reflective invoking: public boolean java.util.Optional.isPresent() args: [this: Optional[test2]]
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
+		Invoking "Optional::isPresent"
+			Breakpoint: public boolean java.util.Optional.isPresent() @ line=<NON-DETERMINISTIC>
 Running TestClass1 constructor
 	Breaking on []
 		Native constructor: public art.Test993$TestClass1(), type: class art.Test993$TestClass1
diff --git a/test/993-breakpoints/src/art/Test993.java b/test/993-breakpoints/src/art/Test993.java
index 781ebff..d6a6a67 100644
--- a/test/993-breakpoints/src/art/Test993.java
+++ b/test/993-breakpoints/src/art/Test993.java
@@ -16,20 +16,20 @@
 
 package art;
 
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.Arrays;
 import java.lang.reflect.Executable;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Set;
-import java.util.Spliterator;
-import java.util.Spliterators;
-import java.util.Collection;
+
+import java.time.Duration;
+
 import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.function.IntUnaryOperator;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Stack;
+import java.util.Vector;
+
 import java.util.function.Supplier;
 
 public class Test993 {
@@ -120,7 +120,13 @@
   }
 
   public static void notifyBreakpointReached(Thread thr, Executable e, long loc) {
-    System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + Breakpoint.locationToLine(e, loc));
+    String line;
+    if (e.getDeclaringClass().getPackage().equals(Test993.class.getPackage())) {
+      line = Integer.valueOf(Breakpoint.locationToLine(e, loc)).toString();
+    } else {
+      line = "<NON-DETERMINISTIC>";
+    }
+    System.out.println("\t\t\tBreakpoint: " + e + " @ line=" + line);
   }
 
   public static interface ThrowRunnable extends Runnable {
@@ -180,6 +186,57 @@
 
   public static native void invokeNative(Method m, Class<?> clazz, Object thizz);
 
+  public static class InvokeNativeBool implements Runnable {
+    Method m;
+    Object this_arg;
+    public InvokeNativeBool(Method m, Object this_arg) {
+      this.m = m;
+      this.this_arg = this_arg;
+    }
+
+    @Override
+    public void run() {
+      System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]");
+      invokeNativeBool(m, m.getDeclaringClass(), this_arg);
+    }
+  }
+
+  public static native void invokeNativeBool(Method m, Class<?> clazz, Object thizz);
+
+  public static class InvokeNativeObject implements Runnable {
+    Method m;
+    Object this_arg;
+    public InvokeNativeObject(Method m, Object this_arg) {
+      this.m = m;
+      this.this_arg = this_arg;
+    }
+
+    @Override
+    public void run() {
+      System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]");
+      invokeNativeObject(m, m.getDeclaringClass(), this_arg);
+    }
+  }
+
+  public static native void invokeNativeObject(Method m, Class<?> clazz, Object thizz);
+
+  public static class InvokeNativeLong implements Runnable {
+    Method m;
+    Object this_arg;
+    public InvokeNativeLong(Method m, Object this_arg) {
+      this.m = m;
+      this.this_arg = this_arg;
+    }
+
+    @Override
+    public void run() {
+      System.out.println("\t\tNative invoking: " + m + " args: [this: " + this_arg + "]");
+      invokeNativeLong(m, m.getDeclaringClass(), this_arg);
+    }
+  }
+
+  public static native void invokeNativeLong(Method m, Class<?> clazz, Object thizz);
+
   public static class ConstructDirect implements Runnable {
     String msg;
     Supplier<Object> s;
@@ -258,7 +315,15 @@
   }
 
   private static Breakpoint.Manager.BP BP(Executable m) {
-    return new Breakpoint.Manager.BP(m);
+    return new Breakpoint.Manager.BP(m) {
+      public String toString() {
+        if (method.getDeclaringClass().getPackage().equals(Test993.class.getPackage())) {
+          return super.toString();
+        } else {
+          return method.toString() + " @ <NON-DETERMINISTIC>";
+        }
+      }
+    };
   }
 
   public static void run() throws Exception {
@@ -271,6 +336,7 @@
         Thread.currentThread());
 
     runMethodTests();
+    runBCPMethodTests();
     runConstructorTests();
 
     Breakpoint.stopBreakpointWatch(Thread.currentThread());
@@ -302,6 +368,94 @@
     runTestGroups("TestClass1ext constructor", tc1ext_constructors, tc1ext_bps);
   }
 
+  // These test to make sure we are able to break on functions that might have been quickened or
+  // inlined from the boot-image. These were all chosen for being in the bootclasspath, not being
+  // long enough to prevent inlining, and not being used for the testing framework.
+  public static void runBCPMethodTests() throws Exception {
+    // The methods we will be breaking on.
+    Method bcp_private_method = Duration.class.getDeclaredMethod("toSeconds");
+    Method bcp_virtual_method = Optional.class.getDeclaredMethod("isPresent");
+    Method bcp_static_method = Optional.class.getDeclaredMethod("empty");
+    Method bcp_private_static_method = Random.class.getDeclaredMethod("seedUniquifier");
+
+    // Some constructors we will break on.
+    Constructor<?> bcp_stack_constructor = Stack.class.getConstructor();
+    Constructor<?> bcp_vector_constructor = Vector.class.getConstructor();
+    if (!(Vector.class.isAssignableFrom(Stack.class))) {
+      throw new Error("Expected Stack to extend Vector!");
+    }
+
+    // BCP constructors.
+    Runnable[] vector_constructors = new Runnable[] {
+      new ConstructNative(bcp_vector_constructor),
+      new ConstructReflect(bcp_vector_constructor),
+      new ConstructDirect("new Vector()", Vector::new),
+    };
+    Breakpoint.Manager.BP[] vector_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(bcp_vector_constructor),
+    };
+    runTestGroups("Vector constructor", vector_constructors, vector_breakpoints);
+
+    Runnable[] stack_constructors = new Runnable[] {
+      new ConstructNative(bcp_stack_constructor),
+      new ConstructReflect(bcp_stack_constructor),
+      new ConstructDirect("new Stack()", Stack::new),
+    };
+    Breakpoint.Manager.BP[] stack_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(bcp_stack_constructor), BP(bcp_vector_constructor),
+    };
+    runTestGroups("Stack constructor", stack_constructors, stack_breakpoints);
+
+    // Static function
+    Runnable[] static_invokes = new Runnable[] {
+      new InvokeNativeObject(bcp_static_method, null),
+
+      new InvokeReflect(bcp_static_method, null),
+
+      new InvokeDirect("Optional::empty", () -> { Optional.empty(); }),
+    };
+    Breakpoint.Manager.BP[] static_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(bcp_static_method)
+    };
+    runTestGroups("bcp static invoke", static_invokes, static_breakpoints);
+
+    // Static private class function
+    Runnable[] private_static_invokes = new Runnable[] {
+      new InvokeNativeLong(bcp_private_static_method, null),
+
+      new InvokeDirect("Random::seedUniquifier", () -> { new Random(); }),
+    };
+    Breakpoint.Manager.BP[] private_static_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(bcp_private_static_method)
+    };
+    runTestGroups("bcp private static invoke", private_static_invokes, private_static_breakpoints);
+
+    // private class method
+    Duration test_duration = Duration.ofDays(14);
+    Runnable[] private_invokes = new Runnable[] {
+      new InvokeNativeObject(bcp_private_method, test_duration),
+
+      new InvokeDirect("Duration::toSeconds", () -> { test_duration.multipliedBy(2); }),
+    };
+    Breakpoint.Manager.BP[] private_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(bcp_private_method)
+    };
+    runTestGroups("bcp private invoke", private_invokes, private_breakpoints);
+
+    // class method
+    Runnable[] public_invokes = new Runnable[] {
+      new InvokeNativeBool(bcp_virtual_method, Optional.of("test")),
+
+      new InvokeReflect(bcp_virtual_method, Optional.of("test2")),
+
+      new InvokeDirect("Optional::isPresent", () -> { Optional.of("test3").isPresent(); }),
+    };
+    Breakpoint.Manager.BP[] public_breakpoints = new Breakpoint.Manager.BP[] {
+      BP(bcp_virtual_method)
+    };
+    runTestGroups("bcp invoke", public_invokes, public_breakpoints);
+  }
+
   public static void runMethodTests() throws Exception {
     // The methods we will be breaking on.
     Method breakpoint_method = Test993.class.getDeclaredMethod("breakpoint");