blob: b178c503553891c7cb639b4cff885fb81c5951b7 [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "third_party/blink/renderer/platform/heap/thread_state.h"
#include <algorithm>
#include <iomanip>
#include <limits>
#include <memory>
#include "base/atomicops.h"
#include "base/location.h"
#include "base/numerics/safe_conversions.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/bindings/runtime_call_stats.h"
#include "third_party/blink/renderer/platform/bindings/v8_per_isolate_data.h"
#include "third_party/blink/renderer/platform/heap/address_cache.h"
#include "third_party/blink/renderer/platform/heap/blink_gc_memory_dump_provider.h"
#include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/heap/heap_buildflags.h"
#include "third_party/blink/renderer/platform/heap/heap_compact.h"
#include "third_party/blink/renderer/platform/heap/heap_stats_collector.h"
#include "third_party/blink/renderer/platform/heap/marking_visitor.h"
#include "third_party/blink/renderer/platform/heap/page_pool.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/heap/unified_heap_marking_visitor.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
#include "third_party/blink/renderer/platform/histogram.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/web_memory_allocator_dump.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/web_process_memory_dump.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/wtf/allocator/partitions.h"
#include "third_party/blink/renderer/platform/wtf/stack_util.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#include "v8/include/v8.h"
#if defined(OS_WIN)
#include <stddef.h>
#include <windows.h>
#include <winnt.h>
#endif
#if defined(MEMORY_SANITIZER)
#include <sanitizer/msan_interface.h>
#endif
#if defined(OS_FREEBSD)
#include <pthread_np.h>
#endif
namespace blink {
WTF::ThreadSpecific<ThreadState*>* ThreadState::thread_specific_ = nullptr;
uint8_t ThreadState::main_thread_state_storage_[sizeof(ThreadState)];
namespace {
const size_t kDefaultAllocatedObjectSizeThreshold = 100 * 1024;
// Duration of one incremental marking step. Should be short enough that it
// doesn't cause jank even though it is scheduled as a normal task.
constexpr TimeDelta kDefaultIncrementalMarkingStepDuration =
TimeDelta::FromMilliseconds(2);
constexpr size_t kMaxTerminationGCLoops = 20;
const char* GcReasonString(BlinkGC::GCReason reason) {
switch (reason) {
case BlinkGC::GCReason::kIdleGC:
return "IdleGC";
case BlinkGC::GCReason::kPreciseGC:
return "PreciseGC";
case BlinkGC::GCReason::kConservativeGC:
return "ConservativeGC";
case BlinkGC::GCReason::kForcedGC:
return "ForcedGC";
case BlinkGC::GCReason::kMemoryPressureGC:
return "MemoryPressureGC";
case BlinkGC::GCReason::kPageNavigationGC:
return "PageNavigationGC";
case BlinkGC::GCReason::kThreadTerminationGC:
return "ThreadTerminationGC";
case BlinkGC::GCReason::kTesting:
return "TestingGC";
case BlinkGC::GCReason::kIncrementalIdleGC:
return "IncrementalIdleGC";
case BlinkGC::GCReason::kIncrementalV8FollowupGC:
return "IncrementalV8FollowupGC";
case BlinkGC::GCReason::kUnifiedHeapGC:
return "UnifiedHeapGC";
}
return "<Unknown>";
}
const char* MarkingTypeString(BlinkGC::MarkingType type) {
switch (type) {
case BlinkGC::kAtomicMarking:
return "AtomicMarking";
case BlinkGC::kIncrementalMarking:
return "IncrementalMarking";
case BlinkGC::kTakeSnapshot:
return "TakeSnapshot";
}
return "<Unknown>";
}
const char* SweepingTypeString(BlinkGC::SweepingType type) {
switch (type) {
case BlinkGC::kLazySweeping:
return "LazySweeping";
case BlinkGC::kEagerSweeping:
return "EagerSweeping";
}
return "<Unknown>";
}
const char* StackStateString(BlinkGC::StackState state) {
switch (state) {
case BlinkGC::kNoHeapPointersOnStack:
return "NoHeapPointersOnStack";
case BlinkGC::kHeapPointersOnStack:
return "HeapPointersOnStack";
}
return "<Unknown>";
}
// Helper function to convert a byte count to a KB count, capping at
// INT_MAX if the number is larger than that.
constexpr size_t CappedSizeInKB(size_t size_in_bytes) {
const size_t size_in_kb = size_in_bytes / 1024;
const size_t limit = std::numeric_limits<int>::max();
return size_in_kb > limit ? limit : size_in_kb;
}
} // namespace
ThreadState::ThreadState()
: thread_(CurrentThread()),
persistent_region_(std::make_unique<PersistentRegion>()),
weak_persistent_region_(std::make_unique<PersistentRegion>()),
start_of_stack_(reinterpret_cast<intptr_t*>(WTF::GetStackStart())),
end_of_stack_(reinterpret_cast<intptr_t*>(WTF::GetStackStart())),
#if HAS_FEATURE(safe_stack)
start_of_unsafe_stack_(
reinterpret_cast<intptr_t*>(__builtin___get_unsafe_stack_top())),
end_of_unsafe_stack_(
reinterpret_cast<intptr_t*>(__builtin___get_unsafe_stack_bottom())),
#endif
sweep_forbidden_(false),
no_allocation_count_(0),
gc_forbidden_count_(0),
mixins_being_constructed_count_(0),
object_resurrection_forbidden_(false),
in_atomic_pause_(false),
gc_mixin_marker_(nullptr),
gc_state_(kNoGCScheduled),
gc_phase_(GCPhase::kNone),
reason_for_scheduled_gc_(BlinkGC::GCReason::kMaxValue),
should_optimize_for_load_time_(false),
isolate_(nullptr),
trace_dom_wrappers_(nullptr),
invalidate_dead_objects_in_wrappers_marking_deque_(nullptr),
perform_cleanup_(nullptr),
wrapper_tracing_(false),
incremental_marking_(false),
#if defined(ADDRESS_SANITIZER)
asan_fake_stack_(__asan_get_current_fake_stack()),
#endif
#if defined(LEAK_SANITIZER)
disabled_static_persistent_registration_(0),
#endif
reported_memory_to_v8_(0) {
DCHECK(CheckThread());
DCHECK(!**thread_specific_);
**thread_specific_ = this;
heap_ = std::make_unique<ThreadHeap>(this);
}
ThreadState::~ThreadState() {
DCHECK(CheckThread());
if (IsMainThread())
DCHECK_EQ(0u, Heap().stats_collector()->allocated_space_bytes());
CHECK(GetGCState() == ThreadState::kNoGCScheduled);
**thread_specific_ = nullptr;
}
void ThreadState::AttachMainThread() {
thread_specific_ = new WTF::ThreadSpecific<ThreadState*>();
new (main_thread_state_storage_) ThreadState();
// PpapiThread doesn't set the current thread.
WebThread* current_thread = Platform::Current()->CurrentThread();
if (current_thread) {
ThreadScheduler* scheduler = current_thread->Scheduler();
// Some binaries do not have a scheduler (e.g.
// v8_context_snapshot_generator)
if (scheduler)
scheduler->AddRAILModeObserver(MainThreadState());
}
}
void ThreadState::AttachCurrentThread() {
new ThreadState();
}
void ThreadState::DetachCurrentThread() {
ThreadState* state = Current();
DCHECK(!state->IsMainThread());
state->RunTerminationGC();
delete state;
}
void ThreadState::RunTerminationGC() {
DCHECK(!IsMainThread());
DCHECK(CheckThread());
FinishIncrementalMarkingIfRunning(
BlinkGC::kNoHeapPointersOnStack, BlinkGC::kIncrementalMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kThreadTerminationGC);
// Finish sweeping.
CompleteSweep();
ReleaseStaticPersistentNodes();
// PrepareForThreadStateTermination removes strong references so no need to
// call it on CrossThreadWeakPersistentRegion.
ProcessHeap::GetCrossThreadPersistentRegion()
.PrepareForThreadStateTermination(this);
// Do thread local GC's as long as the count of thread local Persistents
// changes and is above zero.
int old_count = -1;
int current_count = GetPersistentRegion()->NumberOfPersistents();
DCHECK_GE(current_count, 0);
while (current_count != old_count) {
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping,
BlinkGC::GCReason::kThreadTerminationGC);
// Release the thread-local static persistents that were
// instantiated while running the termination GC.
ReleaseStaticPersistentNodes();
old_count = current_count;
current_count = GetPersistentRegion()->NumberOfPersistents();
}
// We should not have any persistents left when getting to this point,
// if we have it is a bug, and we have a reference cycle or a missing
// RegisterAsStaticReference. Clearing out all the Persistents will avoid
// stale pointers and gets them reported as nullptr dereferences.
if (current_count) {
for (size_t i = 0; i < kMaxTerminationGCLoops &&
GetPersistentRegion()->NumberOfPersistents();
i++) {
GetPersistentRegion()->PrepareForThreadStateTermination();
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping,
BlinkGC::GCReason::kThreadTerminationGC);
}
}
CHECK(!GetPersistentRegion()->NumberOfPersistents());
// All of pre-finalizers should be consumed.
DCHECK(ordered_pre_finalizers_.IsEmpty());
CHECK_EQ(GetGCState(), kNoGCScheduled);
Heap().RemoveAllPages();
}
NO_SANITIZE_ADDRESS
void ThreadState::VisitAsanFakeStackForPointer(MarkingVisitor* visitor,
Address ptr) {
#if defined(ADDRESS_SANITIZER)
Address* start = reinterpret_cast<Address*>(start_of_stack_);
Address* end = reinterpret_cast<Address*>(end_of_stack_);
Address* fake_frame_start = nullptr;
Address* fake_frame_end = nullptr;
Address* maybe_fake_frame = reinterpret_cast<Address*>(ptr);
Address* real_frame_for_fake_frame = reinterpret_cast<Address*>(
__asan_addr_is_in_fake_stack(asan_fake_stack_, maybe_fake_frame,
reinterpret_cast<void**>(&fake_frame_start),
reinterpret_cast<void**>(&fake_frame_end)));
if (real_frame_for_fake_frame) {
// This is a fake frame from the asan fake stack.
if (real_frame_for_fake_frame > end && start > real_frame_for_fake_frame) {
// The real stack address for the asan fake frame is
// within the stack range that we need to scan so we need
// to visit the values in the fake frame.
for (Address* p = fake_frame_start; p < fake_frame_end; ++p)
heap_->CheckAndMarkPointer(visitor, *p);
}
}
#endif
}
// Stack scanning may overrun the bounds of local objects and/or race with
// other threads that use this stack.
NO_SANITIZE_ADDRESS
NO_SANITIZE_THREAD
void ThreadState::VisitStack(MarkingVisitor* visitor) {
DCHECK_EQ(current_gc_data_.stack_state, BlinkGC::kHeapPointersOnStack);
Address* start = reinterpret_cast<Address*>(start_of_stack_);
Address* end = reinterpret_cast<Address*>(end_of_stack_);
// Ensure that current is aligned by address size otherwise the loop below
// will read past start address.
Address* current = reinterpret_cast<Address*>(
reinterpret_cast<intptr_t>(end) & ~(sizeof(Address) - 1));
for (; current < start; ++current) {
Address ptr = *current;
#if defined(MEMORY_SANITIZER)
// |ptr| may be uninitialized by design. Mark it as initialized to keep
// MSan from complaining.
// Note: it may be tempting to get rid of |ptr| and simply use |current|
// here, but that would be incorrect. We intentionally use a local
// variable because we don't want to unpoison the original stack.
__msan_unpoison(&ptr, sizeof(ptr));
#endif
heap_->CheckAndMarkPointer(visitor, ptr);
VisitAsanFakeStackForPointer(visitor, ptr);
}
#if HAS_FEATURE(safe_stack)
start = reinterpret_cast<Address*>(start_of_unsafe_stack_);
end = reinterpret_cast<Address*>(end_of_unsafe_stack_);
current = end;
for (; current < start; ++current) {
Address ptr = *current;
// SafeStack And MSan are not compatible
heap_->CheckAndMarkPointer(visitor, ptr);
VisitAsanFakeStackForPointer(visitor, ptr);
}
#endif
}
void ThreadState::VisitDOMWrappers(Visitor* visitor) {
if (trace_dom_wrappers_) {
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitDOMWrappers);
trace_dom_wrappers_(isolate_, visitor);
}
}
void ThreadState::VisitPersistents(Visitor* visitor) {
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kVisitPersistentRoots);
{
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kVisitCrossThreadPersistents);
// See ProcessHeap::CrossThreadPersistentMutex().
MutexLocker persistent_lock(ProcessHeap::CrossThreadPersistentMutex());
ProcessHeap::GetCrossThreadPersistentRegion().TracePersistentNodes(visitor);
}
{
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitPersistents);
persistent_region_->TracePersistentNodes(visitor);
}
}
void ThreadState::VisitWeakPersistents(Visitor* visitor) {
ProcessHeap::GetCrossThreadWeakPersistentRegion().TracePersistentNodes(
visitor);
weak_persistent_region_->TracePersistentNodes(visitor);
}
ThreadState::GCSnapshotInfo::GCSnapshotInfo(size_t num_object_types)
: live_count(Vector<int>(num_object_types)),
dead_count(Vector<int>(num_object_types)),
live_size(Vector<size_t>(num_object_types)),
dead_size(Vector<size_t>(num_object_types)) {}
size_t ThreadState::TotalMemorySize() {
return heap_->stats_collector()->object_size_in_bytes() +
WTF::Partitions::TotalSizeOfCommittedPages();
}
size_t ThreadState::EstimatedLiveSize(size_t estimation_base_size,
size_t size_at_last_gc) {
const ThreadHeapStatsCollector& stats_collector = *heap_->stats_collector();
const ThreadHeapStatsCollector::Event& prev = stats_collector.previous();
if (prev.wrapper_count_before_sweeping == 0)
return estimation_base_size;
// (estimated size) = (estimation base size) - (heap size at the last GC) /
// (# of persistent handles at the last GC) *
// (# of persistent handles collected since the last GC)
size_t size_retained_by_collected_persistents = static_cast<size_t>(
1.0 * size_at_last_gc / prev.wrapper_count_before_sweeping *
stats_collector.collected_wrapper_count());
if (estimation_base_size < size_retained_by_collected_persistents)
return 0;
return estimation_base_size - size_retained_by_collected_persistents;
}
double ThreadState::HeapGrowingRate() {
const size_t current_size = heap_->stats_collector()->object_size_in_bytes();
// TODO(mlippautz): Clarify those two parameters below.
const size_t estimated_size =
EstimatedLiveSize(heap_->stats_collector()->previous().marked_bytes,
heap_->stats_collector()->previous().marked_bytes);
// If the estimatedSize is 0, we set a high growing rate to trigger a GC.
double growing_rate =
estimated_size > 0 ? 1.0 * current_size / estimated_size : 100;
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"ThreadState::heapEstimatedSizeKB",
CappedSizeInKB(estimated_size));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"ThreadState::heapGrowingRate",
static_cast<int>(100 * growing_rate));
return growing_rate;
}
double ThreadState::PartitionAllocGrowingRate() {
size_t current_size = WTF::Partitions::TotalSizeOfCommittedPages();
size_t estimated_size = EstimatedLiveSize(
current_size, heap_->stats_collector()
->previous()
.partition_alloc_bytes_before_sweeping);
// If the estimatedSize is 0, we set a high growing rate to trigger a GC.
double growing_rate =
estimated_size > 0 ? 1.0 * current_size / estimated_size : 100;
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"ThreadState::partitionAllocEstimatedSizeKB",
CappedSizeInKB(estimated_size));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"ThreadState::partitionAllocGrowingRate",
static_cast<int>(100 * growing_rate));
return growing_rate;
}
// TODO(haraken): We should improve the GC heuristics. The heuristics affect
// performance significantly.
bool ThreadState::JudgeGCThreshold(size_t allocated_object_size_threshold,
size_t total_memory_size_threshold,
double heap_growing_rate_threshold) {
// If the allocated object size or the total memory size is small, don't
// trigger a GC.
if (heap_->stats_collector()->allocated_bytes_since_prev_gc() <
allocated_object_size_threshold ||
TotalMemorySize() < total_memory_size_threshold)
return false;
VLOG(2) << "[state:" << this << "] JudgeGCThreshold:"
<< " heapGrowingRate=" << std::setprecision(1) << HeapGrowingRate()
<< " partitionAllocGrowingRate=" << std::setprecision(1)
<< PartitionAllocGrowingRate();
// If the growing rate of Oilpan's heap or PartitionAlloc is high enough,
// trigger a GC.
return HeapGrowingRate() >= heap_growing_rate_threshold ||
PartitionAllocGrowingRate() >= heap_growing_rate_threshold;
}
bool ThreadState::ShouldScheduleIdleGC() {
if (GetGCState() != kNoGCScheduled)
return false;
return JudgeGCThreshold(kDefaultAllocatedObjectSizeThreshold, 1024 * 1024,
1.5);
}
bool ThreadState::ShouldScheduleV8FollowupGC() {
return JudgeGCThreshold(kDefaultAllocatedObjectSizeThreshold,
32 * 1024 * 1024, 1.5);
}
bool ThreadState::ShouldSchedulePageNavigationGC(
float estimated_removal_ratio) {
// If estimatedRemovalRatio is low we should let IdleGC handle this.
if (estimated_removal_ratio < 0.01)
return false;
return JudgeGCThreshold(kDefaultAllocatedObjectSizeThreshold,
32 * 1024 * 1024,
1.5 * (1 - estimated_removal_ratio));
}
bool ThreadState::ShouldForceConservativeGC() {
// TODO(haraken): 400% is too large. Lower the heap growing factor.
return JudgeGCThreshold(kDefaultAllocatedObjectSizeThreshold,
32 * 1024 * 1024, 5.0);
}
// If we're consuming too much memory, trigger a conservative GC
// aggressively. This is a safe guard to avoid OOM.
bool ThreadState::ShouldForceMemoryPressureGC() {
if (TotalMemorySize() < 300 * 1024 * 1024)
return false;
return JudgeGCThreshold(0, 0, 1.5);
}
void ThreadState::ScheduleV8FollowupGCIfNeeded(BlinkGC::V8GCType gc_type) {
VLOG(2) << "[state:" << this << "] ScheduleV8FollowupGCIfNeeded: v8_gc_type="
<< ((gc_type == BlinkGC::kV8MajorGC) ? "MajorGC" : "MinorGC");
DCHECK(CheckThread());
if (IsGCForbidden())
return;
// This completeSweep() will do nothing in common cases since we've
// called completeSweep() before V8 starts minor/major GCs.
if (gc_type == BlinkGC::kV8MajorGC) {
// TODO(ulan): Try removing this for Major V8 GC too.
CompleteSweep();
DCHECK(!IsSweepingInProgress());
DCHECK(!SweepForbidden());
}
if ((gc_type == BlinkGC::kV8MajorGC && ShouldForceMemoryPressureGC()) ||
ShouldScheduleV8FollowupGC()) {
// When we want to optimize for load time, we should prioritize throughput
// over latency and not do incremental marking.
if (RuntimeEnabledFeatures::HeapIncrementalMarkingEnabled() &&
!should_optimize_for_load_time_) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleV8FollowupGCIfNeeded: Scheduled incremental v8 "
"followup GC";
ScheduleIncrementalGC(BlinkGC::GCReason::kIncrementalV8FollowupGC);
} else {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleV8FollowupGCIfNeeded: Scheduled precise GC";
SchedulePreciseGC();
}
return;
}
if (gc_type == BlinkGC::kV8MajorGC && ShouldScheduleIdleGC()) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleV8FollowupGCIfNeeded: Scheduled idle GC";
ScheduleIdleGC();
return;
}
}
void ThreadState::WillStartV8GC(BlinkGC::V8GCType gc_type) {
// Finish Oilpan's complete sweeping before running a V8 major GC.
// This will let the GC collect more V8 objects.
//
// TODO(haraken): It's a bit too late for a major GC to schedule
// completeSweep() here, because gcPrologue for a major GC is called
// not at the point where the major GC started but at the point where
// the major GC requests object grouping.
DCHECK_EQ(BlinkGC::kV8MajorGC, gc_type);
CompleteSweep();
}
void ThreadState::SchedulePageNavigationGCIfNeeded(
float estimated_removal_ratio) {
VLOG(2) << "[state:" << this << "] SchedulePageNavigationGCIfNeeded: "
<< "estimatedRemovalRatio=" << std::setprecision(2)
<< estimated_removal_ratio;
DCHECK(CheckThread());
if (IsGCForbidden())
return;
// Finish on-going lazy sweeping.
// TODO(haraken): It might not make sense to force completeSweep() for all
// page navigations.
CompleteSweep();
DCHECK(!IsSweepingInProgress());
DCHECK(!SweepForbidden());
if (ShouldForceMemoryPressureGC()) {
VLOG(2) << "[state:" << this << "] "
<< "SchedulePageNavigationGCIfNeeded: Scheduled memory pressure GC";
CollectGarbage(BlinkGC::kHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping,
BlinkGC::GCReason::kMemoryPressureGC);
return;
}
if (ShouldSchedulePageNavigationGC(estimated_removal_ratio)) {
VLOG(2) << "[state:" << this << "] "
<< "SchedulePageNavigationGCIfNeeded: Scheduled page navigation GC";
SchedulePageNavigationGC();
}
}
void ThreadState::SchedulePageNavigationGC() {
DCHECK(CheckThread());
DCHECK(!IsSweepingInProgress());
SetGCState(kPageNavigationGCScheduled);
}
void ThreadState::ScheduleFullGC() {
DCHECK(CheckThread());
CompleteSweep();
SetGCState(kFullGCScheduled);
}
void ThreadState::ScheduleGCIfNeeded() {
VLOG(2) << "[state:" << this << "] ScheduleGCIfNeeded";
DCHECK(CheckThread());
UpdateIncrementalMarkingStepDuration();
// Allocation is allowed during sweeping, but those allocations should not
// trigger nested GCs.
if (IsGCForbidden() || SweepForbidden())
return;
ReportMemoryToV8();
if (ShouldForceMemoryPressureGC()) {
CompleteSweep();
if (ShouldForceMemoryPressureGC()) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleGCIfNeeded: Scheduled memory pressure GC";
CollectGarbage(BlinkGC::kHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping,
BlinkGC::GCReason::kMemoryPressureGC);
return;
}
}
if (ShouldForceConservativeGC()) {
CompleteSweep();
if (ShouldForceConservativeGC()) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleGCIfNeeded: Scheduled conservative GC";
CollectGarbage(BlinkGC::kHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping,
BlinkGC::GCReason::kConservativeGC);
return;
}
}
if (ShouldScheduleIdleGC()) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleGCIfNeeded: Scheduled idle GC";
ScheduleIdleGC();
return;
}
#if BUILDFLAG(BLINK_HEAP_INCREMENTAL_MARKING)
if (GetGCState() == kNoGCScheduled &&
RuntimeEnabledFeatures::HeapIncrementalMarkingStressEnabled()) {
VLOG(2) << "[state:" << this << "] "
<< "ScheduleGCIfNeeded: Scheduled incremental marking for testing";
IncrementalMarkingStart(BlinkGC::GCReason::kTesting);
return;
}
#endif
}
ThreadState* ThreadState::FromObject(const void* object) {
DCHECK(object);
BasePage* page = PageFromObject(object);
DCHECK(page);
DCHECK(page->Arena());
return page->Arena()->GetThreadState();
}
void ThreadState::PerformIdleGC(TimeTicks deadline) {
DCHECK(CheckThread());
DCHECK(Platform::Current()->CurrentThread()->Scheduler());
if (GetGCState() != kIdleGCScheduled)
return;
if (IsGCForbidden()) {
// If GC is forbidden at this point, try again.
RescheduleIdleGC();
return;
}
TimeDelta estimated_marking_time =
heap_->stats_collector()->estimated_marking_time();
if ((deadline - CurrentTimeTicks()) <= estimated_marking_time &&
!Platform::Current()
->CurrentThread()
->Scheduler()
->CanExceedIdleDeadlineIfRequired()) {
// If marking is estimated to take longer than the deadline and we can't
// exceed the deadline, then reschedule for the next idle period.
RescheduleIdleGC();
return;
}
#if BUILDFLAG(BLINK_HEAP_INCREMENTAL_MARKING)
if (RuntimeEnabledFeatures::HeapIncrementalMarkingEnabled()) {
IncrementalMarkingStart(BlinkGC::GCReason::kIncrementalIdleGC);
return;
}
#endif
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kIdleGC);
}
void ThreadState::PerformIdleLazySweep(TimeTicks deadline) {
DCHECK(CheckThread());
// If we are not in a sweeping phase, there is nothing to do here.
if (!IsSweepingInProgress())
return;
// This check is here to prevent performIdleLazySweep() from being called
// recursively. I'm not sure if it can happen but it would be safer to have
// the check just in case.
if (SweepForbidden())
return;
RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(
GetIsolate(), RuntimeCallStats::CounterId::kPerformIdleLazySweep);
bool sweep_completed = false;
{
AtomicPauseScope atomic_pause_scope(this);
SweepForbiddenScope scope(this);
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kLazySweepInIdle,
"idleDeltaInSeconds", (deadline - CurrentTimeTicks()).InSecondsF());
sweep_completed = Heap().AdvanceLazySweep(deadline);
// We couldn't finish the sweeping within the deadline.
// We request another idle task for the remaining sweeping.
if (!sweep_completed)
ScheduleIdleLazySweep();
}
if (sweep_completed)
PostSweep();
}
void ThreadState::ScheduleIncrementalMarkingStep() {
CHECK(!IsSweepingInProgress());
SetGCState(kIncrementalMarkingStepScheduled);
}
void ThreadState::ScheduleIncrementalMarkingFinalize() {
CHECK(!IsSweepingInProgress());
SetGCState(kIncrementalMarkingFinalizeScheduled);
}
void ThreadState::ScheduleIdleGC() {
// Some threads (e.g. PPAPI thread) don't have a scheduler.
// Also some tests can call Platform::SetCurrentPlatformForTesting() at any
// time, so we need to check if it exists.
if (!Platform::Current()->CurrentThread()->Scheduler())
return;
// Idle GC has the lowest priority so do not schedule if a GC is already
// scheduled or if marking is in progress.
if (GetGCState() != kNoGCScheduled)
return;
CompleteSweep();
SetGCState(kIdleGCScheduled);
Platform::Current()->CurrentThread()->Scheduler()->PostNonNestableIdleTask(
FROM_HERE, WTF::Bind(&ThreadState::PerformIdleGC, WTF::Unretained(this)));
}
void ThreadState::RescheduleIdleGC() {
DCHECK_EQ(kIdleGCScheduled, GetGCState());
SetGCState(kNoGCScheduled);
ScheduleIdleGC();
}
void ThreadState::ScheduleIdleLazySweep() {
// Some threads (e.g. PPAPI thread) don't have a scheduler.
if (!Platform::Current()->CurrentThread()->Scheduler())
return;
Platform::Current()->CurrentThread()->Scheduler()->PostIdleTask(
FROM_HERE,
WTF::Bind(&ThreadState::PerformIdleLazySweep, WTF::Unretained(this)));
}
void ThreadState::SchedulePreciseGC() {
DCHECK(CheckThread());
CompleteSweep();
SetGCState(kPreciseGCScheduled);
}
void ThreadState::ScheduleIncrementalGC(BlinkGC::GCReason reason) {
DCHECK(CheckThread());
// Schedule an incremental GC only when no GC is scheduled or an idle GC is
// scheduled. Otherwise, already scheduled GCs should be prioritized.
if (GetGCState() == kNoGCScheduled || GetGCState() == kIdleGCScheduled) {
CompleteSweep();
reason_for_scheduled_gc_ = reason;
SetGCState(kIncrementalGCScheduled);
}
}
namespace {
#define UNEXPECTED_GCSTATE(s) \
case ThreadState::s: \
LOG(FATAL) << "Unexpected transition while in GCState " #s; \
return
void UnexpectedGCState(ThreadState::GCState gc_state) {
switch (gc_state) {
UNEXPECTED_GCSTATE(kNoGCScheduled);
UNEXPECTED_GCSTATE(kIdleGCScheduled);
UNEXPECTED_GCSTATE(kPreciseGCScheduled);
UNEXPECTED_GCSTATE(kFullGCScheduled);
UNEXPECTED_GCSTATE(kIncrementalMarkingStepScheduled);
UNEXPECTED_GCSTATE(kIncrementalMarkingFinalizeScheduled);
UNEXPECTED_GCSTATE(kPageNavigationGCScheduled);
UNEXPECTED_GCSTATE(kIncrementalGCScheduled);
}
}
#undef UNEXPECTED_GCSTATE
} // namespace
#define VERIFY_STATE_TRANSITION(condition) \
if (UNLIKELY(!(condition))) \
UnexpectedGCState(gc_state_)
void ThreadState::SetGCState(GCState gc_state) {
switch (gc_state) {
case kNoGCScheduled:
DCHECK(CheckThread());
VERIFY_STATE_TRANSITION(
gc_state_ == kNoGCScheduled || gc_state_ == kIdleGCScheduled ||
gc_state_ == kPreciseGCScheduled || gc_state_ == kFullGCScheduled ||
gc_state_ == kPageNavigationGCScheduled ||
gc_state_ == kIncrementalMarkingStepScheduled ||
gc_state_ == kIncrementalMarkingFinalizeScheduled ||
gc_state_ == kIncrementalGCScheduled);
break;
case kIncrementalMarkingStepScheduled:
DCHECK(CheckThread());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled ||
gc_state_ == kIncrementalMarkingStepScheduled ||
gc_state_ == kIdleGCScheduled ||
gc_state_ == kIncrementalGCScheduled);
break;
case kIncrementalMarkingFinalizeScheduled:
DCHECK(CheckThread());
VERIFY_STATE_TRANSITION(gc_state_ == kIncrementalMarkingStepScheduled);
break;
case kFullGCScheduled:
case kPageNavigationGCScheduled:
case kPreciseGCScheduled:
DCHECK(CheckThread());
DCHECK(!IsSweepingInProgress());
VERIFY_STATE_TRANSITION(
gc_state_ == kNoGCScheduled || gc_state_ == kIdleGCScheduled ||
gc_state_ == kIncrementalMarkingStepScheduled ||
gc_state_ == kIncrementalMarkingFinalizeScheduled ||
gc_state_ == kPreciseGCScheduled || gc_state_ == kFullGCScheduled ||
gc_state_ == kPageNavigationGCScheduled ||
gc_state_ == kIncrementalGCScheduled);
break;
case kIdleGCScheduled:
DCHECK(CheckThread());
DCHECK(!IsMarkingInProgress());
DCHECK(!IsSweepingInProgress());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled);
break;
case kIncrementalGCScheduled:
DCHECK(CheckThread());
DCHECK(!IsMarkingInProgress());
DCHECK(!IsSweepingInProgress());
VERIFY_STATE_TRANSITION(gc_state_ == kNoGCScheduled ||
gc_state_ == kIdleGCScheduled);
break;
default:
NOTREACHED();
}
gc_state_ = gc_state;
}
#undef VERIFY_STATE_TRANSITION
void ThreadState::SetGCPhase(GCPhase gc_phase) {
switch (gc_phase) {
case GCPhase::kNone:
DCHECK_EQ(gc_phase_, GCPhase::kSweeping);
break;
case GCPhase::kMarking:
DCHECK_EQ(gc_phase_, GCPhase::kNone);
break;
case GCPhase::kSweeping:
DCHECK_EQ(gc_phase_, GCPhase::kMarking);
break;
}
gc_phase_ = gc_phase;
}
void ThreadState::RunScheduledGC(BlinkGC::StackState stack_state) {
DCHECK(CheckThread());
if (stack_state != BlinkGC::kNoHeapPointersOnStack)
return;
// If a safe point is entered while initiating a GC, we clearly do
// not want to do another as part of that -- the safe point is only
// entered after checking if a scheduled GC ought to run first.
// Prevent that from happening by marking GCs as forbidden while
// one is initiated and later running.
if (IsGCForbidden())
return;
switch (GetGCState()) {
case kFullGCScheduled:
CollectAllGarbage();
break;
case kPreciseGCScheduled:
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kLazySweeping, BlinkGC::GCReason::kPreciseGC);
break;
case kPageNavigationGCScheduled:
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping,
BlinkGC::GCReason::kPageNavigationGC);
break;
case kIdleGCScheduled:
// Idle time GC will be scheduled by Blink Scheduler.
break;
case kIncrementalMarkingStepScheduled:
IncrementalMarkingStep();
break;
case kIncrementalMarkingFinalizeScheduled:
IncrementalMarkingFinalize();
break;
case kIncrementalGCScheduled:
IncrementalMarkingStart(reason_for_scheduled_gc_);
break;
default:
break;
}
}
void ThreadState::FinishSnapshot() {
// Force setting NoGCScheduled to circumvent checkThread()
// in setGCState().
gc_state_ = kNoGCScheduled;
SetGCPhase(GCPhase::kSweeping);
SetGCPhase(GCPhase::kNone);
}
void ThreadState::AtomicPauseEpilogue(BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type) {
DCHECK(InAtomicMarkingPause());
DCHECK(CheckThread());
Heap().PrepareForSweep();
if (marking_type == BlinkGC::kTakeSnapshot) {
// Doing lazy sweeping for kTakeSnapshot doesn't make any sense so the
// sweeping type should always be kEagerSweeping.
DCHECK_EQ(sweeping_type, BlinkGC::kEagerSweeping);
Heap().TakeSnapshot(ThreadHeap::SnapshotType::kHeapSnapshot);
// This unmarks all marked objects and marks all unmarked objects dead.
Heap().MakeConsistentForMutator();
Heap().TakeSnapshot(ThreadHeap::SnapshotType::kFreelistSnapshot);
return;
}
// We have to set the GCPhase to Sweeping before calling pre-finalizers
// to disallow a GC during the pre-finalizers.
SetGCPhase(GCPhase::kSweeping);
// Allocation is allowed during the pre-finalizers and destructors.
// However, they must not mutate an object graph in a way in which
// a dead object gets resurrected.
InvokePreFinalizers();
EagerSweep();
// Any sweep compaction must happen after pre-finalizers and eager
// sweeping, as it will finalize dead objects in compactable arenas
// (e.g., backing stores for container objects.)
//
// As per-contract for prefinalizers, those finalizable objects must
// still be accessible when the prefinalizer runs, hence we cannot
// schedule compaction until those have run. Similarly for eager sweeping.
{
SweepForbiddenScope scope(this);
NoAllocationScope no_allocation_scope(this);
Heap().Compact();
}
#if defined(ADDRESS_SANITIZER)
Heap().PoisonAllHeaps();
#endif
}
void ThreadState::EagerSweep() {
#if defined(ADDRESS_SANITIZER)
Heap().PoisonEagerArena();
#endif
DCHECK(CheckThread());
// Some objects need to be finalized promptly and cannot be handled
// by lazy sweeping. Keep those in a designated heap and sweep it
// eagerly.
DCHECK(IsSweepingInProgress());
SweepForbiddenScope scope(this);
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kEagerSweep);
Heap().Arena(BlinkGC::kEagerSweepArenaIndex)->CompleteSweep();
}
void ThreadState::CompleteSweep() {
DCHECK(CheckThread());
// If we are not in a sweeping phase, there is nothing to do here.
if (!IsSweepingInProgress())
return;
// completeSweep() can be called recursively if finalizers can allocate
// memory and the allocation triggers completeSweep(). This check prevents
// the sweeping from being executed recursively.
if (SweepForbidden())
return;
{
// CompleteSweep may be called during regular mutator exececution, from a
// task, or from the atomic pause in which the atomic scope has already been
// opened.
const bool was_in_atomic_pause = in_atomic_pause();
if (!was_in_atomic_pause)
EnterAtomicPause();
ScriptForbiddenScope script_forbidden;
SweepForbiddenScope scope(this);
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kCompleteSweep,
"forced", current_gc_data_.reason == BlinkGC::GCReason::kForcedGC);
Heap().CompleteSweep();
if (!was_in_atomic_pause)
LeaveAtomicPause();
}
PostSweep();
}
BlinkGCObserver::BlinkGCObserver(ThreadState* thread_state)
: thread_state_(thread_state) {
thread_state_->AddObserver(this);
}
BlinkGCObserver::~BlinkGCObserver() {
thread_state_->RemoveObserver(this);
}
namespace {
// Update trace counters with statistics from the current and previous garbage
// collection cycle. We allow emitting current values here since these values
// can be useful for inspecting traces.
void UpdateTraceCounters(const ThreadHeapStatsCollector& stats_collector) {
bool gc_tracing_enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
&gc_tracing_enabled);
if (!gc_tracing_enabled)
return;
// Previous garbage collection cycle values.
const ThreadHeapStatsCollector::Event& event = stats_collector.previous();
const int collection_rate_percent =
static_cast<int>(100 * (1.0 - event.live_object_rate));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.CollectionRate", collection_rate_percent);
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.MarkedObjectSizeAtLastCompleteSweepKB",
CappedSizeInKB(event.marked_bytes));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.ObjectSizeAtLastGCKB",
CappedSizeInKB(event.object_size_in_bytes_before_sweeping));
TRACE_COUNTER1(
TRACE_DISABLED_BY_DEFAULT("blink_gc"), "BlinkGC.AllocatedSpaceAtLastGCKB",
CappedSizeInKB(event.allocated_space_in_bytes_before_sweeping));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.PartitionAllocSizeAtLastGCKB",
CappedSizeInKB(event.partition_alloc_bytes_before_sweeping));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.WrapperCountAtLastGC",
event.wrapper_count_before_sweeping);
// Current values.
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.AllocatedSpaceKB",
CappedSizeInKB(stats_collector.allocated_space_bytes()));
TRACE_COUNTER1(
TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.AllocatedObjectSizeSincePreviousGCKB",
CappedSizeInKB(stats_collector.allocated_bytes_since_prev_gc()));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"PartitionAlloc.TotalSizeOfCommittedPagesKB",
CappedSizeInKB(WTF::Partitions::TotalSizeOfCommittedPages()));
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"), "BlinkGC.WrapperCount",
stats_collector.wrapper_count());
TRACE_COUNTER1(TRACE_DISABLED_BY_DEFAULT("blink_gc"),
"BlinkGC.CollectedWrapperCount",
stats_collector.collected_wrapper_count());
}
// Update histograms with statistics from the previous garbage collection cycle.
// Anything that is part of a histogram should have a well-defined lifetime wrt.
// to a garbage collection cycle.
void UpdateHistograms(const ThreadHeapStatsCollector::Event& event) {
UMA_HISTOGRAM_ENUMERATION("BlinkGC.GCReason", event.reason);
// TODO(mlippautz): Update name of this histogram.
UMA_HISTOGRAM_TIMES(
"BlinkGC.CollectGarbage",
event.scope_data[ThreadHeapStatsCollector::kAtomicPhaseMarking]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.AtomicPhaseMarking",
event.scope_data[ThreadHeapStatsCollector::kAtomicPhaseMarking]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.CompleteSweep",
event.scope_data[ThreadHeapStatsCollector::kCompleteSweep]);
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForSweepingAllObjects",
event.sweeping_time());
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForInvokingPreFinalizers",
event.scope_data[ThreadHeapStatsCollector::kInvokePreFinalizers]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForHeapCompaction",
event.scope_data[ThreadHeapStatsCollector::kAtomicPhaseCompaction]);
DEFINE_STATIC_LOCAL(
CustomCountHistogram, object_size_freed_by_heap_compaction,
("BlinkGC.ObjectSizeFreedByHeapCompaction", 1, 4 * 1024 * 1024, 50));
object_size_freed_by_heap_compaction.Count(
CappedSizeInKB(event.compaction_freed_bytes));
UMA_HISTOGRAM_TIMES(
"BlinkGC.TimeForGlobalWeakProcessing",
event.scope_data[ThreadHeapStatsCollector::kMarkWeakProcessing]);
constexpr base::TimeDelta kSlowIncrementalMarkingFinalizeTheshold =
base::TimeDelta::FromMilliseconds(40);
if (event.scope_data[ThreadHeapStatsCollector::kIncrementalMarkingFinalize] >
kSlowIncrementalMarkingFinalizeTheshold) {
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.IncrementalMarkingFinalize",
event
.scope_data[ThreadHeapStatsCollector::kIncrementalMarkingFinalize]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.AtomicPhaseMarking",
event.scope_data[ThreadHeapStatsCollector::kAtomicPhaseMarking]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.VisitCrossThreadPersistents",
event.scope_data
[ThreadHeapStatsCollector::kVisitCrossThreadPersistents]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.VisitDOMWrappers",
event.scope_data[ThreadHeapStatsCollector::kVisitDOMWrappers]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.MarkWeakProcessing",
event.scope_data[ThreadHeapStatsCollector::kMarkWeakProcessing]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.InvokePreFinalizers",
event.scope_data[ThreadHeapStatsCollector::kInvokePreFinalizers]);
UMA_HISTOGRAM_TIMES(
"BlinkGC.SlowIncrementalMarkingFinalize.EagerSweep",
event.scope_data[ThreadHeapStatsCollector::kEagerSweep]);
}
DEFINE_STATIC_LOCAL(CustomCountHistogram, object_size_before_gc_histogram,
("BlinkGC.ObjectSizeBeforeGC", 1, 4 * 1024 * 1024, 50));
object_size_before_gc_histogram.Count(
CappedSizeInKB(event.object_size_in_bytes_before_sweeping));
DEFINE_STATIC_LOCAL(CustomCountHistogram, object_size_after_gc_histogram,
("BlinkGC.ObjectSizeAfterGC", 1, 4 * 1024 * 1024, 50));
object_size_after_gc_histogram.Count(CappedSizeInKB(event.marked_bytes));
const int collection_rate_percent =
static_cast<int>(100 * (1.0 - event.live_object_rate));
DEFINE_STATIC_LOCAL(CustomCountHistogram, collection_rate_histogram,
("BlinkGC.CollectionRate", 1, 100, 20));
collection_rate_histogram.Count(collection_rate_percent);
// Per GCReason metrics.
switch (event.reason) {
#define COUNT_BY_GC_REASON(reason) \
case BlinkGC::GCReason::k##reason: { \
UMA_HISTOGRAM_TIMES( \
"BlinkGC.AtomicPhaseMarking_" #reason, \
event.scope_data[ThreadHeapStatsCollector::kAtomicPhaseMarking]); \
DEFINE_STATIC_LOCAL(CustomCountHistogram, collection_rate_histogram, \
("BlinkGC.CollectionRate_" #reason, 1, 100, 20)); \
collection_rate_histogram.Count(collection_rate_percent); \
break; \
}
COUNT_BY_GC_REASON(IdleGC)
COUNT_BY_GC_REASON(PreciseGC)
COUNT_BY_GC_REASON(ConservativeGC)
COUNT_BY_GC_REASON(ForcedGC)
COUNT_BY_GC_REASON(MemoryPressureGC)
COUNT_BY_GC_REASON(PageNavigationGC)
COUNT_BY_GC_REASON(ThreadTerminationGC)
COUNT_BY_GC_REASON(Testing)
COUNT_BY_GC_REASON(IncrementalIdleGC)
COUNT_BY_GC_REASON(IncrementalV8FollowupGC)
COUNT_BY_GC_REASON(UnifiedHeapGC)
#undef COUNT_BY_GC_REASON
}
static constexpr size_t kSupportedMaxSizeInMB = 4 * 1024;
static size_t max_committed_size_in_mb = 0;
// +1 for rounding up the size to the next MB.
size_t size_in_mb =
event.allocated_space_in_bytes_before_sweeping / 1024 / 1024 + 1;
if (size_in_mb >= kSupportedMaxSizeInMB)
size_in_mb = kSupportedMaxSizeInMB - 1;
if (size_in_mb > max_committed_size_in_mb) {
// Only update the counter for the maximum value.
DEFINE_STATIC_LOCAL(EnumerationHistogram, commited_size_histogram,
("BlinkGC.CommittedSize", kSupportedMaxSizeInMB));
commited_size_histogram.Count(size_in_mb);
max_committed_size_in_mb = size_in_mb;
}
}
} // namespace
void ThreadState::UpdateStatisticsAfterSweeping() {
DCHECK(!IsSweepingInProgress());
DCHECK(Heap().stats_collector()->is_started());
Heap().stats_collector()->NotifySweepingCompleted();
if (IsMainThread())
UpdateHistograms(Heap().stats_collector()->previous());
// Emit trace counters for all threads.
UpdateTraceCounters(*Heap().stats_collector());
}
void ThreadState::PostSweep() {
DCHECK(CheckThread());
SetGCPhase(GCPhase::kNone);
if (GetGCState() == kIdleGCScheduled)
ScheduleIdleGC();
gc_age_++;
for (auto* const observer : observers_)
observer->OnCompleteSweepDone();
if (!in_atomic_pause()) {
// Immediately update the statistics if running outside of the atomic pause.
UpdateStatisticsAfterSweeping();
}
}
void ThreadState::SafePoint(BlinkGC::StackState stack_state) {
DCHECK(CheckThread());
RunScheduledGC(stack_state);
}
// TODO(haraken): The first void* pointer is unused. Remove it.
using PushAllRegistersCallback = void (*)(void*, ThreadState*, intptr_t*);
extern "C" void PushAllRegisters(void*, ThreadState*, PushAllRegistersCallback);
static void DidPushRegisters(void*, ThreadState* state, intptr_t* stack_end) {
state->RecordStackEnd(stack_end);
#if HAS_FEATURE(safe_stack)
state->RecordUnsafeStackEnd(
reinterpret_cast<intptr_t*>(__builtin___get_unsafe_stack_ptr()));
#endif
}
void ThreadState::PushRegistersAndVisitStack() {
DCHECK(CheckThread());
DCHECK(IsGCForbidden());
DCHECK_EQ(current_gc_data_.stack_state, BlinkGC::kHeapPointersOnStack);
PushAllRegisters(nullptr, this, DidPushRegisters);
VisitStack(static_cast<MarkingVisitor*>(CurrentVisitor()));
}
void ThreadState::AddObserver(BlinkGCObserver* observer) {
DCHECK(observer);
DCHECK(observers_.find(observer) == observers_.end());
observers_.insert(observer);
}
void ThreadState::RemoveObserver(BlinkGCObserver* observer) {
DCHECK(observer);
DCHECK(observers_.find(observer) != observers_.end());
observers_.erase(observer);
}
void ThreadState::ReportMemoryToV8() {
if (!isolate_)
return;
const size_t current_heap_size =
heap_->stats_collector()->object_size_in_bytes();
int64_t diff = static_cast<int64_t>(current_heap_size) -
static_cast<int64_t>(reported_memory_to_v8_);
isolate_->AdjustAmountOfExternalAllocatedMemory(diff);
reported_memory_to_v8_ = current_heap_size;
}
void ThreadState::RegisterStaticPersistentNode(
PersistentNode* node,
PersistentClearCallback callback) {
#if defined(LEAK_SANITIZER)
if (disabled_static_persistent_registration_)
return;
#endif
DCHECK(!static_persistents_.Contains(node));
static_persistents_.insert(node, callback);
}
void ThreadState::ReleaseStaticPersistentNodes() {
HashMap<PersistentNode*, ThreadState::PersistentClearCallback>
static_persistents;
static_persistents.swap(static_persistents_);
PersistentRegion* persistent_region = GetPersistentRegion();
for (const auto& it : static_persistents)
persistent_region->ReleasePersistentNode(it.key, it.value);
}
void ThreadState::FreePersistentNode(PersistentRegion* persistent_region,
PersistentNode* persistent_node) {
persistent_region->FreePersistentNode(persistent_node);
// Do not allow static persistents to be freed before
// they're all released in releaseStaticPersistentNodes().
//
// There's no fundamental reason why this couldn't be supported,
// but no known use for it.
if (persistent_region == GetPersistentRegion())
DCHECK(!static_persistents_.Contains(persistent_node));
}
#if defined(LEAK_SANITIZER)
void ThreadState::enterStaticReferenceRegistrationDisabledScope() {
disabled_static_persistent_registration_++;
}
void ThreadState::leaveStaticReferenceRegistrationDisabledScope() {
DCHECK(disabled_static_persistent_registration_);
disabled_static_persistent_registration_--;
}
#endif
void ThreadState::InvokePreFinalizers() {
DCHECK(CheckThread());
DCHECK(!SweepForbidden());
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kInvokePreFinalizers);
SweepForbiddenScope sweep_forbidden(this);
// Pre finalizers may access unmarked objects but are forbidden from
// ressurecting them.
ObjectResurrectionForbiddenScope object_resurrection_forbidden(this);
// Call the prefinalizers in the opposite order to their registration.
//
// LinkedHashSet does not support modification during iteration, so
// copy items first.
//
// The prefinalizer callback wrapper returns |true| when its associated
// object is unreachable garbage and the prefinalizer callback has run.
// The registered prefinalizer entry must then be removed and deleted.
Vector<PreFinalizer> reversed;
for (auto rit = ordered_pre_finalizers_.rbegin();
rit != ordered_pre_finalizers_.rend(); ++rit) {
reversed.push_back(*rit);
}
for (PreFinalizer pre_finalizer : reversed) {
if ((pre_finalizer.second)(pre_finalizer.first))
ordered_pre_finalizers_.erase(pre_finalizer);
}
}
// static
base::subtle::AtomicWord ThreadState::incremental_marking_counter_ = 0;
// static
base::subtle::AtomicWord ThreadState::wrapper_tracing_counter_ = 0;
void ThreadState::EnableIncrementalMarkingBarrier() {
CHECK(!IsIncrementalMarking());
base::subtle::Barrier_AtomicIncrement(&incremental_marking_counter_, 1);
SetIncrementalMarking(true);
}
void ThreadState::DisableIncrementalMarkingBarrier() {
CHECK(IsIncrementalMarking());
base::subtle::Barrier_AtomicIncrement(&incremental_marking_counter_, -1);
SetIncrementalMarking(false);
}
void ThreadState::EnableWrapperTracingBarrier() {
CHECK(!IsWrapperTracing());
base::subtle::Barrier_AtomicIncrement(&wrapper_tracing_counter_, 1);
SetWrapperTracing(true);
}
void ThreadState::DisableWrapperTracingBarrier() {
CHECK(IsWrapperTracing());
base::subtle::Barrier_AtomicIncrement(&wrapper_tracing_counter_, -1);
SetWrapperTracing(false);
}
void ThreadState::IncrementalMarkingStart(BlinkGC::GCReason reason) {
VLOG(2) << "[state:" << this << "] "
<< "IncrementalMarking: Start";
DCHECK(!IsMarkingInProgress());
CompleteSweep();
Heap().stats_collector()->NotifyMarkingStarted(reason);
{
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingStartMarking, "reason",
GcReasonString(reason));
AtomicPauseScope atomic_pause_scope(this);
next_incremental_marking_step_duration_ =
kDefaultIncrementalMarkingStepDuration;
previous_incremental_marking_time_left_ = TimeDelta::Max();
MarkPhasePrologue(BlinkGC::kNoHeapPointersOnStack,
BlinkGC::kIncrementalMarking, reason);
MarkPhaseVisitRoots();
EnableIncrementalMarkingBarrier();
ScheduleIncrementalMarkingStep();
DCHECK(IsMarkingInProgress());
}
}
void ThreadState::IncrementalMarkingStep() {
DCHECK(IsMarkingInProgress());
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingStep);
VLOG(2) << "[state:" << this << "] "
<< "IncrementalMarking: Step "
<< "Reason: " << GcReasonString(current_gc_data_.reason);
AtomicPauseScope atomic_pause_scope(this);
const bool complete = MarkPhaseAdvanceMarking(
CurrentTimeTicks() + next_incremental_marking_step_duration_);
if (complete) {
if (IsUnifiedGCMarkingInProgress()) {
// If there are no more objects to mark for unified garbage collections
// just bail out of helping incrementally using tasks. V8 will drive
// further marking if new objects are discovered. Otherwise, just process
// the rest in the atomic pause.
DCHECK(IsUnifiedGCMarkingInProgress());
SetGCState(kNoGCScheduled);
} else {
ScheduleIncrementalMarkingFinalize();
}
} else {
ScheduleIncrementalMarkingStep();
}
DCHECK(IsMarkingInProgress());
}
void ThreadState::IncrementalMarkingFinalize() {
DCHECK(IsMarkingInProgress());
DCHECK(!IsUnifiedGCMarkingInProgress());
ThreadHeapStatsCollector::EnabledScope stats_scope(
Heap().stats_collector(),
ThreadHeapStatsCollector::kIncrementalMarkingFinalize);
VLOG(2) << "[state:" << this << "] "
<< "IncrementalMarking: Finalize "
<< "Reason: " << GcReasonString(current_gc_data_.reason);
// Call into the regular bottleneck instead of the internal version to get
// UMA accounting and allow follow up GCs if necessary.
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kIncrementalMarking,
BlinkGC::kLazySweeping, current_gc_data_.reason);
}
bool ThreadState::FinishIncrementalMarkingIfRunning(
BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
if (IsMarkingInProgress()) {
// TODO(mlippautz): Consider improving this mechanism as it will pull in
// finalization of V8 upon Oilpan GCs during a unified GC. Alternative
// include either breaking up the GCs or avoiding the call in first place.
if (IsUnifiedGCMarkingInProgress()) {
V8PerIsolateData::From(isolate_)
->GetUnifiedHeapController()
->FinalizeTracing();
} else {
RunAtomicPause(stack_state, marking_type, sweeping_type, reason);
}
return true;
}
return false;
}
void ThreadState::CollectGarbage(BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
// Nested garbage collection invocations are not supported.
CHECK(!IsGCForbidden());
// Garbage collection during sweeping is not supported. This can happen when
// finalizers trigger garbage collections.
if (SweepForbidden())
return;
TimeTicks start_total_collect_garbage_time = WTF::CurrentTimeTicks();
RUNTIME_CALL_TIMER_SCOPE_IF_ISOLATE_EXISTS(
GetIsolate(), RuntimeCallStats::CounterId::kCollectGarbage);
const bool was_incremental_marking = FinishIncrementalMarkingIfRunning(
stack_state, marking_type, sweeping_type, reason);
// We don't want floating garbage for the specific garbage collection types
// mentioned below. In this case we will follow up with a regular full
// garbage collection.
const bool should_do_full_gc =
!was_incremental_marking || reason == BlinkGC::GCReason::kForcedGC ||
reason == BlinkGC::GCReason::kMemoryPressureGC ||
reason == BlinkGC::GCReason::kThreadTerminationGC;
if (should_do_full_gc) {
CompleteSweep();
SetGCState(kNoGCScheduled);
Heap().stats_collector()->NotifyMarkingStarted(reason);
RunAtomicPause(stack_state, marking_type, sweeping_type, reason);
}
const TimeDelta total_collect_garbage_time =
WTF::CurrentTimeTicks() - start_total_collect_garbage_time;
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForTotalCollectGarbage",
total_collect_garbage_time);
#define COUNT_BY_GC_REASON(reason) \
case BlinkGC::GCReason::k##reason: { \
UMA_HISTOGRAM_TIMES("BlinkGC.TimeForTotalCollectGarbage_" #reason, \
total_collect_garbage_time); \
break; \
}
switch (reason) {
COUNT_BY_GC_REASON(IdleGC)
COUNT_BY_GC_REASON(PreciseGC)
COUNT_BY_GC_REASON(ConservativeGC)
COUNT_BY_GC_REASON(ForcedGC)
COUNT_BY_GC_REASON(MemoryPressureGC)
COUNT_BY_GC_REASON(PageNavigationGC)
COUNT_BY_GC_REASON(ThreadTerminationGC)
COUNT_BY_GC_REASON(Testing)
COUNT_BY_GC_REASON(IncrementalIdleGC)
COUNT_BY_GC_REASON(IncrementalV8FollowupGC)
COUNT_BY_GC_REASON(UnifiedHeapGC)
}
#undef COUNT_BY_GC_REASON
VLOG(1) << "[state:" << this << "]"
<< " CollectGarbage: time: " << std::setprecision(2)
<< total_collect_garbage_time.InMillisecondsF() << "ms"
<< " stack: " << StackStateString(stack_state)
<< " marking: " << MarkingTypeString(marking_type)
<< " sweeping: " << SweepingTypeString(sweeping_type)
<< " reason: " << GcReasonString(reason);
}
void ThreadState::AtomicPauseMarkPrologue(BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::GCReason reason) {
AtomicPausePrologue(stack_state, marking_type, reason);
MarkPhaseVisitRoots();
MarkPhaseVisitNotFullyConstructedObjects();
}
void ThreadState::AtomicPauseMarkTransitiveClosure() {
CHECK(MarkPhaseAdvanceMarking(TimeTicks::Max()));
}
void ThreadState::AtomicPauseMarkEpilogue(BlinkGC::MarkingType marking_type) {
MarkPhaseEpilogue(marking_type);
}
void ThreadState::AtomicPauseSweepAndCompact(
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type) {
AtomicPauseScope atomic_pause_scope(this);
AtomicPauseEpilogue(marking_type, sweeping_type);
if (marking_type == BlinkGC::kTakeSnapshot) {
FinishSnapshot();
CHECK(!IsSweepingInProgress());
CHECK_EQ(GetGCState(), kNoGCScheduled);
return;
}
DCHECK(IsSweepingInProgress());
if (sweeping_type == BlinkGC::kEagerSweeping) {
// Eager sweeping should happen only in testing.
CompleteSweep();
} else {
DCHECK(sweeping_type == BlinkGC::kLazySweeping);
// The default behavior is lazy sweeping.
ScheduleIdleLazySweep();
}
}
void ThreadState::RunAtomicPause(BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::SweepingType sweeping_type,
BlinkGC::GCReason reason) {
{
ThreadHeapStatsCollector::DevToolsScope stats1(
Heap().stats_collector(), ThreadHeapStatsCollector::kAtomicPhase,
"forced", reason == BlinkGC::GCReason::kForcedGC);
{
AtomicPauseScope atomic_pause_scope(this);
ThreadHeapStatsCollector::EnabledScope stats2(
Heap().stats_collector(),
ThreadHeapStatsCollector::kAtomicPhaseMarking, "lazySweeping",
sweeping_type == BlinkGC::kLazySweeping ? "yes" : "no", "gcReason",
GcReasonString(reason));
AtomicPauseMarkPrologue(stack_state, marking_type, reason);
AtomicPauseMarkTransitiveClosure();
AtomicPauseMarkEpilogue(marking_type);
}
AtomicPauseSweepAndCompact(marking_type, sweeping_type);
}
if (!IsSweepingInProgress()) {
// Sweeping was finished during the atomic pause. Update statistics needs to
// run outside of the top-most stats scope.
UpdateStatisticsAfterSweeping();
}
}
namespace {
MarkingVisitor::MarkingMode GetMarkingMode(bool should_compact,
bool create_snapshot) {
CHECK(!should_compact || !create_snapshot);
return (create_snapshot)
? MarkingVisitor::kSnapshotMarking
: (should_compact) ? MarkingVisitor::kGlobalMarkingWithCompaction
: MarkingVisitor::kGlobalMarking;
}
} // namespace
void ThreadState::MarkPhasePrologue(BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::GCReason reason) {
SetGCPhase(GCPhase::kMarking);
Heap().CommitCallbackStacks();
const bool take_snapshot = marking_type == BlinkGC::kTakeSnapshot;
const bool should_compact =
!take_snapshot && Heap().Compaction()->ShouldCompact(
&Heap(), stack_state, marking_type, reason);
current_gc_data_.reason = reason;
current_gc_data_.visitor =
IsUnifiedGCMarkingInProgress()
? UnifiedHeapMarkingVisitor::Create(
this, GetMarkingMode(should_compact, take_snapshot),
GetIsolate())
: MarkingVisitor::Create(
this, GetMarkingMode(should_compact, take_snapshot));
current_gc_data_.stack_state = stack_state;
current_gc_data_.marking_type = marking_type;
if (should_compact)
Heap().Compaction()->Initialize(this);
}
void ThreadState::AtomicPausePrologue(BlinkGC::StackState stack_state,
BlinkGC::MarkingType marking_type,
BlinkGC::GCReason reason) {
if (IsMarkingInProgress()) {
// Incremental marking is already in progress. Only update the state
// that is necessary to update.
current_gc_data_.reason = reason;
current_gc_data_.stack_state = stack_state;
Heap().stats_collector()->UpdateReason(reason);
SetGCState(kNoGCScheduled);
DisableIncrementalMarkingBarrier();
} else {
MarkPhasePrologue(stack_state, marking_type, reason);
}
if (marking_type == BlinkGC::kTakeSnapshot)
BlinkGCMemoryDumpProvider::Instance()->ClearProcessDumpForCurrentGC();
if (isolate_ && perform_cleanup_)
perform_cleanup_(isolate_);
DCHECK(InAtomicMarkingPause());
Heap().MakeConsistentForGC();
Heap().ClearArenaAges();
}
void ThreadState::MarkPhaseVisitRoots() {
// StackFrameDepth should be disabled to avoid eagerly tracing into the object
// graph when just visiting roots.
DCHECK(!Heap().GetStackFrameDepth().IsEnabled());
Visitor* visitor = current_gc_data_.visitor.get();
VisitPersistents(visitor);
// Unified garbage collections do not consider DOM wrapper references as
// roots. The cross-component references between V8<->Blink are found using
// collaborative tracing where both GCs report live references to each other.
if (!IsUnifiedGCMarkingInProgress()) {
VisitDOMWrappers(visitor);
}
if (current_gc_data_.stack_state == BlinkGC::kHeapPointersOnStack) {
ThreadHeapStatsCollector::Scope stats_scope(
Heap().stats_collector(), ThreadHeapStatsCollector::kVisitStackRoots);
AddressCache::EnabledScope address_cache_scope(Heap().address_cache());
PushRegistersAndVisitStack();
}
}
bool ThreadState::MarkPhaseAdvanceMarking(TimeTicks deadline) {
StackFrameDepthScope stack_depth_scope(&Heap().GetStackFrameDepth());
return Heap().AdvanceMarking(
reinterpret_cast<MarkingVisitor*>(current_gc_data_.visitor.get()),
deadline);
}
bool ThreadState::ShouldVerifyMarking() const {
bool should_verify_marking =
RuntimeEnabledFeatures::HeapIncrementalMarkingStressEnabled();
#if BUILDFLAG(BLINK_HEAP_VERIFICATION)
should_verify_marking = true;
#endif // BLINK_HEAP_VERIFICATION
return should_verify_marking;
}
void ThreadState::MarkPhaseVisitNotFullyConstructedObjects() {
Heap().MarkNotFullyConstructedObjects(
reinterpret_cast<MarkingVisitor*>(current_gc_data_.visitor.get()));
}
void ThreadState::MarkPhaseEpilogue(BlinkGC::MarkingType marking_type) {
Visitor* visitor = current_gc_data_.visitor.get();
{
// See ProcessHeap::CrossThreadPersistentMutex().
MutexLocker persistent_lock(ProcessHeap::CrossThreadPersistentMutex());
VisitWeakPersistents(visitor);
Heap().WeakProcessing(visitor);
}
Heap().DecommitCallbackStacks();
current_gc_data_.visitor.reset();
if (ShouldVerifyMarking())
VerifyMarking(marking_type);
ProcessHeap::DecreaseTotalAllocatedObjectSize(
Heap().stats_collector()->allocated_bytes_since_prev_gc());
ProcessHeap::DecreaseTotalMarkedObjectSize(
Heap().stats_collector()->previous().marked_bytes);
Heap().stats_collector()->NotifyMarkingCompleted();
WTF::Partitions::ReportMemoryUsageHistogram();
if (invalidate_dead_objects_in_wrappers_marking_deque_)
invalidate_dead_objects_in_wrappers_marking_deque_(isolate_);
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, total_object_space_histogram,
("BlinkGC.TotalObjectSpace", 0, 4 * 1024 * 1024, 50));
total_object_space_histogram.Count(ProcessHeap::TotalAllocatedObjectSize() /
1024);
DEFINE_THREAD_SAFE_STATIC_LOCAL(
CustomCountHistogram, total_allocated_space_histogram,
("BlinkGC.TotalAllocatedSpace", 0, 4 * 1024 * 1024, 50));
total_allocated_space_histogram.Count(ProcessHeap::TotalAllocatedSpace() /
1024);
}
void ThreadState::VerifyMarking(BlinkGC::MarkingType marking_type) {
// Marking for snapshot does not clear unreachable weak fields prohibiting
// verification of markbits as we leave behind non-marked non-cleared weak
// fields.
if (marking_type == BlinkGC::kTakeSnapshot)
return;
Heap().VerifyMarking();
}
void ThreadState::CollectAllGarbage() {
// We need to run multiple GCs to collect a chain of persistent handles.
size_t previous_live_objects = 0;
for (int i = 0; i < 5; ++i) {
CollectGarbage(BlinkGC::kNoHeapPointersOnStack, BlinkGC::kAtomicMarking,
BlinkGC::kEagerSweeping, BlinkGC::GCReason::kForcedGC);
const size_t live_objects =
Heap().stats_collector()->previous().marked_bytes;
if (live_objects == previous_live_objects)
break;
previous_live_objects = live_objects;
}
}
void ThreadState::UpdateIncrementalMarkingStepDuration() {
if (!IsIncrementalMarking())
return;
TimeDelta time_left = Heap().stats_collector()->estimated_marking_time() -
Heap().stats_collector()->marking_time_so_far();
// Increase step size if estimated time left is increasing.
if (previous_incremental_marking_time_left_ < time_left) {
constexpr double ratio = 2.0;
next_incremental_marking_step_duration_ *= ratio;
}
previous_incremental_marking_time_left_ = time_left;
}
} // namespace blink