Synchronize JNI critical calls with the CC collector thread flip.
JNI critical calls (like GetArrayElementsCritical) would need to block
for the whole GC run to finish if the CC collector GC is ongoing. This
CL changes it so that they don't need to block for the GC run, but
only for the duration of the thread flip operation, which is much
shorter. This is valid due to the to-space invariant.
Bug: 12687968
Bug: 19235243
Change-Id: I1b6b4ae4fa539ddc0ec50b10ae8c8709f2a12fe8
diff --git a/runtime/debugger.cc b/runtime/debugger.cc
index 0cbbb79..67099d7 100644
--- a/runtime/debugger.cc
+++ b/runtime/debugger.cc
@@ -2094,6 +2094,7 @@
case kWaitingInMainSignalCatcherLoop:
case kWaitingPerformingGc:
case kWaitingWeakGcRootRead:
+ case kWaitingForGcThreadFlip:
case kWaiting:
return JDWP::TS_WAIT;
// Don't add a 'default' here so the compiler can spot incompatible enum changes.
diff --git a/runtime/gc/collector/concurrent_copying.cc b/runtime/gc/collector/concurrent_copying.cc
index 65e946f..a5bc60a 100644
--- a/runtime/gc/collector/concurrent_copying.cc
+++ b/runtime/gc/collector/concurrent_copying.cc
@@ -261,8 +261,10 @@
gc_barrier_->Init(self, 0);
ThreadFlipVisitor thread_flip_visitor(this, heap_->use_tlab_);
FlipCallback flip_callback(this);
+ heap_->ThreadFlipBegin(self); // Sync with JNI critical calls.
size_t barrier_count = Runtime::Current()->FlipThreadRoots(
&thread_flip_visitor, &flip_callback, this);
+ heap_->ThreadFlipEnd(self);
{
ScopedThreadStateChange tsc(self, kWaitingForCheckPointsToRun);
gc_barrier_->Increment(self, barrier_count);
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index d7f918b..aed7c62 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -161,6 +161,8 @@
zygote_creation_lock_("zygote creation lock", kZygoteCreationLock),
zygote_space_(nullptr),
large_object_threshold_(large_object_threshold),
+ disable_thread_flip_count_(0),
+ thread_flip_running_(false),
collector_type_running_(kCollectorTypeNone),
last_gc_type_(collector::kGcTypeNone),
next_gc_type_(collector::kGcTypePartial),
@@ -480,6 +482,9 @@
gc_complete_lock_ = new Mutex("GC complete lock");
gc_complete_cond_.reset(new ConditionVariable("GC complete condition variable",
*gc_complete_lock_));
+ thread_flip_lock_ = new Mutex("GC thread flip lock");
+ thread_flip_cond_.reset(new ConditionVariable("GC thread flip condition variable",
+ *thread_flip_lock_));
task_processor_.reset(new TaskProcessor());
reference_processor_.reset(new ReferenceProcessor());
pending_task_lock_ = new Mutex("Pending task lock");
@@ -770,6 +775,71 @@
--disable_moving_gc_count_;
}
+void Heap::IncrementDisableThreadFlip(Thread* self) {
+ // Supposed to be called by mutators. If thread_flip_running_ is true, block. Otherwise, go ahead.
+ CHECK(kUseReadBarrier);
+ ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+ MutexLock mu(self, *thread_flip_lock_);
+ bool has_waited = false;
+ uint64_t wait_start = NanoTime();
+ while (thread_flip_running_) {
+ has_waited = true;
+ thread_flip_cond_->Wait(self);
+ }
+ ++disable_thread_flip_count_;
+ if (has_waited) {
+ uint64_t wait_time = NanoTime() - wait_start;
+ total_wait_time_ += wait_time;
+ if (wait_time > long_pause_log_threshold_) {
+ LOG(INFO) << __FUNCTION__ << " blocked for " << PrettyDuration(wait_time);
+ }
+ }
+}
+
+void Heap::DecrementDisableThreadFlip(Thread* self) {
+ // Supposed to be called by mutators. Decrement disable_thread_flip_count_ and potentially wake up
+ // the GC waiting before doing a thread flip.
+ CHECK(kUseReadBarrier);
+ MutexLock mu(self, *thread_flip_lock_);
+ CHECK_GT(disable_thread_flip_count_, 0U);
+ --disable_thread_flip_count_;
+ thread_flip_cond_->Broadcast(self);
+}
+
+void Heap::ThreadFlipBegin(Thread* self) {
+ // Supposed to be called by GC. Set thread_flip_running_ to be true. If disable_thread_flip_count_
+ // > 0, block. Otherwise, go ahead.
+ CHECK(kUseReadBarrier);
+ ScopedThreadStateChange tsc(self, kWaitingForGcThreadFlip);
+ MutexLock mu(self, *thread_flip_lock_);
+ bool has_waited = false;
+ uint64_t wait_start = NanoTime();
+ CHECK(!thread_flip_running_);
+ // Set this to true before waiting so that a new mutator entering a JNI critical won't starve GC.
+ thread_flip_running_ = true;
+ while (disable_thread_flip_count_ > 0) {
+ has_waited = true;
+ thread_flip_cond_->Wait(self);
+ }
+ if (has_waited) {
+ uint64_t wait_time = NanoTime() - wait_start;
+ total_wait_time_ += wait_time;
+ if (wait_time > long_pause_log_threshold_) {
+ LOG(INFO) << __FUNCTION__ << " blocked for " << PrettyDuration(wait_time);
+ }
+ }
+}
+
+void Heap::ThreadFlipEnd(Thread* self) {
+ // Supposed to be called by GC. Set thread_flip_running_ to false and potentially wake up mutators
+ // waiting before doing a JNI critical.
+ CHECK(kUseReadBarrier);
+ MutexLock mu(self, *thread_flip_lock_);
+ CHECK(thread_flip_running_);
+ thread_flip_running_ = false;
+ thread_flip_cond_->Broadcast(self);
+}
+
void Heap::UpdateProcessState(ProcessState process_state) {
if (process_state_ != process_state) {
process_state_ = process_state;
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index d94f109..85688ae 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -300,6 +300,12 @@
void IncrementDisableMovingGC(Thread* self) REQUIRES(!*gc_complete_lock_);
void DecrementDisableMovingGC(Thread* self) REQUIRES(!*gc_complete_lock_);
+ // Temporarily disable thread flip for JNI critical calls.
+ void IncrementDisableThreadFlip(Thread* self) REQUIRES(!*thread_flip_lock_);
+ void DecrementDisableThreadFlip(Thread* self) REQUIRES(!*thread_flip_lock_);
+ void ThreadFlipBegin(Thread* self) REQUIRES(!*thread_flip_lock_);
+ void ThreadFlipEnd(Thread* self) REQUIRES(!*thread_flip_lock_);
+
// Clear all of the mark bits, doesn't clear bitmaps which have the same live bits as mark bits.
void ClearMarkedObjects() REQUIRES(Locks::heap_bitmap_lock_);
@@ -1065,6 +1071,12 @@
Mutex* gc_complete_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
std::unique_ptr<ConditionVariable> gc_complete_cond_ GUARDED_BY(gc_complete_lock_);
+ // Used to synchronize between JNI critical calls and the thread flip of the CC collector.
+ Mutex* thread_flip_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ std::unique_ptr<ConditionVariable> thread_flip_cond_ GUARDED_BY(thread_flip_lock_);
+ size_t disable_thread_flip_count_ GUARDED_BY(thread_flip_lock_);
+ bool thread_flip_running_ GUARDED_BY(thread_flip_lock_);
+
// Reference processor;
std::unique_ptr<ReferenceProcessor> reference_processor_;
diff --git a/runtime/jni_internal.cc b/runtime/jni_internal.cc
index 6a716b5..6bc1829 100644
--- a/runtime/jni_internal.cc
+++ b/runtime/jni_internal.cc
@@ -1729,7 +1729,13 @@
if (heap->IsMovableObject(s)) {
StackHandleScope<1> hs(soa.Self());
HandleWrapper<mirror::String> h(hs.NewHandleWrapper(&s));
- heap->IncrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->IncrementDisableMovingGC(soa.Self());
+ } else {
+ // For the CC collector, we only need to wait for the thread flip rather than the whole GC
+ // to occur thanks to the to-space invariant.
+ heap->IncrementDisableThreadFlip(soa.Self());
+ }
}
if (is_copy != nullptr) {
*is_copy = JNI_FALSE;
@@ -1744,7 +1750,11 @@
gc::Heap* heap = Runtime::Current()->GetHeap();
mirror::String* s = soa.Decode<mirror::String*>(java_string);
if (heap->IsMovableObject(s)) {
- heap->DecrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->DecrementDisableMovingGC(soa.Self());
+ } else {
+ heap->DecrementDisableThreadFlip(soa.Self());
+ }
}
}
@@ -1891,7 +1901,13 @@
}
gc::Heap* heap = Runtime::Current()->GetHeap();
if (heap->IsMovableObject(array)) {
- heap->IncrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->IncrementDisableMovingGC(soa.Self());
+ } else {
+ // For the CC collector, we only need to wait for the thread flip rather than the whole GC
+ // to occur thanks to the to-space invariant.
+ heap->IncrementDisableThreadFlip(soa.Self());
+ }
// Re-decode in case the object moved since IncrementDisableGC waits for GC to complete.
array = soa.Decode<mirror::Array*>(java_array);
}
@@ -2437,7 +2453,11 @@
delete[] reinterpret_cast<uint64_t*>(elements);
} else if (heap->IsMovableObject(array)) {
// Non copy to a movable object must means that we had disabled the moving GC.
- heap->DecrementDisableMovingGC(soa.Self());
+ if (!kUseReadBarrier) {
+ heap->DecrementDisableMovingGC(soa.Self());
+ } else {
+ heap->DecrementDisableThreadFlip(soa.Self());
+ }
}
}
}
diff --git a/runtime/native/java_lang_Thread.cc b/runtime/native/java_lang_Thread.cc
index c76f6ee..c75ff78 100644
--- a/runtime/native/java_lang_Thread.cc
+++ b/runtime/native/java_lang_Thread.cc
@@ -90,6 +90,7 @@
case kWaitingForMethodTracingStart: return kJavaWaiting;
case kWaitingForVisitObjects: return kJavaWaiting;
case kWaitingWeakGcRootRead: return kJavaWaiting;
+ case kWaitingForGcThreadFlip: return kJavaWaiting;
case kSuspended: return kJavaRunnable;
// Don't add a 'default' here so the compiler can spot incompatible enum changes.
}
diff --git a/runtime/thread_state.h b/runtime/thread_state.h
index a11d213..8f2f70f 100644
--- a/runtime/thread_state.h
+++ b/runtime/thread_state.h
@@ -44,6 +44,7 @@
kWaitingForVisitObjects, // WAITING TS_WAIT waiting for visiting objects
kWaitingForGetObjectsAllocated, // WAITING TS_WAIT waiting for getting the number of allocated objects
kWaitingWeakGcRootRead, // WAITING TS_WAIT waiting on the GC to read a weak root
+ kWaitingForGcThreadFlip, // WAITING TS_WAIT waiting on the GC thread flip (CC collector) to finish
kStarting, // NEW TS_WAIT native thread started, not yet ready to run managed code
kNative, // RUNNABLE TS_RUNNING running in a JNI native method
kSuspended, // RUNNABLE TS_RUNNING suspended by GC or debugger