Update target footprint on process state switch
Test: Greenday/MPTS test
Bug: 145161943
Change-Id: I519fda7e15ca1acc68c5958ef67f1aae56c53662
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index de15927..0601b8d 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -282,6 +282,10 @@
capacity_(capacity),
growth_limit_(growth_limit),
target_footprint_(initial_size),
+ // Using kPostMonitorLock as a lock at kDefaultMutexLevel is acquired after
+ // this one.
+ process_state_update_lock_("process state update lock", kPostMonitorLock),
+ min_foreground_target_footprint_(0),
concurrent_start_bytes_(std::numeric_limits<size_t>::max()),
total_bytes_freed_ever_(0),
total_objects_freed_ever_(0),
@@ -976,12 +980,24 @@
thread_flip_cond_->Broadcast(self);
}
+void Heap::GrowHeapOnJankPerceptibleSwitch() {
+ MutexLock mu(Thread::Current(), process_state_update_lock_);
+ size_t orig_target_footprint = target_footprint_.load(std::memory_order_relaxed);
+ if (orig_target_footprint < min_foreground_target_footprint_) {
+ target_footprint_.compare_exchange_strong(orig_target_footprint,
+ min_foreground_target_footprint_,
+ std::memory_order_relaxed);
+ }
+ min_foreground_target_footprint_ = 0;
+}
+
void Heap::UpdateProcessState(ProcessState old_process_state, ProcessState new_process_state) {
if (old_process_state != new_process_state) {
const bool jank_perceptible = new_process_state == kProcessStateJankPerceptible;
if (jank_perceptible) {
// Transition back to foreground right away to prevent jank.
RequestCollectorTransition(foreground_collector_type_, 0);
+ GrowHeapOnJankPerceptibleSwitch();
} else {
// Don't delay for debug builds since we may want to stress test the GC.
// If background_collector_type_ is kCollectorTypeHomogeneousSpaceCompact then we have
@@ -3499,23 +3515,19 @@
const size_t bytes_allocated = GetBytesAllocated();
// Trace the new heap size after the GC is finished.
TraceHeapSize(bytes_allocated);
- uint64_t target_size;
+ uint64_t target_size, grow_bytes;
collector::GcType gc_type = collector_ran->GetGcType();
+ MutexLock mu(Thread::Current(), process_state_update_lock_);
// Use the multiplier to grow more for foreground.
- const double multiplier = HeapGrowthMultiplier(); // Use the multiplier to grow more for
- // foreground.
- const size_t adjusted_min_free = static_cast<size_t>(min_free_ * multiplier);
- const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
+ const double multiplier = HeapGrowthMultiplier();
if (gc_type != collector::kGcTypeSticky) {
// Grow the heap for non sticky GC.
uint64_t delta = bytes_allocated * (1.0 / GetTargetHeapUtilization() - 1.0);
DCHECK_LE(delta, std::numeric_limits<size_t>::max()) << "bytes_allocated=" << bytes_allocated
<< " target_utilization_=" << target_utilization_;
- target_size = bytes_allocated + delta * multiplier;
- target_size = std::min(target_size,
- static_cast<uint64_t>(bytes_allocated + adjusted_max_free));
- target_size = std::max(target_size,
- static_cast<uint64_t>(bytes_allocated + adjusted_min_free));
+ grow_bytes = std::min(delta, static_cast<uint64_t>(max_free_));
+ grow_bytes = std::max(grow_bytes, static_cast<uint64_t>(min_free_));
+ target_size = bytes_allocated + static_cast<uint64_t>(grow_bytes * multiplier);
next_gc_type_ = collector::kGcTypeSticky;
} else {
collector::GcType non_sticky_gc_type = NonStickyGcType();
@@ -3545,15 +3557,28 @@
next_gc_type_ = non_sticky_gc_type;
}
// If we have freed enough memory, shrink the heap back down.
+ const size_t adjusted_max_free = static_cast<size_t>(max_free_ * multiplier);
if (bytes_allocated + adjusted_max_free < target_footprint) {
target_size = bytes_allocated + adjusted_max_free;
+ grow_bytes = max_free_;
} else {
target_size = std::max(bytes_allocated, target_footprint);
+ // The same whether jank perceptible or not; just avoid the adjustment.
+ grow_bytes = 0;
}
}
CHECK_LE(target_size, std::numeric_limits<size_t>::max());
if (!ignore_target_footprint_) {
SetIdealFootprint(target_size);
+ // Store target size (computed with foreground heap growth multiplier) for updating
+ // target_footprint_ when process state switches to foreground.
+ // target_size = 0 ensures that target_footprint_ is not updated on
+ // process-state switch.
+ min_foreground_target_footprint_ =
+ (multiplier <= 1.0 && grow_bytes > 0)
+ ? bytes_allocated + static_cast<size_t>(grow_bytes * foreground_heap_growth_multiplier_)
+ : 0;
+
if (IsGcConcurrent()) {
const uint64_t freed_bytes = current_gc_iteration_.GetFreedBytes() +
current_gc_iteration_.GetFreedLargeObjectBytes() +
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index f8bbe10..9d40b93 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -234,6 +234,7 @@
REQUIRES(!*gc_complete_lock_,
!*pending_task_lock_,
!*backtrace_lock_,
+ !process_state_update_lock_,
!Roles::uninterruptible_) {
return AllocObjectWithAllocator<kInstrumented>(self,
klass,
@@ -251,6 +252,7 @@
REQUIRES(!*gc_complete_lock_,
!*pending_task_lock_,
!*backtrace_lock_,
+ !process_state_update_lock_,
!Roles::uninterruptible_) {
return AllocObjectWithAllocator<kInstrumented>(self,
klass,
@@ -269,6 +271,7 @@
REQUIRES(!*gc_complete_lock_,
!*pending_task_lock_,
!*backtrace_lock_,
+ !process_state_update_lock_,
!Roles::uninterruptible_);
AllocatorType GetCurrentAllocator() const {
@@ -297,14 +300,14 @@
// Inform the garbage collector of a non-malloc allocated native memory that might become
// reclaimable in the future as a result of Java garbage collection.
void RegisterNativeAllocation(JNIEnv* env, size_t bytes)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
void RegisterNativeFree(JNIEnv* env, size_t bytes);
// Notify the garbage collector of malloc allocations that might be reclaimable
// as a result of Java garbage collection. Each such call represents approximately
// kNotifyNativeInterval such allocations.
void NotifyNativeAllocations(JNIEnv* env)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
uint32_t GetNotifyNativeInterval() {
return kNotifyNativeInterval;
@@ -370,12 +373,13 @@
// Initiates an explicit garbage collection.
void CollectGarbage(bool clear_soft_references, GcCause cause = kGcCauseExplicit)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ 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)
- REQUIRES(!Locks::runtime_shutdown_lock_, !*gc_complete_lock_, !*pending_task_lock_);
+ REQUIRES(!Locks::runtime_shutdown_lock_, !*gc_complete_lock_,
+ !*pending_task_lock_, !process_state_update_lock_);
// Implements VMDebug.countInstancesOfClass and JDWP VM_InstanceCount.
// The boolean decides whether to use IsAssignableFrom or == when comparing classes.
@@ -465,7 +469,7 @@
// Update the heap's process state to a new value, may cause compaction to occur.
void UpdateProcessState(ProcessState old_process_state, ProcessState new_process_state)
- REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
+ REQUIRES(!*pending_task_lock_, !*gc_complete_lock_, !process_state_update_lock_);
bool HaveContinuousSpaces() const NO_THREAD_SAFETY_ANALYSIS {
// No lock since vector empty is thread safe.
@@ -625,7 +629,8 @@
void DumpForSigQuit(std::ostream& os) REQUIRES(!*gc_complete_lock_);
// Do a pending collector transition.
- void DoPendingCollectorTransition() REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ void DoPendingCollectorTransition()
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
// Deflate monitors, ... and trim the spaces.
void Trim(Thread* self) REQUIRES(!*gc_complete_lock_);
@@ -886,7 +891,8 @@
void DisableGCForShutdown() REQUIRES(!*gc_complete_lock_);
// Create a new alloc space and compact default alloc space to it.
- HomogeneousSpaceCompactResult PerformHomogeneousSpaceCompact() REQUIRES(!*gc_complete_lock_);
+ HomogeneousSpaceCompactResult PerformHomogeneousSpaceCompact()
+ REQUIRES(!*gc_complete_lock_, !process_state_update_lock_);
bool SupportHomogeneousSpaceCompactAndCollectorTransitions() const;
// Install an allocation listener.
@@ -987,7 +993,7 @@
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
void CheckGCForNative(Thread* self)
- REQUIRES(!*pending_task_lock_, !*gc_complete_lock_);
+ REQUIRES(!*pending_task_lock_, !*gc_complete_lock_, !process_state_update_lock_);
accounting::ObjectStack* GetMarkStack() {
return mark_stack_.get();
@@ -1000,7 +1006,8 @@
size_t byte_count,
const PreFenceVisitor& pre_fence_visitor)
REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !*backtrace_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_,
+ !*backtrace_lock_, !process_state_update_lock_);
// Handles Allocate()'s slow allocation path with GC involved after
// an initial allocation attempt failed.
@@ -1080,7 +1087,7 @@
GcCause gc_cause,
bool clear_soft_references)
REQUIRES(!*gc_complete_lock_, !Locks::heap_bitmap_lock_, !Locks::thread_suspend_count_lock_,
- !*pending_task_lock_);
+ !*pending_task_lock_, !process_state_update_lock_);
void PreGcVerification(collector::GarbageCollector* gc)
REQUIRES(!Locks::mutator_lock_, !*gc_complete_lock_);
@@ -1117,7 +1124,8 @@
// collection. bytes_allocated_before_gc is used to measure bytes / second for the period which
// the GC was run.
void GrowForUtilization(collector::GarbageCollector* collector_ran,
- size_t bytes_allocated_before_gc = 0);
+ size_t bytes_allocated_before_gc = 0)
+ REQUIRES(!process_state_update_lock_);
size_t GetPercentFree();
@@ -1136,13 +1144,13 @@
// Push an object onto the allocation stack.
void PushOnAllocationStack(Thread* self, ObjPtr<mirror::Object>* obj)
REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
void PushOnAllocationStackWithInternalGC(Thread* self, ObjPtr<mirror::Object>* obj)
REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
void PushOnThreadLocalAllocationStackWithInternalGC(Thread* thread, ObjPtr<mirror::Object>* obj)
REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !process_state_update_lock_);
void ClearConcurrentGCRequest();
void ClearPendingTrim(Thread* self) REQUIRES(!*pending_task_lock_);
@@ -1175,7 +1183,8 @@
// GC stress mode attempts to do one GC per unique backtrace.
void CheckGcStressMode(Thread* self, ObjPtr<mirror::Object>* obj)
REQUIRES_SHARED(Locks::mutator_lock_)
- REQUIRES(!*gc_complete_lock_, !*pending_task_lock_, !*backtrace_lock_);
+ REQUIRES(!*gc_complete_lock_, !*pending_task_lock_,
+ !*backtrace_lock_, !process_state_update_lock_);
collector::GcType NonStickyGcType() const {
return HasZygoteSpace() ? collector::kGcTypePartial : collector::kGcTypeFull;
@@ -1192,6 +1201,10 @@
ALWAYS_INLINE void IncrementNumberOfBytesFreedRevoke(size_t freed_bytes_revoke);
+ // On switching app from background to foreground, grow the heap size
+ // to incorporate foreground heap growth multiplier.
+ void GrowHeapOnJankPerceptibleSwitch() REQUIRES(!process_state_update_lock_);
+
// Update *_freed_ever_ counters to reflect current GC values.
void IncrementFreedEver();
@@ -1341,6 +1354,12 @@
// concurrent GC case.
Atomic<size_t> target_footprint_;
+ // Computed with foreground-multiplier in GrowForUtilization() when run in
+ // jank non-perceptible state. On update to process state from background to
+ // foreground we set target_footprint_ to this value.
+ Mutex process_state_update_lock_ DEFAULT_MUTEX_ACQUIRED_AFTER;
+ size_t min_foreground_target_footprint_ GUARDED_BY(process_state_update_lock_);
+
// When num_bytes_allocated_ exceeds this amount then a concurrent GC should be requested so that
// it completes ahead of an allocation failing.
// A multiple of this is also used to determine when to trigger a GC in response to native