summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/gc/heap-inl.h68
-rw-r--r--runtime/gc/heap.cc125
-rw-r--r--runtime/gc/heap.h58
-rw-r--r--runtime/native/dalvik_system_VMRuntime.cc13
4 files changed, 164 insertions, 100 deletions
diff --git a/runtime/gc/heap-inl.h b/runtime/gc/heap-inl.h
index 61378c90a2..4cfbe2d4bd 100644
--- a/runtime/gc/heap-inl.h
+++ b/runtime/gc/heap-inl.h
@@ -81,7 +81,12 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
size_t bytes_allocated;
size_t usable_size;
size_t new_num_bytes_allocated = 0;
+ bool need_gc = false;
+ uint32_t starting_gc_num; // o.w. GC number at which we observed need for GC.
{
+ // Bytes allocated that includes bulk thread-local buffer allocations in addition to direct
+ // non-TLAB object allocations. Only set for non-thread-local allocation,
+ size_t bytes_tl_bulk_allocated = 0u;
// Do the initial pre-alloc
pre_object_allocated();
ScopedAssertNoThreadSuspension ants("Called PreObjectAllocated, no suspend until alloc");
@@ -132,9 +137,6 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
no_suspend_pre_fence_visitor(obj, usable_size);
QuasiAtomic::ThreadFenceForConstructor();
} else {
- // Bytes allocated that includes bulk thread-local buffer allocations in addition to direct
- // non-TLAB object allocations.
- size_t bytes_tl_bulk_allocated = 0u;
obj = TryToAllocate<kInstrumented, false>(self, allocator, byte_count, &bytes_allocated,
&usable_size, &bytes_tl_bulk_allocated);
if (UNLIKELY(obj == nullptr)) {
@@ -180,22 +182,32 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
}
no_suspend_pre_fence_visitor(obj, usable_size);
QuasiAtomic::ThreadFenceForConstructor();
- if (bytes_tl_bulk_allocated > 0) {
- size_t num_bytes_allocated_before =
- num_bytes_allocated_.fetch_add(bytes_tl_bulk_allocated, std::memory_order_relaxed);
- new_num_bytes_allocated = num_bytes_allocated_before + bytes_tl_bulk_allocated;
- // Only trace when we get an increase in the number of bytes allocated. This happens when
- // obtaining a new TLAB and isn't often enough to hurt performance according to golem.
- if (region_space_) {
- // With CC collector, during a GC cycle, the heap usage increases as
- // there are two copies of evacuated objects. Therefore, add evac-bytes
- // to the heap size. When the GC cycle is not running, evac-bytes
- // are 0, as required.
- TraceHeapSize(new_num_bytes_allocated + region_space_->EvacBytes());
- } else {
- TraceHeapSize(new_num_bytes_allocated);
- }
+ }
+ if (bytes_tl_bulk_allocated > 0) {
+ starting_gc_num = GetCurrentGcNum();
+ size_t num_bytes_allocated_before =
+ num_bytes_allocated_.fetch_add(bytes_tl_bulk_allocated, std::memory_order_relaxed);
+ new_num_bytes_allocated = num_bytes_allocated_before + bytes_tl_bulk_allocated;
+ // Only trace when we get an increase in the number of bytes allocated. This happens when
+ // obtaining a new TLAB and isn't often enough to hurt performance according to golem.
+ if (region_space_) {
+ // With CC collector, during a GC cycle, the heap usage increases as
+ // there are two copies of evacuated objects. Therefore, add evac-bytes
+ // to the heap size. When the GC cycle is not running, evac-bytes
+ // are 0, as required.
+ TraceHeapSize(new_num_bytes_allocated + region_space_->EvacBytes());
+ } else {
+ TraceHeapSize(new_num_bytes_allocated);
+ }
+ // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for the
+ // BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
+ // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant
+ // since the allocator_type should be constant propagated.
+ if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()
+ && UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
+ need_gc = true;
}
+ GetMetrics()->TotalBytesAllocated()->Add(bytes_tl_bulk_allocated);
}
}
if (kIsDebugBuild && Runtime::Current()->IsStarted()) {
@@ -214,7 +226,6 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
} else {
DCHECK(!Runtime::Current()->HasStatsEnabled());
}
- GetMetrics()->TotalBytesAllocated()->Add(bytes_allocated);
if (kInstrumented) {
if (IsAllocTrackingEnabled()) {
// allocation_records_ is not null since it never becomes null after allocation tracking is
@@ -241,14 +252,9 @@ inline mirror::Object* Heap::AllocObjectWithAllocator(Thread* self,
} else {
DCHECK(!gc_stress_mode_);
}
- // IsGcConcurrent() isn't known at compile time so we can optimize by not checking it for
- // the BumpPointer or TLAB allocators. This is nice since it allows the entire if statement to be
- // optimized out. And for the other allocators, AllocatorMayHaveConcurrentGC is a constant since
- // the allocator_type should be constant propagated.
- if (AllocatorMayHaveConcurrentGC(allocator) && IsGcConcurrent()) {
- // New_num_bytes_allocated is zero if we didn't update num_bytes_allocated_.
- // That's fine.
- CheckConcurrentGCForJava(self, new_num_bytes_allocated, &obj);
+ if (need_gc) {
+ // Do this only once thread suspension is allowed again, and we're done with kInstrumented.
+ RequestConcurrentGCAndSaveObject(self, /*force_full=*/ false, starting_gc_num, &obj);
}
VerifyObject(obj);
self->VerifyStack();
@@ -464,14 +470,6 @@ inline bool Heap::ShouldConcurrentGCForJava(size_t new_num_bytes_allocated) {
return new_num_bytes_allocated >= concurrent_start_bytes_;
}
-inline void Heap::CheckConcurrentGCForJava(Thread* self,
- size_t new_num_bytes_allocated,
- ObjPtr<mirror::Object>* obj) {
- if (UNLIKELY(ShouldConcurrentGCForJava(new_num_bytes_allocated))) {
- RequestConcurrentGCAndSaveObject(self, false /* force_full */, obj);
- }
-}
-
} // namespace gc
} // namespace art
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 7cfa0fae3d..a55df4631d 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -366,6 +366,8 @@ Heap::Heap(size_t initial_size,
min_interval_homogeneous_space_compaction_by_oom_(
min_interval_homogeneous_space_compaction_by_oom),
last_time_homogeneous_space_compaction_by_oom_(NanoTime()),
+ gcs_completed_(0u),
+ gcs_requested_(0u),
pending_collector_transition_(nullptr),
pending_heap_trim_(nullptr),
use_homogeneous_space_compaction_for_oom_(use_homogeneous_space_compaction_for_oom),
@@ -1465,7 +1467,7 @@ void Heap::DoPendingCollectorTransition() {
// Invoke CC full compaction.
CollectGarbageInternal(collector::kGcTypeFull,
kGcCauseCollectorTransition,
- /*clear_soft_references=*/false);
+ /*clear_soft_references=*/false, GC_NUM_ANY);
} else {
VLOG(gc) << "CC background compaction ignored due to jank perceptible process state";
}
@@ -1824,6 +1826,7 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self,
(!instrumented && EntrypointsInstrumented())) {
return nullptr;
}
+ uint32_t starting_gc_num = GetCurrentGcNum();
if (last_gc != collector::kGcTypeNone) {
// A GC was in progress and we blocked, retry allocation now that memory has been freed.
mirror::Object* ptr = TryToAllocate<true, false>(self, allocator, alloc_size, bytes_allocated,
@@ -1848,7 +1851,8 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self,
collector::GcType tried_type = next_gc_type_;
if (last_gc < tried_type) {
const bool gc_ran = PERFORM_SUSPENDING_OPERATION(
- CollectGarbageInternal(tried_type, kGcCauseForAlloc, false) != collector::kGcTypeNone);
+ CollectGarbageInternal(tried_type, kGcCauseForAlloc, false, starting_gc_num + 1)
+ != collector::kGcTypeNone);
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
@@ -1871,8 +1875,11 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self,
<< " allocation";
// TODO: Run finalization, but this may cause more allocations to occur.
// We don't need a WaitForGcToComplete here either.
+ // TODO: Should check whether another thread already just ran a GC with soft
+ // references.
DCHECK(!gc_plan_.empty());
- PERFORM_SUSPENDING_OPERATION(CollectGarbageInternal(gc_plan_.back(), kGcCauseForAlloc, true));
+ PERFORM_SUSPENDING_OPERATION(
+ CollectGarbageInternal(gc_plan_.back(), kGcCauseForAlloc, true, GC_NUM_ANY));
if ((was_default_allocator && allocator != GetCurrentAllocator()) ||
(!instrumented && EntrypointsInstrumented())) {
return nullptr;
@@ -2028,7 +2035,7 @@ void Heap::CountInstances(const std::vector<Handle<mirror::Class>>& classes,
void Heap::CollectGarbage(bool clear_soft_references, GcCause cause) {
// Even if we waited for a GC we still need to do another GC since weaks allocated during the
// last GC will not have necessarily been cleared.
- CollectGarbageInternal(gc_plan_.back(), cause, clear_soft_references);
+ CollectGarbageInternal(gc_plan_.back(), cause, clear_soft_references, GC_NUM_ANY);
}
bool Heap::SupportHomogeneousSpaceCompactAndCollectorTransitions() const {
@@ -2295,7 +2302,7 @@ void Heap::PreZygoteFork() {
if (!HasZygoteSpace()) {
// We still want to GC in case there is some unreachable non moving objects that could cause a
// suboptimal bin packing when we compact the zygote space.
- CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false);
+ CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false, GC_NUM_ANY);
// Trim the pages at the end of the non moving space. Trim while not holding zygote lock since
// the trim process may require locking the mutator lock.
non_moving_space_->Trim();
@@ -2552,9 +2559,17 @@ size_t Heap::GetNativeBytes() {
// other things. It seems risky to trigger GCs as a result of such changes.
}
+static inline bool GCNumberLt(uint32_t gcs_completed, uint32_t gcs_requested) {
+ uint32_t difference = gcs_requested - gcs_completed;
+ bool completed_more_than_requested = difference > 0x80000000;
+ return difference > 0 && !completed_more_than_requested;
+}
+
+
collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
GcCause gc_cause,
- bool clear_soft_references) {
+ bool clear_soft_references,
+ uint32_t requested_gc_num) {
Thread* self = Thread::Current();
Runtime* runtime = Runtime::Current();
// If the heap can't run the GC, silently fail and return that no GC was run.
@@ -2584,6 +2599,10 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
MutexLock mu(self, *gc_complete_lock_);
// Ensure there is only one GC at a time.
WaitForGcToCompleteLocked(gc_cause, self);
+ if (requested_gc_num != GC_NUM_ANY && !GCNumberLt(GetCurrentGcNum(), requested_gc_num)) {
+ // The appropriate GC was already triggered elsewhere.
+ return collector::kGcTypeNone;
+ }
compacting_gc = IsMovingGc(collector_type_);
// GC can be disabled if someone has a used GetPrimitiveArrayCritical.
if (compacting_gc && disable_moving_gc_count_ != 0) {
@@ -2594,6 +2613,7 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
return collector::kGcTypeNone;
}
collector_type_running_ = collector_type_;
+ last_gc_cause_ = gc_cause;
}
if (gc_cause == kGcCauseForAlloc && runtime->HasStatsEnabled()) {
++runtime->GetStats()->gc_for_alloc_count;
@@ -2662,6 +2682,7 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
SelfDeletingTask* clear = reference_processor_->CollectClearedReferences(self);
// Grow the heap so that we know when to perform the next GC.
GrowForUtilization(collector, bytes_allocated_before_gc);
+ old_native_bytes_allocated_.store(GetNativeBytes());
LogGC(gc_cause, collector);
FinishGC(self, gc_type);
// Actually enqueue all cleared references. Do this after the GC has officially finished since
@@ -2671,8 +2692,6 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type,
// Inform DDMS that a GC completed.
Dbg::GcDidFinish();
- old_native_bytes_allocated_.store(GetNativeBytes());
-
// Unload native libraries for class unloading. We do this after calling FinishGC to prevent
// deadlocks in case the JNI_OnUnload function does allocations.
{
@@ -2738,6 +2757,9 @@ void Heap::FinishGC(Thread* self, collector::GcType gc_type) {
// Reset.
running_collection_is_blocking_ = false;
thread_running_gc_ = nullptr;
+ if (gc_type != collector::kGcTypeNone) {
+ gcs_completed_.fetch_add(1, std::memory_order_release);
+ }
// Wake anyone who may have been waiting for the GC to complete.
gc_complete_cond_->Broadcast(self);
}
@@ -3000,7 +3022,10 @@ void Heap::PushOnAllocationStackWithInternalGC(Thread* self, ObjPtr<mirror::Obje
// to heap verification requiring that roots are live (either in the live bitmap or in the
// allocation stack).
CHECK(allocation_stack_->AtomicPushBackIgnoreGrowthLimit(obj->Ptr()));
- CollectGarbageInternal(collector::kGcTypeSticky, kGcCauseForAlloc, false);
+ CollectGarbageInternal(collector::kGcTypeSticky,
+ kGcCauseForAlloc,
+ false,
+ GetCurrentGcNum() + 1);
} while (!allocation_stack_->AtomicPushBack(obj->Ptr()));
}
@@ -3020,7 +3045,10 @@ void Heap::PushOnThreadLocalAllocationStackWithInternalGC(Thread* self,
// allocation stack).
CHECK(allocation_stack_->AtomicPushBackIgnoreGrowthLimit(obj->Ptr()));
// Push into the reserve allocation stack.
- CollectGarbageInternal(collector::kGcTypeSticky, kGcCauseForAlloc, false);
+ CollectGarbageInternal(collector::kGcTypeSticky,
+ kGcCauseForAlloc,
+ false,
+ GetCurrentGcNum() + 1);
}
self->SetThreadLocalAllocationStack(start_address, end_address);
// Retry on the new thread-local allocation stack.
@@ -3441,7 +3469,6 @@ collector::GcType Heap::WaitForGcToCompleteLocked(GcCause cause, Thread* self) {
// Don't log fake "GC" types that are only used for debugger or hidden APIs. If we log these,
// it results in log spam. kGcCauseExplicit is already logged in LogGC, so avoid it here too.
if (cause == kGcCauseForAlloc ||
- cause == kGcCauseForNativeAlloc ||
cause == kGcCauseDisableMovingGc) {
VLOG(gc) << "Starting a blocking GC " << cause;
}
@@ -3656,25 +3683,26 @@ void Heap::AddFinalizerReference(Thread* self, ObjPtr<mirror::Object>* object) {
void Heap::RequestConcurrentGCAndSaveObject(Thread* self,
bool force_full,
+ uint32_t observed_gc_num,
ObjPtr<mirror::Object>* obj) {
StackHandleScope<1> hs(self);
HandleWrapperObjPtr<mirror::Object> wrapper(hs.NewHandleWrapper(obj));
- RequestConcurrentGC(self, kGcCauseBackground, force_full);
+ RequestConcurrentGC(self, kGcCauseBackground, force_full, observed_gc_num);
}
class Heap::ConcurrentGCTask : public HeapTask {
public:
- ConcurrentGCTask(uint64_t target_time, GcCause cause, bool force_full)
- : HeapTask(target_time), cause_(cause), force_full_(force_full) {}
+ ConcurrentGCTask(uint64_t target_time, GcCause cause, bool force_full, uint32_t gc_num)
+ : HeapTask(target_time), cause_(cause), force_full_(force_full), my_gc_num_(gc_num) {}
void Run(Thread* self) override {
gc::Heap* heap = Runtime::Current()->GetHeap();
- heap->ConcurrentGC(self, cause_, force_full_);
- heap->ClearConcurrentGCRequest();
+ heap->ConcurrentGC(self, cause_, force_full_, my_gc_num_);
}
private:
const GcCause cause_;
const bool force_full_; // If true, force full (or partial) collection.
+ const uint32_t my_gc_num_; // Sequence number of requested GC.
};
static bool CanAddHeapTask(Thread* self) REQUIRES(!Locks::runtime_shutdown_lock_) {
@@ -3683,20 +3711,24 @@ static bool CanAddHeapTask(Thread* self) REQUIRES(!Locks::runtime_shutdown_lock_
!self->IsHandlingStackOverflow();
}
-void Heap::ClearConcurrentGCRequest() {
- concurrent_gc_pending_.store(false, std::memory_order_relaxed);
-}
-
-void Heap::RequestConcurrentGC(Thread* self, GcCause cause, bool force_full) {
- if (CanAddHeapTask(self) &&
- concurrent_gc_pending_.CompareAndSetStrongSequentiallyConsistent(false, true)) {
- task_processor_->AddTask(self, new ConcurrentGCTask(NanoTime(), // Start straight away.
- cause,
- force_full));
+void Heap::RequestConcurrentGC(Thread* self,
+ GcCause cause,
+ bool force_full,
+ uint32_t observed_gc_num) {
+ uint32_t gcs_requested = gcs_requested_.load(std::memory_order_relaxed);
+ if (!GCNumberLt(observed_gc_num, gcs_requested)) {
+ // Nobody beat us to requesting the next gc after observed_gc_num.
+ if (CanAddHeapTask(self)
+ && gcs_requested_.CompareAndSetStrongRelaxed(gcs_requested, observed_gc_num + 1)) {
+ task_processor_->AddTask(self, new ConcurrentGCTask(NanoTime(), // Start straight away.
+ cause,
+ force_full,
+ observed_gc_num + 1));
+ }
}
}
-void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full) {
+void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t requested_gc_num) {
if (!Runtime::Current()->IsShuttingDown(self)) {
// Wait for any GCs currently running to finish.
if (WaitForGcToComplete(cause, self) == collector::kGcTypeNone) {
@@ -3707,11 +3739,18 @@ void Heap::ConcurrentGC(Thread* self, GcCause cause, bool force_full) {
if (force_full && next_gc_type == collector::kGcTypeSticky) {
next_gc_type = NonStickyGcType();
}
- if (CollectGarbageInternal(next_gc_type, cause, false) == collector::kGcTypeNone) {
+ if (CollectGarbageInternal(next_gc_type, cause, false, requested_gc_num)
+ == collector::kGcTypeNone) {
for (collector::GcType gc_type : gc_plan_) {
// Attempt to run the collector, if we succeed, we are done.
+ uint32_t gcs_completed = GetCurrentGcNum();
+ if (!GCNumberLt(gcs_completed, requested_gc_num)) {
+ // Somebody did it for us.
+ break;
+ }
if (gc_type > next_gc_type &&
- CollectGarbageInternal(gc_type, cause, false) != collector::kGcTypeNone) {
+ CollectGarbageInternal(gc_type, cause, false, requested_gc_num)
+ != collector::kGcTypeNone) {
break;
}
}
@@ -3752,7 +3791,7 @@ void Heap::RequestCollectorTransition(CollectorType desired_collector_type, uint
const uint64_t target_time = NanoTime() + delta_time;
{
MutexLock mu(self, *pending_task_lock_);
- // If we have an existing collector transition, update the targe time to be the new target.
+ // If we have an existing collector transition, update the target time to be the new target.
if (pending_collector_transition_ != nullptr) {
task_processor_->UpdateTargetRunTime(self, pending_collector_transition_, target_time);
return;
@@ -3856,10 +3895,6 @@ void Heap::RevokeAllThreadLocalBuffers() {
}
}
-bool Heap::IsGCRequestPending() const {
- return concurrent_gc_pending_.load(std::memory_order_relaxed);
-}
-
void Heap::RunFinalization(JNIEnv* env, uint64_t timeout) {
env->CallStaticVoidMethod(WellKnownClasses::dalvik_system_VMRuntime,
WellKnownClasses::dalvik_system_VMRuntime_runFinalization,
@@ -3913,21 +3948,35 @@ inline float Heap::NativeMemoryOverTarget(size_t current_native_bytes, bool is_g
inline void Heap::CheckGCForNative(Thread* self) {
bool is_gc_concurrent = IsGcConcurrent();
+ uint32_t starting_gc_num = GetCurrentGcNum();
size_t current_native_bytes = GetNativeBytes();
float gc_urgency = NativeMemoryOverTarget(current_native_bytes, is_gc_concurrent);
if (UNLIKELY(gc_urgency >= 1.0)) {
if (is_gc_concurrent) {
- RequestConcurrentGC(self, kGcCauseForNativeAlloc, /*force_full=*/true);
+ RequestConcurrentGC(self, kGcCauseForNativeAlloc, /*force_full=*/true, starting_gc_num);
if (gc_urgency > kStopForNativeFactor
&& current_native_bytes > stop_for_native_allocs_) {
// We're in danger of running out of memory due to rampant native allocation.
if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
LOG(INFO) << "Stopping for native allocation, urgency: " << gc_urgency;
}
- WaitForGcToComplete(kGcCauseForNativeAlloc, self);
+ // Count how many times we do this, so we can warn if this becomes excessive.
+ // Stop after a while out of excessive caution.
+ static constexpr int kGcWaitIters = 50;
+ for (int i = 1; i <= kGcWaitIters; ++i) {
+ if (GCNumberLt(starting_gc_num, starting_gc_num)
+ || WaitForGcToComplete(kGcCauseForNativeAlloc, self) != collector::kGcTypeNone) {
+ break;
+ }
+ if (i % 10 == 0) {
+ LOG(WARNING) << "Slept " << i << " times in native allocation, waiting for GC";
+ }
+ static constexpr int kGcWaitSleepMicros = 2000;
+ usleep(kGcWaitSleepMicros); // Encourage our requested GC to start.
+ }
}
} else {
- CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAlloc, false);
+ CollectGarbageInternal(NonStickyGcType(), kGcCauseForNativeAlloc, false, starting_gc_num + 1);
}
}
}
@@ -4372,7 +4421,7 @@ class Heap::TriggerPostForkCCGcTask : public HeapTask {
// Trigger a GC, if not already done. The first GC after fork, whenever it
// takes place, will adjust the thresholds to normal levels.
if (heap->target_footprint_.load(std::memory_order_relaxed) == heap->growth_limit_) {
- heap->RequestConcurrentGC(self, kGcCauseBackground, false);
+ heap->RequestConcurrentGC(self, kGcCauseBackground, false, heap->GetCurrentGcNum());
}
}
};
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 35dce5ef81..450f38732f 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -376,13 +376,14 @@ class Heap {
REQUIRES(Locks::heap_bitmap_lock_)
REQUIRES_SHARED(Locks::mutator_lock_);
- // Initiates an explicit garbage collection.
+ // Initiates an explicit garbage collection. Guarantees that a GC started after this call has
+ // completed.
void CollectGarbage(bool clear_soft_references, GcCause cause = kGcCauseExplicit)
REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
- // Does a concurrent GC, should only be called by the GC daemon thread
- // through runtime.
- void ConcurrentGC(Thread* self, GcCause cause, bool force_full)
+ // Does a concurrent GC, provided the GC numbered requested_gc_num has not already been
+ // completed. Should only be called by the GC daemon thread through runtime.
+ void ConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t requested_gc_num)
REQUIRES(!Locks::runtime_shutdown_lock_, !*gc_complete_lock_,
!*pending_task_lock_, !process_state_update_lock_);
@@ -452,7 +453,8 @@ class Heap {
void SetIdealFootprint(size_t max_allowed_footprint);
// Blocks the caller until the garbage collector becomes idle and returns the type of GC we
- // waited for.
+ // waited for. Only waits for running collections, ignoring a requested but unstarted GC. Only
+ // heuristic, since a new GC may have started by the time we return.
collector::GcType WaitForGcToComplete(GcCause cause, Thread* self) REQUIRES(!*gc_complete_lock_);
// Update the heap's process state to a new value, may cause compaction to occur.
@@ -815,8 +817,17 @@ class Heap {
// Request an asynchronous trim.
void RequestTrim(Thread* self) REQUIRES(!*pending_task_lock_);
- // Request asynchronous GC.
- void RequestConcurrentGC(Thread* self, GcCause cause, bool force_full)
+ // Retrieve the current GC number, i.e. the number n such that we completed n GCs so far.
+ // Provides acquire ordering, so that if we read this first, and then check whether a GC is
+ // required, we know that the GC number read actually preceded the test.
+ uint32_t GetCurrentGcNum() {
+ return gcs_completed_.load(std::memory_order_acquire);
+ }
+
+ // Request asynchronous GC. Observed_gc_num is the value of GetCurrentGcNum() when we started to
+ // evaluate the GC triggering condition. If a GC has been completed since then, we consider our
+ // job done.
+ void RequestConcurrentGC(Thread* self, GcCause cause, bool force_full, uint32_t observed_gc_num)
REQUIRES(!*pending_task_lock_);
// Whether or not we may use a garbage collector, used so that we only create collectors we need.
@@ -1002,11 +1013,6 @@ class Heap {
// Checks whether we should garbage collect:
ALWAYS_INLINE bool ShouldConcurrentGCForJava(size_t new_num_bytes_allocated);
float NativeMemoryOverTarget(size_t current_native_bytes, bool is_gc_concurrent);
- ALWAYS_INLINE void CheckConcurrentGCForJava(Thread* self,
- size_t new_num_bytes_allocated,
- ObjPtr<mirror::Object>* obj)
- REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
void CheckGCForNative(Thread* self)
REQUIRES(!*pending_task_lock_, !*gc_complete_lock_, !process_state_update_lock_);
@@ -1092,16 +1098,25 @@ class Heap {
void RequestCollectorTransition(CollectorType desired_collector_type, uint64_t delta_time)
REQUIRES(!*pending_task_lock_);
- void RequestConcurrentGCAndSaveObject(Thread* self, bool force_full, ObjPtr<mirror::Object>* obj)
+ void RequestConcurrentGCAndSaveObject(Thread* self,
+ bool force_full,
+ uint32_t observed_gc_num,
+ ObjPtr<mirror::Object>* obj)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!*pending_task_lock_);
- bool IsGCRequestPending() const;
+
+ static constexpr uint32_t GC_NUM_ANY = std::numeric_limits<uint32_t>::max();
// Sometimes CollectGarbageInternal decides to run a different Gc than you requested. Returns
- // which type of Gc was actually ran.
+ // which type of Gc was actually run.
+ // We pass in the intended GC sequence number to ensure that multiple approximately concurrent
+ // requests result in a single GC; clearly redundant request will be pruned. A requested_gc_num
+ // of GC_NUM_ANY indicates that we should not prune redundant requests. (In the unlikely case
+ // that gcs_completed_ gets this big, we just accept a potential extra GC or two.)
collector::GcType CollectGarbageInternal(collector::GcType gc_plan,
GcCause gc_cause,
- bool clear_soft_references)
+ bool clear_soft_references,
+ uint32_t requested_gc_num)
REQUIRES(!*gc_complete_lock_, !Locks::heap_bitmap_lock_, !Locks::thread_suspend_count_lock_,
!*pending_task_lock_, !process_state_update_lock_);
@@ -1168,7 +1183,6 @@ class Heap {
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
- void ClearConcurrentGCRequest();
void ClearPendingTrim(Thread* self) REQUIRES(!*pending_task_lock_);
void ClearPendingCollectorTransition(Thread* self) REQUIRES(!*pending_task_lock_);
@@ -1550,8 +1564,14 @@ class Heap {
// Count for performed homogeneous space compaction.
Atomic<size_t> count_performed_homogeneous_space_compaction_;
- // Whether or not a concurrent GC is pending.
- Atomic<bool> concurrent_gc_pending_;
+ // The number of garbage collections (either young or full, not trims or the like) we have
+ // completed since heap creation. We guard against wrapping, though that's unlikely.
+ // Increment is guarded by gc_complete_lock_.
+ Atomic<uint32_t> gcs_completed_;
+
+ // The number of garbage collections we've scheduled. Normally either gcs_complete_ or
+ // gcs_complete + 1.
+ Atomic<uint32_t> gcs_requested_;
// Active tasks which we can modify (change target time, desired collector type, etc..).
CollectorTransitionTask* pending_collector_transition_ GUARDED_BY(pending_task_lock_);
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index eb1814d687..a483ae714d 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -336,18 +336,16 @@ static void VMRuntime_trimHeap(JNIEnv* env, jobject) {
Runtime::Current()->GetHeap()->Trim(ThreadForEnv(env));
}
-static void VMRuntime_concurrentGC(JNIEnv* env, jobject) {
- Runtime::Current()->GetHeap()->ConcurrentGC(ThreadForEnv(env), gc::kGcCauseBackground, true);
-}
-
static void VMRuntime_requestHeapTrim(JNIEnv* env, jobject) {
Runtime::Current()->GetHeap()->RequestTrim(ThreadForEnv(env));
}
static void VMRuntime_requestConcurrentGC(JNIEnv* env, jobject) {
- Runtime::Current()->GetHeap()->RequestConcurrentGC(ThreadForEnv(env),
- gc::kGcCauseBackground,
- true);
+ gc::Heap *heap = Runtime::Current()->GetHeap();
+ heap->RequestConcurrentGC(ThreadForEnv(env),
+ gc::kGcCauseBackground,
+ true,
+ heap->GetCurrentGcNum());
}
static void VMRuntime_startHeapTaskProcessor(JNIEnv* env, jobject) {
@@ -497,7 +495,6 @@ static JNINativeMethod gMethods[] = {
NATIVE_METHOD(VMRuntime, clampGrowthLimit, "()V"),
NATIVE_METHOD(VMRuntime, classPath, "()Ljava/lang/String;"),
NATIVE_METHOD(VMRuntime, clearGrowthLimit, "()V"),
- NATIVE_METHOD(VMRuntime, concurrentGC, "()V"),
NATIVE_METHOD(VMRuntime, setHiddenApiExemptions, "([Ljava/lang/String;)V"),
NATIVE_METHOD(VMRuntime, setHiddenApiAccessLogSamplingRate, "(I)V"),
NATIVE_METHOD(VMRuntime, getTargetHeapUtilization, "()F"),