| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #include "vm/heap/pages.h" |
| |
| #include "platform/assert.h" |
| #include "platform/leak_sanitizer.h" |
| #include "vm/dart.h" |
| #include "vm/heap/become.h" |
| #include "vm/heap/compactor.h" |
| #include "vm/heap/marker.h" |
| #include "vm/heap/safepoint.h" |
| #include "vm/heap/sweeper.h" |
| #include "vm/lockers.h" |
| #include "vm/log.h" |
| #include "vm/object.h" |
| #include "vm/object_set.h" |
| #include "vm/os_thread.h" |
| #include "vm/virtual_memory.h" |
| |
| namespace dart { |
| |
| DEFINE_FLAG(int, |
| old_gen_growth_space_ratio, |
| 20, |
| "The desired maximum percentage of free space after old gen GC"); |
| DEFINE_FLAG(int, |
| old_gen_growth_time_ratio, |
| 3, |
| "The desired maximum percentage of time spent in old gen GC"); |
| DEFINE_FLAG(int, |
| old_gen_growth_rate, |
| 280, |
| "The max number of pages the old generation can grow at a time"); |
| DEFINE_FLAG(bool, |
| print_free_list_before_gc, |
| false, |
| "Print free list statistics before a GC"); |
| DEFINE_FLAG(bool, |
| print_free_list_after_gc, |
| false, |
| "Print free list statistics after a GC"); |
| DEFINE_FLAG(bool, log_growth, false, "Log PageSpace growth policy decisions."); |
| |
| OldPage* OldPage::Allocate(intptr_t size_in_words, |
| PageType type, |
| const char* name) { |
| const bool executable = type == kExecutable; |
| |
| VirtualMemory* memory = VirtualMemory::AllocateAligned( |
| size_in_words << kWordSizeLog2, kOldPageSize, executable, name); |
| if (memory == NULL) { |
| return NULL; |
| } |
| |
| OldPage* result = reinterpret_cast<OldPage*>(memory->address()); |
| ASSERT(result != NULL); |
| result->memory_ = memory; |
| result->next_ = NULL; |
| result->used_in_bytes_ = 0; |
| result->forwarding_page_ = NULL; |
| result->card_table_ = NULL; |
| result->type_ = type; |
| |
| LSAN_REGISTER_ROOT_REGION(result, sizeof(*result)); |
| |
| return result; |
| } |
| |
| void OldPage::Deallocate() { |
| if (card_table_ != NULL) { |
| free(card_table_); |
| card_table_ = NULL; |
| } |
| |
| bool image_page = is_image_page(); |
| |
| if (!image_page) { |
| LSAN_UNREGISTER_ROOT_REGION(this, sizeof(*this)); |
| } |
| |
| // For a regular heap pages, the memory for this object will become |
| // unavailable after the delete below. |
| delete memory_; |
| |
| // For a heap page from a snapshot, the OldPage object lives in the malloc |
| // heap rather than the page itself. |
| if (image_page) { |
| free(this); |
| } |
| } |
| |
| void OldPage::VisitObjects(ObjectVisitor* visitor) const { |
| ASSERT(Thread::Current()->IsAtSafepoint()); |
| NoSafepointScope no_safepoint; |
| uword obj_addr = object_start(); |
| uword end_addr = object_end(); |
| while (obj_addr < end_addr) { |
| ObjectPtr raw_obj = UntaggedObject::FromAddr(obj_addr); |
| visitor->VisitObject(raw_obj); |
| obj_addr += raw_obj->untag()->HeapSize(); |
| } |
| ASSERT(obj_addr == end_addr); |
| } |
| |
| void OldPage::VisitObjectPointers(ObjectPointerVisitor* visitor) const { |
| ASSERT(Thread::Current()->IsAtSafepoint() || |
| (Thread::Current()->task_kind() == Thread::kCompactorTask)); |
| NoSafepointScope no_safepoint; |
| uword obj_addr = object_start(); |
| uword end_addr = object_end(); |
| while (obj_addr < end_addr) { |
| ObjectPtr raw_obj = UntaggedObject::FromAddr(obj_addr); |
| obj_addr += raw_obj->untag()->VisitPointers(visitor); |
| } |
| ASSERT(obj_addr == end_addr); |
| } |
| |
| void OldPage::VisitRememberedCards(ObjectPointerVisitor* visitor) { |
| ASSERT(Thread::Current()->IsAtSafepoint() || |
| (Thread::Current()->task_kind() == Thread::kScavengerTask)); |
| NoSafepointScope no_safepoint; |
| |
| if (card_table_ == NULL) { |
| return; |
| } |
| |
| bool table_is_empty = false; |
| |
| ArrayPtr obj = |
| static_cast<ArrayPtr>(UntaggedObject::FromAddr(object_start())); |
| ASSERT(obj->IsArray()); |
| ASSERT(obj->untag()->IsCardRemembered()); |
| CompressedObjectPtr* obj_from = obj->untag()->from(); |
| CompressedObjectPtr* obj_to = |
| obj->untag()->to(Smi::Value(obj->untag()->length())); |
| uword heap_base = obj.heap_base(); |
| |
| const intptr_t size = card_table_size(); |
| for (intptr_t i = 0; i < size; i++) { |
| if (card_table_[i] != 0) { |
| CompressedObjectPtr* card_from = |
| reinterpret_cast<CompressedObjectPtr*>(this) + |
| (i << kSlotsPerCardLog2); |
| CompressedObjectPtr* card_to = |
| reinterpret_cast<CompressedObjectPtr*>(card_from) + |
| (1 << kSlotsPerCardLog2) - 1; |
| // Minus 1 because to is inclusive. |
| |
| if (card_from < obj_from) { |
| // First card overlaps with header. |
| card_from = obj_from; |
| } |
| if (card_to > obj_to) { |
| // Last card(s) may extend past the object. Array truncation can make |
| // this happen for more than one card. |
| card_to = obj_to; |
| } |
| |
| visitor->VisitCompressedPointers(heap_base, card_from, card_to); |
| |
| bool has_new_target = false; |
| for (CompressedObjectPtr* slot = card_from; slot <= card_to; slot++) { |
| if ((*slot)->IsNewObjectMayBeSmi()) { |
| has_new_target = true; |
| break; |
| } |
| } |
| |
| if (has_new_target) { |
| // Card remains remembered. |
| table_is_empty = false; |
| } else { |
| card_table_[i] = 0; |
| } |
| } |
| } |
| |
| if (table_is_empty) { |
| free(card_table_); |
| card_table_ = NULL; |
| } |
| } |
| |
| ObjectPtr OldPage::FindObject(FindObjectVisitor* visitor) const { |
| uword obj_addr = object_start(); |
| uword end_addr = object_end(); |
| if (visitor->VisitRange(obj_addr, end_addr)) { |
| while (obj_addr < end_addr) { |
| ObjectPtr raw_obj = UntaggedObject::FromAddr(obj_addr); |
| uword next_obj_addr = obj_addr + raw_obj->untag()->HeapSize(); |
| if (visitor->VisitRange(obj_addr, next_obj_addr) && |
| raw_obj->untag()->FindObject(visitor)) { |
| return raw_obj; // Found object, return it. |
| } |
| obj_addr = next_obj_addr; |
| } |
| ASSERT(obj_addr == end_addr); |
| } |
| return Object::null(); |
| } |
| |
| void OldPage::WriteProtect(bool read_only) { |
| ASSERT(!is_image_page()); |
| |
| VirtualMemory::Protection prot; |
| if (read_only) { |
| if ((type_ == kExecutable) && (memory_->AliasOffset() == 0)) { |
| prot = VirtualMemory::kReadExecute; |
| } else { |
| prot = VirtualMemory::kReadOnly; |
| } |
| } else { |
| prot = VirtualMemory::kReadWrite; |
| } |
| memory_->Protect(prot); |
| } |
| |
| // The initial estimate of how many words we can mark per microsecond (usage |
| // before / mark-sweep time). This is a conservative value observed running |
| // Flutter on a Nexus 4. After the first mark-sweep, we instead use a value |
| // based on the device's actual speed. |
| static const intptr_t kConservativeInitialMarkSpeed = 20; |
| |
| PageSpace::PageSpace(Heap* heap, intptr_t max_capacity_in_words) |
| : heap_(heap), |
| num_freelists_(Utils::Maximum(FLAG_scavenger_tasks, 1) + 1), |
| freelists_(new FreeList[num_freelists_]), |
| pages_lock_(), |
| max_capacity_in_words_(max_capacity_in_words), |
| usage_(), |
| allocated_black_in_words_(0), |
| tasks_lock_(), |
| tasks_(0), |
| concurrent_marker_tasks_(0), |
| phase_(kDone), |
| #if defined(DEBUG) |
| iterating_thread_(NULL), |
| #endif |
| page_space_controller_(heap, |
| FLAG_old_gen_growth_space_ratio, |
| FLAG_old_gen_growth_rate, |
| FLAG_old_gen_growth_time_ratio), |
| marker_(NULL), |
| gc_time_micros_(0), |
| collections_(0), |
| mark_words_per_micro_(kConservativeInitialMarkSpeed), |
| enable_concurrent_mark_(FLAG_concurrent_mark) { |
| // We aren't holding the lock but no one can reference us yet. |
| UpdateMaxCapacityLocked(); |
| UpdateMaxUsed(); |
| |
| for (intptr_t i = 0; i < num_freelists_; i++) { |
| freelists_[i].Reset(); |
| } |
| |
| TryReserveForOOM(); |
| } |
| |
| PageSpace::~PageSpace() { |
| { |
| MonitorLocker ml(tasks_lock()); |
| while (tasks() > 0) { |
| ml.Wait(); |
| } |
| } |
| FreePages(pages_); |
| FreePages(exec_pages_); |
| FreePages(large_pages_); |
| FreePages(image_pages_); |
| ASSERT(marker_ == NULL); |
| delete[] freelists_; |
| } |
| |
| intptr_t PageSpace::LargePageSizeInWordsFor(intptr_t size) { |
| intptr_t page_size = Utils::RoundUp(size + OldPage::ObjectStartOffset(), |
| VirtualMemory::PageSize()); |
| return page_size >> kWordSizeLog2; |
| } |
| |
| void PageSpace::AddPageLocked(OldPage* page) { |
| if (pages_ == nullptr) { |
| pages_ = page; |
| } else { |
| pages_tail_->set_next(page); |
| } |
| pages_tail_ = page; |
| } |
| |
| void PageSpace::AddLargePageLocked(OldPage* page) { |
| if (large_pages_ == nullptr) { |
| large_pages_ = page; |
| } else { |
| large_pages_tail_->set_next(page); |
| } |
| large_pages_tail_ = page; |
| } |
| |
| void PageSpace::AddExecPageLocked(OldPage* page) { |
| if (exec_pages_ == nullptr) { |
| exec_pages_ = page; |
| } else { |
| if (FLAG_write_protect_code) { |
| exec_pages_tail_->WriteProtect(false); |
| } |
| exec_pages_tail_->set_next(page); |
| if (FLAG_write_protect_code) { |
| exec_pages_tail_->WriteProtect(true); |
| } |
| } |
| exec_pages_tail_ = page; |
| } |
| |
| void PageSpace::RemovePageLocked(OldPage* page, OldPage* previous_page) { |
| if (previous_page != NULL) { |
| previous_page->set_next(page->next()); |
| } else { |
| pages_ = page->next(); |
| } |
| if (page == pages_tail_) { |
| pages_tail_ = previous_page; |
| } |
| } |
| |
| void PageSpace::RemoveLargePageLocked(OldPage* page, OldPage* previous_page) { |
| if (previous_page != NULL) { |
| previous_page->set_next(page->next()); |
| } else { |
| large_pages_ = page->next(); |
| } |
| if (page == large_pages_tail_) { |
| large_pages_tail_ = previous_page; |
| } |
| } |
| |
| void PageSpace::RemoveExecPageLocked(OldPage* page, OldPage* previous_page) { |
| if (previous_page != NULL) { |
| previous_page->set_next(page->next()); |
| } else { |
| exec_pages_ = page->next(); |
| } |
| if (page == exec_pages_tail_) { |
| exec_pages_tail_ = previous_page; |
| } |
| } |
| |
| OldPage* PageSpace::AllocatePage(OldPage::PageType type, bool link) { |
| { |
| MutexLocker ml(&pages_lock_); |
| if (!CanIncreaseCapacityInWordsLocked(kOldPageSizeInWords)) { |
| return nullptr; |
| } |
| IncreaseCapacityInWordsLocked(kOldPageSizeInWords); |
| } |
| const bool is_exec = (type == OldPage::kExecutable); |
| const char* name = Heap::RegionName(is_exec ? Heap::kCode : Heap::kOld); |
| OldPage* page = OldPage::Allocate(kOldPageSizeInWords, type, name); |
| if (page == nullptr) { |
| RELEASE_ASSERT(!FLAG_abort_on_oom); |
| IncreaseCapacityInWords(-kOldPageSizeInWords); |
| return nullptr; |
| } |
| |
| MutexLocker ml(&pages_lock_); |
| if (link) { |
| if (is_exec) { |
| AddExecPageLocked(page); |
| } else { |
| AddPageLocked(page); |
| } |
| } |
| |
| page->set_object_end(page->memory_->end()); |
| if ((type != OldPage::kExecutable) && (heap_ != nullptr) && |
| (!heap_->is_vm_isolate())) { |
| page->AllocateForwardingPage(); |
| } |
| return page; |
| } |
| |
| OldPage* PageSpace::AllocateLargePage(intptr_t size, OldPage::PageType type) { |
| const intptr_t page_size_in_words = LargePageSizeInWordsFor(size); |
| { |
| MutexLocker ml(&pages_lock_); |
| if (!CanIncreaseCapacityInWordsLocked(page_size_in_words)) { |
| return nullptr; |
| } |
| IncreaseCapacityInWordsLocked(page_size_in_words); |
| } |
| const bool is_exec = (type == OldPage::kExecutable); |
| const char* name = Heap::RegionName(is_exec ? Heap::kCode : Heap::kOld); |
| OldPage* page = OldPage::Allocate(page_size_in_words, type, name); |
| |
| MutexLocker ml(&pages_lock_); |
| if (page == nullptr) { |
| IncreaseCapacityInWordsLocked(-page_size_in_words); |
| return nullptr; |
| } else { |
| intptr_t actual_size_in_words = page->memory_->size() >> kWordSizeLog2; |
| if (actual_size_in_words != page_size_in_words) { |
| IncreaseCapacityInWordsLocked(actual_size_in_words - page_size_in_words); |
| } |
| } |
| if (is_exec) { |
| AddExecPageLocked(page); |
| } else { |
| AddLargePageLocked(page); |
| } |
| |
| // Only one object in this page (at least until Array::MakeFixedLength |
| // is called). |
| page->set_object_end(page->object_start() + size); |
| return page; |
| } |
| |
| void PageSpace::TruncateLargePage(OldPage* page, |
| intptr_t new_object_size_in_bytes) { |
| const intptr_t old_object_size_in_bytes = |
| page->object_end() - page->object_start(); |
| ASSERT(new_object_size_in_bytes <= old_object_size_in_bytes); |
| const intptr_t new_page_size_in_words = |
| LargePageSizeInWordsFor(new_object_size_in_bytes); |
| VirtualMemory* memory = page->memory_; |
| const intptr_t old_page_size_in_words = (memory->size() >> kWordSizeLog2); |
| if (new_page_size_in_words < old_page_size_in_words) { |
| memory->Truncate(new_page_size_in_words << kWordSizeLog2); |
| IncreaseCapacityInWords(new_page_size_in_words - old_page_size_in_words); |
| page->set_object_end(page->object_start() + new_object_size_in_bytes); |
| } |
| } |
| |
| void PageSpace::FreePage(OldPage* page, OldPage* previous_page) { |
| bool is_exec = (page->type() == OldPage::kExecutable); |
| { |
| MutexLocker ml(&pages_lock_); |
| IncreaseCapacityInWordsLocked(-(page->memory_->size() >> kWordSizeLog2)); |
| if (is_exec) { |
| RemoveExecPageLocked(page, previous_page); |
| } else { |
| RemovePageLocked(page, previous_page); |
| } |
| } |
| // TODO(iposva): Consider adding to a pool of empty pages. |
| page->Deallocate(); |
| } |
| |
| void PageSpace::FreeLargePage(OldPage* page, OldPage* previous_page) { |
| ASSERT(page->type() != OldPage::kExecutable); |
| MutexLocker ml(&pages_lock_); |
| IncreaseCapacityInWordsLocked(-(page->memory_->size() >> kWordSizeLog2)); |
| RemoveLargePageLocked(page, previous_page); |
| page->Deallocate(); |
| } |
| |
| void PageSpace::FreePages(OldPage* pages) { |
| OldPage* page = pages; |
| while (page != NULL) { |
| OldPage* next = page->next(); |
| page->Deallocate(); |
| page = next; |
| } |
| } |
| |
| void PageSpace::EvaluateConcurrentMarking(GrowthPolicy growth_policy) { |
| if (growth_policy != kForceGrowth) { |
| ASSERT(GrowthControlState()); |
| if (heap_ != NULL) { // Some unit tests. |
| Thread* thread = Thread::Current(); |
| if (thread->CanCollectGarbage()) { |
| heap_->CheckFinishConcurrentMarking(thread); |
| heap_->CheckStartConcurrentMarking(thread, Heap::kOldSpace); |
| } |
| } |
| } |
| } |
| |
| uword PageSpace::TryAllocateInFreshPage(intptr_t size, |
| FreeList* freelist, |
| OldPage::PageType type, |
| GrowthPolicy growth_policy, |
| bool is_locked) { |
| ASSERT(Heap::IsAllocatableViaFreeLists(size)); |
| |
| EvaluateConcurrentMarking(growth_policy); |
| |
| uword result = 0; |
| SpaceUsage after_allocation = GetCurrentUsage(); |
| after_allocation.used_in_words += size >> kWordSizeLog2; |
| // Can we grow by one page? |
| after_allocation.capacity_in_words += kOldPageSizeInWords; |
| if (growth_policy == kForceGrowth || |
| !page_space_controller_.ReachedHardThreshold(after_allocation)) { |
| OldPage* page = AllocatePage(type); |
| if (page == NULL) { |
| return 0; |
| } |
| // Start of the newly allocated page is the allocated object. |
| result = page->object_start(); |
| // Note: usage_.capacity_in_words is increased by AllocatePage. |
| usage_.used_in_words += (size >> kWordSizeLog2); |
| // Enqueue the remainder in the free list. |
| uword free_start = result + size; |
| intptr_t free_size = page->object_end() - free_start; |
| if (free_size > 0) { |
| if (is_locked) { |
| freelist->FreeLocked(free_start, free_size); |
| } else { |
| freelist->Free(free_start, free_size); |
| } |
| } |
| } |
| return result; |
| } |
| |
| uword PageSpace::TryAllocateInFreshLargePage(intptr_t size, |
| OldPage::PageType type, |
| GrowthPolicy growth_policy) { |
| ASSERT(!Heap::IsAllocatableViaFreeLists(size)); |
| |
| EvaluateConcurrentMarking(growth_policy); |
| |
| intptr_t page_size_in_words = LargePageSizeInWordsFor(size); |
| if ((page_size_in_words << kWordSizeLog2) < size) { |
| // On overflow we fail to allocate. |
| return 0; |
| } |
| |
| uword result = 0; |
| SpaceUsage after_allocation = GetCurrentUsage(); |
| after_allocation.used_in_words += size >> kWordSizeLog2; |
| after_allocation.capacity_in_words += page_size_in_words; |
| if (growth_policy == kForceGrowth || |
| !page_space_controller_.ReachedHardThreshold(after_allocation)) { |
| OldPage* page = AllocateLargePage(size, type); |
| if (page != NULL) { |
| result = page->object_start(); |
| // Note: usage_.capacity_in_words is increased by AllocateLargePage. |
| usage_.used_in_words += (size >> kWordSizeLog2); |
| } |
| } |
| return result; |
| } |
| |
| uword PageSpace::TryAllocateInternal(intptr_t size, |
| FreeList* freelist, |
| OldPage::PageType type, |
| GrowthPolicy growth_policy, |
| bool is_protected, |
| bool is_locked) { |
| ASSERT(size >= kObjectAlignment); |
| ASSERT(Utils::IsAligned(size, kObjectAlignment)); |
| uword result = 0; |
| if (Heap::IsAllocatableViaFreeLists(size)) { |
| if (is_locked) { |
| result = freelist->TryAllocateLocked(size, is_protected); |
| } else { |
| result = freelist->TryAllocate(size, is_protected); |
| } |
| if (result == 0) { |
| result = TryAllocateInFreshPage(size, freelist, type, growth_policy, |
| is_locked); |
| // usage_ is updated by the call above. |
| } else { |
| usage_.used_in_words += (size >> kWordSizeLog2); |
| } |
| } else { |
| result = TryAllocateInFreshLargePage(size, type, growth_policy); |
| // usage_ is updated by the call above. |
| } |
| ASSERT((result & kObjectAlignmentMask) == kOldObjectAlignmentOffset); |
| return result; |
| } |
| |
| void PageSpace::AcquireLock(FreeList* freelist) { |
| freelist->mutex()->Lock(); |
| } |
| |
| void PageSpace::ReleaseLock(FreeList* freelist) { |
| intptr_t size = freelist->TakeUnaccountedSizeLocked(); |
| usage_.used_in_words += (size >> kWordSizeLog2); |
| freelist->mutex()->Unlock(); |
| } |
| |
| class BasePageIterator : ValueObject { |
| public: |
| explicit BasePageIterator(const PageSpace* space) : space_(space) {} |
| |
| OldPage* page() const { return page_; } |
| |
| bool Done() const { return page_ == NULL; } |
| |
| void Advance() { |
| ASSERT(!Done()); |
| page_ = page_->next(); |
| if ((page_ == NULL) && (list_ == kRegular)) { |
| list_ = kExecutable; |
| page_ = space_->exec_pages_; |
| } |
| if ((page_ == NULL) && (list_ == kExecutable)) { |
| list_ = kLarge; |
| page_ = space_->large_pages_; |
| } |
| if ((page_ == NULL) && (list_ == kLarge)) { |
| list_ = kImage; |
| page_ = space_->image_pages_; |
| } |
| ASSERT((page_ != NULL) || (list_ == kImage)); |
| } |
| |
| protected: |
| enum List { kRegular, kExecutable, kLarge, kImage }; |
| |
| void Initialize() { |
| list_ = kRegular; |
| page_ = space_->pages_; |
| if (page_ == NULL) { |
| list_ = kExecutable; |
| page_ = space_->exec_pages_; |
| if (page_ == NULL) { |
| list_ = kLarge; |
| page_ = space_->large_pages_; |
| if (page_ == NULL) { |
| list_ = kImage; |
| page_ = space_->image_pages_; |
| } |
| } |
| } |
| } |
| |
| const PageSpace* space_ = nullptr; |
| List list_; |
| OldPage* page_ = nullptr; |
| }; |
| |
| // Provides unsafe access to all pages. Assumes pages are walkable. |
| class UnsafeExclusivePageIterator : public BasePageIterator { |
| public: |
| explicit UnsafeExclusivePageIterator(const PageSpace* space) |
| : BasePageIterator(space) { |
| Initialize(); |
| } |
| }; |
| |
| // Provides exclusive access to all pages, and ensures they are walkable. |
| class ExclusivePageIterator : public BasePageIterator { |
| public: |
| explicit ExclusivePageIterator(const PageSpace* space) |
| : BasePageIterator(space), ml_(&space->pages_lock_) { |
| space_->MakeIterable(); |
| Initialize(); |
| } |
| |
| private: |
| MutexLocker ml_; |
| NoSafepointScope no_safepoint; |
| }; |
| |
| // Provides exclusive access to code pages, and ensures they are walkable. |
| // NOTE: This does not iterate over large pages which can contain code. |
| class ExclusiveCodePageIterator : ValueObject { |
| public: |
| explicit ExclusiveCodePageIterator(const PageSpace* space) |
| : space_(space), ml_(&space->pages_lock_) { |
| space_->MakeIterable(); |
| page_ = space_->exec_pages_; |
| } |
| OldPage* page() const { return page_; } |
| bool Done() const { return page_ == NULL; } |
| void Advance() { |
| ASSERT(!Done()); |
| page_ = page_->next(); |
| } |
| |
| private: |
| const PageSpace* space_; |
| MutexLocker ml_; |
| NoSafepointScope no_safepoint; |
| OldPage* page_; |
| }; |
| |
| void PageSpace::MakeIterable() const { |
| // Assert not called from concurrent sweeper task. |
| // TODO(koda): Use thread/task identity when implemented. |
| ASSERT(IsolateGroup::Current()->heap() != NULL); |
| for (intptr_t i = 0; i < num_freelists_; i++) { |
| freelists_[i].MakeIterable(); |
| } |
| } |
| |
| void PageSpace::AbandonBumpAllocation() { |
| for (intptr_t i = 0; i < num_freelists_; i++) { |
| freelists_[i].AbandonBumpAllocation(); |
| } |
| } |
| |
| void PageSpace::AbandonMarkingForShutdown() { |
| delete marker_; |
| marker_ = NULL; |
| } |
| |
| void PageSpace::UpdateMaxCapacityLocked() { |
| if (heap_ == NULL) { |
| // Some unit tests. |
| return; |
| } |
| ASSERT(heap_ != NULL); |
| ASSERT(heap_->isolate_group() != NULL); |
| auto isolate_group = heap_->isolate_group(); |
| isolate_group->GetHeapOldCapacityMaxMetric()->SetValue( |
| static_cast<int64_t>(usage_.capacity_in_words) * kWordSize); |
| } |
| |
| void PageSpace::UpdateMaxUsed() { |
| if (heap_ == NULL) { |
| // Some unit tests. |
| return; |
| } |
| ASSERT(heap_ != NULL); |
| ASSERT(heap_->isolate_group() != NULL); |
| auto isolate_group = heap_->isolate_group(); |
| isolate_group->GetHeapOldUsedMaxMetric()->SetValue(UsedInWords() * kWordSize); |
| } |
| |
| bool PageSpace::Contains(uword addr) const { |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if (it.page()->Contains(addr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PageSpace::ContainsUnsafe(uword addr) const { |
| for (UnsafeExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if (it.page()->Contains(addr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PageSpace::Contains(uword addr, OldPage::PageType type) const { |
| if (type == OldPage::kExecutable) { |
| // Fast path executable pages. |
| for (ExclusiveCodePageIterator it(this); !it.Done(); it.Advance()) { |
| if (it.page()->Contains(addr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if ((it.page()->type() == type) && it.page()->Contains(addr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool PageSpace::DataContains(uword addr) const { |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if ((it.page()->type() != OldPage::kExecutable) && |
| it.page()->Contains(addr)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void PageSpace::AddRegionsToObjectSet(ObjectSet* set) const { |
| ASSERT((pages_ != NULL) || (exec_pages_ != NULL) || (large_pages_ != NULL)); |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| set->AddRegion(it.page()->object_start(), it.page()->object_end()); |
| } |
| } |
| |
| void PageSpace::VisitObjects(ObjectVisitor* visitor) const { |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| it.page()->VisitObjects(visitor); |
| } |
| } |
| |
| void PageSpace::VisitObjectsNoImagePages(ObjectVisitor* visitor) const { |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if (!it.page()->is_image_page()) { |
| it.page()->VisitObjects(visitor); |
| } |
| } |
| } |
| |
| void PageSpace::VisitObjectsImagePages(ObjectVisitor* visitor) const { |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if (it.page()->is_image_page()) { |
| it.page()->VisitObjects(visitor); |
| } |
| } |
| } |
| |
| void PageSpace::VisitObjectPointers(ObjectPointerVisitor* visitor) const { |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| it.page()->VisitObjectPointers(visitor); |
| } |
| } |
| |
| void PageSpace::VisitRememberedCards(ObjectPointerVisitor* visitor) const { |
| ASSERT(Thread::Current()->IsAtSafepoint() || |
| (Thread::Current()->task_kind() == Thread::kScavengerTask)); |
| |
| // Wait for the sweeper to finish mutating the large page list. |
| { |
| MonitorLocker ml(tasks_lock()); |
| while (phase() == kSweepingLarge) { |
| ml.Wait(); // No safepoint check. |
| } |
| } |
| |
| // Large pages may be added concurrently due to promotion in another scavenge |
| // worker, so terminate the traversal when we hit the tail we saw while |
| // holding the pages lock, instead of at NULL, otherwise we are racing when we |
| // read OldPage::next_ and OldPage::remembered_cards_. |
| OldPage* page; |
| OldPage* tail; |
| { |
| MutexLocker ml(&pages_lock_); |
| page = large_pages_; |
| tail = large_pages_tail_; |
| } |
| while (page != nullptr) { |
| page->VisitRememberedCards(visitor); |
| if (page == tail) break; |
| page = page->next(); |
| } |
| } |
| |
| ObjectPtr PageSpace::FindObject(FindObjectVisitor* visitor, |
| OldPage::PageType type) const { |
| if (type == OldPage::kExecutable) { |
| // Fast path executable pages. |
| for (ExclusiveCodePageIterator it(this); !it.Done(); it.Advance()) { |
| ObjectPtr obj = it.page()->FindObject(visitor); |
| if (obj != Object::null()) { |
| return obj; |
| } |
| } |
| return Object::null(); |
| } |
| |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if (it.page()->type() == type) { |
| ObjectPtr obj = it.page()->FindObject(visitor); |
| if (obj != Object::null()) { |
| return obj; |
| } |
| } |
| } |
| return Object::null(); |
| } |
| |
| void PageSpace::WriteProtect(bool read_only) { |
| if (read_only) { |
| // Avoid MakeIterable trying to write to the heap. |
| AbandonBumpAllocation(); |
| } |
| for (ExclusivePageIterator it(this); !it.Done(); it.Advance()) { |
| if (!it.page()->is_image_page()) { |
| it.page()->WriteProtect(read_only); |
| } |
| } |
| } |
| |
| #ifndef PRODUCT |
| void PageSpace::PrintToJSONObject(JSONObject* object) const { |
| auto isolate_group = IsolateGroup::Current(); |
| ASSERT(isolate_group != nullptr); |
| JSONObject space(object, "old"); |
| space.AddProperty("type", "HeapSpace"); |
| space.AddProperty("name", "old"); |
| space.AddProperty("vmName", "PageSpace"); |
| space.AddProperty("collections", collections()); |
| space.AddProperty64("used", UsedInWords() * kWordSize); |
| space.AddProperty64("capacity", CapacityInWords() * kWordSize); |
| space.AddProperty64("external", ExternalInWords() * kWordSize); |
| space.AddProperty("time", MicrosecondsToSeconds(gc_time_micros())); |
| if (collections() > 0) { |
| int64_t run_time = isolate_group->UptimeMicros(); |
| run_time = Utils::Maximum(run_time, static_cast<int64_t>(0)); |
| double run_time_millis = MicrosecondsToMilliseconds(run_time); |
| double avg_time_between_collections = |
| run_time_millis / static_cast<double>(collections()); |
| space.AddProperty("avgCollectionPeriodMillis", |
| avg_time_between_collections); |
| } else { |
| space.AddProperty("avgCollectionPeriodMillis", 0.0); |
| } |
| } |
| |
| class HeapMapAsJSONVisitor : public ObjectVisitor { |
| public: |
| explicit HeapMapAsJSONVisitor(JSONArray* array) : array_(array) {} |
| virtual void VisitObject(ObjectPtr obj) { |
| array_->AddValue(obj->untag()->HeapSize() / kObjectAlignment); |
| array_->AddValue(obj->GetClassId()); |
| } |
| |
| private: |
| JSONArray* array_; |
| }; |
| |
| void PageSpace::PrintHeapMapToJSONStream(IsolateGroup* isolate_group, |
| JSONStream* stream) const { |
| JSONObject heap_map(stream); |
| heap_map.AddProperty("type", "HeapMap"); |
| heap_map.AddProperty("freeClassId", static_cast<intptr_t>(kFreeListElement)); |
| heap_map.AddProperty("unitSizeBytes", |
| static_cast<intptr_t>(kObjectAlignment)); |
| heap_map.AddProperty("pageSizeBytes", kOldPageSizeInWords * kWordSize); |
| { |
| JSONObject class_list(&heap_map, "classList"); |
| isolate_group->class_table()->PrintToJSONObject(&class_list); |
| } |
| { |
| // "pages" is an array [page0, page1, ..., pageN], each page of the form |
| // {"object_start": "0x...", "objects": [size, class id, size, ...]} |
| // TODO(19445): Use ExclusivePageIterator once HeapMap supports large pages. |
| HeapIterationScope iteration(Thread::Current()); |
| MutexLocker ml(&pages_lock_); |
| MakeIterable(); |
| JSONArray all_pages(&heap_map, "pages"); |
| for (OldPage* page = pages_; page != NULL; page = page->next()) { |
| JSONObject page_container(&all_pages); |
| page_container.AddPropertyF("objectStart", "0x%" Px "", |
| page->object_start()); |
| JSONArray page_map(&page_container, "objects"); |
| HeapMapAsJSONVisitor printer(&page_map); |
| page->VisitObjects(&printer); |
| } |
| for (OldPage* page = exec_pages_; page != NULL; page = page->next()) { |
| JSONObject page_container(&all_pages); |
| page_container.AddPropertyF("objectStart", "0x%" Px "", |
| page->object_start()); |
| JSONArray page_map(&page_container, "objects"); |
| HeapMapAsJSONVisitor printer(&page_map); |
| page->VisitObjects(&printer); |
| } |
| } |
| } |
| #endif // PRODUCT |
| |
| void PageSpace::WriteProtectCode(bool read_only) { |
| if (FLAG_write_protect_code) { |
| MutexLocker ml(&pages_lock_); |
| NoSafepointScope no_safepoint; |
| // No need to go through all of the data pages first. |
| OldPage* page = exec_pages_; |
| while (page != NULL) { |
| ASSERT(page->type() == OldPage::kExecutable); |
| page->WriteProtect(read_only); |
| page = page->next(); |
| } |
| page = large_pages_; |
| while (page != NULL) { |
| if (page->type() == OldPage::kExecutable) { |
| page->WriteProtect(read_only); |
| } |
| page = page->next(); |
| } |
| } |
| } |
| |
| bool PageSpace::ShouldStartIdleMarkSweep(int64_t deadline) { |
| // To make a consistent decision, we should not yield for a safepoint in the |
| // middle of deciding whether to perform an idle GC. |
| NoSafepointScope no_safepoint; |
| |
| if (!page_space_controller_.ReachedIdleThreshold(usage_)) { |
| return false; |
| } |
| |
| { |
| MonitorLocker locker(tasks_lock()); |
| if (tasks() > 0) { |
| // A concurrent sweeper is running. If we start a mark sweep now |
| // we'll have to wait for it, and this wait time is not included in |
| // mark_words_per_micro_. |
| return false; |
| } |
| } |
| |
| // This uses the size of new-space because the pause time to start concurrent |
| // marking is related to the size of the root set, which is mostly new-space. |
| int64_t estimated_mark_completion = |
| OS::GetCurrentMonotonicMicros() + |
| heap_->new_space()->UsedInWords() / mark_words_per_micro_; |
| return estimated_mark_completion <= deadline; |
| } |
| |
| bool PageSpace::ShouldPerformIdleMarkCompact(int64_t deadline) { |
| // To make a consistent decision, we should not yield for a safepoint in the |
| // middle of deciding whether to perform an idle GC. |
| NoSafepointScope no_safepoint; |
| |
| // Discount two pages to account for the newest data and code pages, whose |
| // partial use doesn't indicate fragmentation. |
| const intptr_t excess_in_words = |
| usage_.capacity_in_words - usage_.used_in_words - 2 * kOldPageSizeInWords; |
| const double excess_ratio = static_cast<double>(excess_in_words) / |
| static_cast<double>(usage_.capacity_in_words); |
| const bool fragmented = excess_ratio > 0.05; |
| |
| if (!fragmented && !page_space_controller_.ReachedIdleThreshold(usage_)) { |
| return false; |
| } |
| |
| { |
| MonitorLocker locker(tasks_lock()); |
| if (tasks() > 0) { |
| // A concurrent sweeper is running. If we start a mark sweep now |
| // we'll have to wait for it, and this wait time is not included in |
| // mark_words_per_micro_. |
| return false; |
| } |
| } |
| |
| // Assuming compaction takes as long as marking. |
| intptr_t mark_compact_words_per_micro = mark_words_per_micro_ / 2; |
| if (mark_compact_words_per_micro == 0) { |
| mark_compact_words_per_micro = 1; // Prevent division by zero. |
| } |
| |
| int64_t estimated_mark_compact_completion = |
| OS::GetCurrentMonotonicMicros() + |
| UsedInWords() / mark_compact_words_per_micro; |
| return estimated_mark_compact_completion <= deadline; |
| } |
| |
| void PageSpace::TryReleaseReservation() { |
| if (oom_reservation_ == nullptr) return; |
| uword addr = reinterpret_cast<uword>(oom_reservation_); |
| intptr_t size = oom_reservation_->HeapSize(); |
| oom_reservation_ = nullptr; |
| freelists_[OldPage::kData].Free(addr, size); |
| } |
| |
| bool PageSpace::MarkReservation() { |
| if (oom_reservation_ == nullptr) { |
| return false; |
| } |
| UntaggedObject* ptr = reinterpret_cast<UntaggedObject*>(oom_reservation_); |
| if (!ptr->IsMarked()) { |
| ptr->SetMarkBit(); |
| } |
| return true; |
| } |
| |
| void PageSpace::TryReserveForOOM() { |
| if (oom_reservation_ == nullptr) { |
| uword addr = TryAllocate(kOOMReservationSize, OldPage::kData, |
| kForceGrowth /* Don't re-enter GC */); |
| if (addr != 0) { |
| oom_reservation_ = FreeListElement::AsElement(addr, kOOMReservationSize); |
| } |
| } |
| } |
| |
| void PageSpace::VisitRoots(ObjectPointerVisitor* visitor) { |
| if (oom_reservation_ != nullptr) { |
| // FreeListElements are generally held untagged, but ObjectPointerVisitors |
| // expect tagged pointers. |
| ObjectPtr ptr = |
| UntaggedObject::FromAddr(reinterpret_cast<uword>(oom_reservation_)); |
| visitor->VisitPointer(&ptr); |
| oom_reservation_ = |
| reinterpret_cast<FreeListElement*>(UntaggedObject::ToAddr(ptr)); |
| } |
| } |
| |
| void PageSpace::CollectGarbage(bool compact, bool finalize) { |
| ASSERT(GrowthControlState()); |
| |
| if (!finalize) { |
| #if defined(TARGET_ARCH_IA32) |
| return; // Barrier not implemented. |
| #else |
| if (!enable_concurrent_mark()) return; // Disabled. |
| if (FLAG_marker_tasks == 0) return; // Disabled. |
| #endif |
| } |
| |
| Thread* thread = Thread::Current(); |
| const int64_t pre_safe_point = OS::GetCurrentMonotonicMicros(); |
| GcSafepointOperationScope safepoint_scope(thread); |
| |
| const int64_t pre_wait_for_sweepers = OS::GetCurrentMonotonicMicros(); |
| // Wait for pending tasks to complete and then account for the driver task. |
| Phase waited_for; |
| { |
| MonitorLocker locker(tasks_lock()); |
| waited_for = phase(); |
| if (!finalize && |
| (phase() == kMarking || phase() == kAwaitingFinalization)) { |
| // Concurrent mark is already running. |
| return; |
| } |
| |
| while (tasks() > 0) { |
| locker.Wait(); |
| } |
| ASSERT(phase() == kAwaitingFinalization || phase() == kDone); |
| set_tasks(1); |
| } |
| |
| if (FLAG_verbose_gc) { |
| const int64_t wait = |
| OS::GetCurrentMonotonicMicros() - pre_wait_for_sweepers; |
| if (waited_for == kMarking) { |
| THR_Print("Waited %" Pd64 " us for concurrent marking to finish.\n", |
| wait); |
| } else if (waited_for == kSweepingRegular || waited_for == kSweepingLarge) { |
| THR_Print("Waited %" Pd64 " us for concurrent sweeping to finish.\n", |
| wait); |
| } |
| } |
| |
| // Ensure that all threads for this isolate are at a safepoint (either |
| // stopped or in native code). We have guards around Newgen GC and oldgen GC |
| // to ensure that if two threads are racing to collect at the same time the |
| // loser skips collection and goes straight to allocation. |
| { |
| CollectGarbageHelper(compact, finalize, pre_wait_for_sweepers, |
| pre_safe_point); |
| } |
| |
| // Done, reset the task count. |
| { |
| MonitorLocker ml(tasks_lock()); |
| set_tasks(tasks() - 1); |
| ml.NotifyAll(); |
| } |
| } |
| |
| void PageSpace::CollectGarbageHelper(bool compact, |
| bool finalize, |
| int64_t pre_wait_for_sweepers, |
| int64_t pre_safe_point) { |
| Thread* thread = Thread::Current(); |
| ASSERT(thread->IsAtSafepoint()); |
| auto isolate_group = heap_->isolate_group(); |
| ASSERT(isolate_group == IsolateGroup::Current()); |
| |
| const int64_t start = OS::GetCurrentMonotonicMicros(); |
| |
| // Perform various cleanup that relies on no tasks interfering. |
| isolate_group->shared_class_table()->FreeOldTables(); |
| isolate_group->ForEachIsolate( |
| [&](Isolate* isolate) { isolate->field_table()->FreeOldTables(); }, |
| /*at_safepoint=*/true); |
| |
| NoSafepointScope no_safepoints; |
| |
| if (FLAG_print_free_list_before_gc) { |
| for (intptr_t i = 0; i < num_freelists_; i++) { |
| OS::PrintErr("Before GC: Freelist %" Pd "\n", i); |
| freelists_[i].Print(); |
| } |
| } |
| |
| if (FLAG_verify_before_gc) { |
| OS::PrintErr("Verifying before marking..."); |
| heap_->VerifyGC(phase() == kDone ? kForbidMarked : kAllowMarked); |
| OS::PrintErr(" done.\n"); |
| } |
| |
| // Make code pages writable. |
| if (finalize) WriteProtectCode(false); |
| |
| // Save old value before GCMarker visits the weak persistent handles. |
| SpaceUsage usage_before = GetCurrentUsage(); |
| |
| // Mark all reachable old-gen objects. |
| if (marker_ == NULL) { |
| ASSERT(phase() == kDone); |
| marker_ = new GCMarker(isolate_group, heap_); |
| } else { |
| ASSERT(phase() == kAwaitingFinalization); |
| } |
| |
| if (!finalize) { |
| ASSERT(phase() == kDone); |
| marker_->StartConcurrentMark(this); |
| return; |
| } |
| |
| marker_->MarkObjects(this); |
| usage_.used_in_words = marker_->marked_words() + allocated_black_in_words_; |
| allocated_black_in_words_ = 0; |
| mark_words_per_micro_ = marker_->MarkedWordsPerMicro(); |
| delete marker_; |
| marker_ = NULL; |
| |
| int64_t mid1 = OS::GetCurrentMonotonicMicros(); |
| |
| // Abandon the remainder of the bump allocation block. |
| AbandonBumpAllocation(); |
| // Reset the freelists and setup sweeping. |
| for (intptr_t i = 0; i < num_freelists_; i++) { |
| freelists_[i].Reset(); |
| } |
| |
| int64_t mid2 = OS::GetCurrentMonotonicMicros(); |
| int64_t mid3 = 0; |
| |
| { |
| if (FLAG_verify_before_gc) { |
| OS::PrintErr("Verifying before sweeping..."); |
| heap_->VerifyGC(kAllowMarked); |
| OS::PrintErr(" done.\n"); |
| } |
| |
| // Executable pages are always swept immediately to simplify |
| // code protection. |
| |
| TIMELINE_FUNCTION_GC_DURATION(thread, "SweepExecutable"); |
| GCSweeper sweeper; |
| OldPage* prev_page = NULL; |
| OldPage* page = exec_pages_; |
| FreeList* freelist = &freelists_[OldPage::kExecutable]; |
| MutexLocker ml(freelist->mutex()); |
| while (page != NULL) { |
| OldPage* next_page = page->next(); |
| bool page_in_use = sweeper.SweepPage(page, freelist, true /*is_locked*/); |
| if (page_in_use) { |
| prev_page = page; |
| } else { |
| FreePage(page, prev_page); |
| } |
| // Advance to the next page. |
| page = next_page; |
| } |
| |
| mid3 = OS::GetCurrentMonotonicMicros(); |
| } |
| |
| bool has_reservation = MarkReservation(); |
| |
| if (compact) { |
| SweepLarge(); |
| Compact(thread); |
| set_phase(kDone); |
| } else if (FLAG_concurrent_sweep && has_reservation) { |
| ConcurrentSweep(isolate_group); |
| } else { |
| SweepLarge(); |
| Sweep(); |
| set_phase(kDone); |
| } |
| |
| TryReserveForOOM(); |
| |
| // Make code pages read-only. |
| if (finalize) WriteProtectCode(true); |
| |
| int64_t end = OS::GetCurrentMonotonicMicros(); |
| |
| // Record signals for growth control. Include size of external allocations. |
| page_space_controller_.EvaluateGarbageCollection( |
| usage_before, GetCurrentUsage(), start, end); |
| |
| heap_->RecordTime(kConcurrentSweep, pre_safe_point - pre_wait_for_sweepers); |
| heap_->RecordTime(kSafePoint, start - pre_safe_point); |
| heap_->RecordTime(kMarkObjects, mid1 - start); |
| heap_->RecordTime(kResetFreeLists, mid2 - mid1); |
| heap_->RecordTime(kSweepPages, mid3 - mid2); |
| heap_->RecordTime(kSweepLargePages, end - mid3); |
| |
| if (FLAG_print_free_list_after_gc) { |
| for (intptr_t i = 0; i < num_freelists_; i++) { |
| OS::PrintErr("After GC: Freelist %" Pd "\n", i); |
| freelists_[i].Print(); |
| } |
| } |
| |
| UpdateMaxUsed(); |
| if (heap_ != NULL) { |
| heap_->UpdateGlobalMaxUsed(); |
| } |
| } |
| |
| void PageSpace::SweepLarge() { |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "SweepLarge"); |
| |
| GCSweeper sweeper; |
| OldPage* prev_page = nullptr; |
| OldPage* page = large_pages_; |
| while (page != nullptr) { |
| OldPage* next_page = page->next(); |
| const intptr_t words_to_end = sweeper.SweepLargePage(page); |
| if (words_to_end == 0) { |
| FreeLargePage(page, prev_page); |
| } else { |
| TruncateLargePage(page, words_to_end << kWordSizeLog2); |
| prev_page = page; |
| } |
| // Advance to the next page. |
| page = next_page; |
| } |
| } |
| |
| void PageSpace::Sweep() { |
| TIMELINE_FUNCTION_GC_DURATION(Thread::Current(), "Sweep"); |
| |
| GCSweeper sweeper; |
| |
| intptr_t shard = 0; |
| const intptr_t num_shards = Utils::Maximum(FLAG_scavenger_tasks, 1); |
| for (intptr_t i = 0; i < num_shards; i++) { |
| DataFreeList(i)->mutex()->Lock(); |
| } |
| |
| OldPage* prev_page = nullptr; |
| OldPage* page = pages_; |
| while (page != nullptr) { |
| OldPage* next_page = page->next(); |
| ASSERT(page->type() == OldPage::kData); |
| shard = (shard + 1) % num_shards; |
| bool page_in_use = |
| sweeper.SweepPage(page, DataFreeList(shard), true /*is_locked*/); |
| if (page_in_use) { |
| prev_page = page; |
| } else { |
| FreePage(page, prev_page); |
| } |
| // Advance to the next page. |
| page = next_page; |
| } |
| |
| for (intptr_t i = 0; i < num_shards; i++) { |
| DataFreeList(i)->mutex()->Unlock(); |
| } |
| |
| if (FLAG_verify_after_gc) { |
| OS::PrintErr("Verifying after sweeping..."); |
| heap_->VerifyGC(kForbidMarked); |
| OS::PrintErr(" done.\n"); |
| } |
| } |
| |
| void PageSpace::ConcurrentSweep(IsolateGroup* isolate_group) { |
| // Start the concurrent sweeper task now. |
| GCSweeper::SweepConcurrent(isolate_group, pages_, pages_tail_, large_pages_, |
| large_pages_tail_, &freelists_[OldPage::kData]); |
| } |
| |
| void PageSpace::Compact(Thread* thread) { |
| thread->isolate_group()->set_compaction_in_progress(true); |
| GCCompactor compactor(thread, heap_); |
| compactor.Compact(pages_, &freelists_[OldPage::kData], &pages_lock_); |
| thread->isolate_group()->set_compaction_in_progress(false); |
| |
| if (FLAG_verify_after_gc) { |
| OS::PrintErr("Verifying after compacting..."); |
| heap_->VerifyGC(kForbidMarked); |
| OS::PrintErr(" done.\n"); |
| } |
| } |
| |
| uword PageSpace::TryAllocateDataBumpLocked(FreeList* freelist, intptr_t size) { |
| ASSERT(size >= kObjectAlignment); |
| ASSERT(Utils::IsAligned(size, kObjectAlignment)); |
| |
| intptr_t remaining = freelist->end() - freelist->top(); |
| if (UNLIKELY(remaining < size)) { |
| // Checking this first would be logical, but needlessly slow. |
| if (!Heap::IsAllocatableViaFreeLists(size)) { |
| return TryAllocateDataLocked(freelist, size, kForceGrowth); |
| } |
| FreeListElement* block = freelist->TryAllocateLargeLocked(size); |
| if (block == NULL) { |
| // Allocating from a new page (if growth policy allows) will have the |
| // side-effect of populating the freelist with a large block. The next |
| // bump allocation request will have a chance to consume that block. |
| // TODO(koda): Could take freelist lock just once instead of twice. |
| return TryAllocateInFreshPage(size, freelist, OldPage::kData, |
| kForceGrowth, true /* is_locked*/); |
| } |
| intptr_t block_size = block->HeapSize(); |
| if (remaining > 0) { |
| freelist->FreeLocked(freelist->top(), remaining); |
| } |
| freelist->set_top(reinterpret_cast<uword>(block)); |
| freelist->set_end(freelist->top() + block_size); |
| remaining = block_size; |
| } |
| ASSERT(remaining >= size); |
| uword result = freelist->top(); |
| freelist->set_top(result + size); |
| |
| freelist->AddUnaccountedSize(size); |
| |
| // Note: Remaining block is unwalkable until MakeIterable is called. |
| #ifdef DEBUG |
| if (freelist->top() < freelist->end()) { |
| // Fail fast if we try to walk the remaining block. |
| COMPILE_ASSERT(kIllegalCid == 0); |
| *reinterpret_cast<uword*>(freelist->top()) = 0; |
| } |
| #endif // DEBUG |
| return result; |
| } |
| |
| uword PageSpace::TryAllocatePromoLockedSlow(FreeList* freelist, intptr_t size) { |
| uword result = freelist->TryAllocateSmallLocked(size); |
| if (result != 0) { |
| freelist->AddUnaccountedSize(size); |
| return result; |
| } |
| return TryAllocateDataBumpLocked(freelist, size); |
| } |
| |
| ObjectPtr PageSpace::AllocateSnapshot(intptr_t size) { |
| ASSERT(Utils::IsAligned(size, kObjectAlignment)); |
| uword address = TryAllocateDataBumpLocked(size); |
| if (address == 0) { |
| OUT_OF_MEMORY(); |
| } |
| return UntaggedObject::FromAddr(address); |
| } |
| |
| void PageSpace::SetupImagePage(void* pointer, uword size, bool is_executable) { |
| // Setup a OldPage so precompiled Instructions can be traversed. |
| // Instructions are contiguous at [pointer, pointer + size). OldPage |
| // expects to find objects at [memory->start() + ObjectStartOffset, |
| // memory->end()). |
| uword offset = OldPage::ObjectStartOffset(); |
| pointer = reinterpret_cast<void*>(reinterpret_cast<uword>(pointer) - offset); |
| ASSERT(Utils::IsAligned(pointer, kObjectAlignment)); |
| size += offset; |
| |
| VirtualMemory* memory = VirtualMemory::ForImagePage(pointer, size); |
| ASSERT(memory != NULL); |
| OldPage* page = reinterpret_cast<OldPage*>(malloc(sizeof(OldPage))); |
| page->memory_ = memory; |
| page->next_ = NULL; |
| page->object_end_ = memory->end(); |
| page->used_in_bytes_ = page->object_end_ - page->object_start(); |
| page->forwarding_page_ = NULL; |
| page->card_table_ = NULL; |
| if (is_executable) { |
| page->type_ = OldPage::kExecutable; |
| } else { |
| page->type_ = OldPage::kData; |
| } |
| |
| MutexLocker ml(&pages_lock_); |
| page->next_ = image_pages_; |
| image_pages_ = page; |
| } |
| |
| bool PageSpace::IsObjectFromImagePages(dart::ObjectPtr object) { |
| uword object_addr = UntaggedObject::ToAddr(object); |
| OldPage* image_page = image_pages_; |
| while (image_page != nullptr) { |
| if (image_page->Contains(object_addr)) { |
| return true; |
| } |
| image_page = image_page->next(); |
| } |
| return false; |
| } |
| |
| PageSpaceController::PageSpaceController(Heap* heap, |
| int heap_growth_ratio, |
| int heap_growth_max, |
| int garbage_collection_time_ratio) |
| : heap_(heap), |
| is_enabled_(false), |
| heap_growth_ratio_(heap_growth_ratio), |
| desired_utilization_((100.0 - heap_growth_ratio) / 100.0), |
| heap_growth_max_(heap_growth_max), |
| garbage_collection_time_ratio_(garbage_collection_time_ratio), |
| idle_gc_threshold_in_words_(0) { |
| const intptr_t growth_in_pages = heap_growth_max / 2; |
| RecordUpdate(last_usage_, last_usage_, growth_in_pages, "initial"); |
| } |
| |
| PageSpaceController::~PageSpaceController() {} |
| |
| bool PageSpaceController::ReachedHardThreshold(SpaceUsage after) const { |
| if (!is_enabled_) { |
| return false; |
| } |
| if (heap_growth_ratio_ == 100) { |
| return false; |
| } |
| return after.CombinedUsedInWords() > hard_gc_threshold_in_words_; |
| } |
| |
| bool PageSpaceController::ReachedSoftThreshold(SpaceUsage after) const { |
| if (!is_enabled_) { |
| return false; |
| } |
| if (heap_growth_ratio_ == 100) { |
| return false; |
| } |
| return after.CombinedUsedInWords() > soft_gc_threshold_in_words_; |
| } |
| |
| bool PageSpaceController::ReachedIdleThreshold(SpaceUsage current) const { |
| if (!is_enabled_) { |
| return false; |
| } |
| if (heap_growth_ratio_ == 100) { |
| return false; |
| } |
| return current.CombinedUsedInWords() > idle_gc_threshold_in_words_; |
| } |
| |
| void PageSpaceController::EvaluateGarbageCollection(SpaceUsage before, |
| SpaceUsage after, |
| int64_t start, |
| int64_t end) { |
| ASSERT(end >= start); |
| history_.AddGarbageCollectionTime(start, end); |
| const int gc_time_fraction = history_.GarbageCollectionTimeFraction(); |
| heap_->RecordData(PageSpace::kGCTimeFraction, gc_time_fraction); |
| |
| // Assume garbage increases linearly with allocation: |
| // G = kA, and estimate k from the previous cycle. |
| const intptr_t allocated_since_previous_gc = |
| before.CombinedUsedInWords() - last_usage_.CombinedUsedInWords(); |
| intptr_t grow_heap; |
| if (allocated_since_previous_gc > 0) { |
| intptr_t garbage = |
| before.CombinedUsedInWords() - after.CombinedUsedInWords(); |
| // Garbage may be negative if when the OOM reservation is refilled. |
| garbage = Utils::Maximum(static_cast<intptr_t>(0), garbage); |
| // It makes no sense to expect that each kb allocated will cause more than |
| // one kb of garbage, so we clamp k at 1.0. |
| const double k = Utils::Minimum( |
| 1.0, garbage / static_cast<double>(allocated_since_previous_gc)); |
| |
| const int garbage_ratio = static_cast<int>(k * 100); |
| heap_->RecordData(PageSpace::kGarbageRatio, garbage_ratio); |
| |
| // Define GC to be 'worthwhile' iff at least fraction t of heap is garbage. |
| double t = 1.0 - desired_utilization_; |
| // If we spend too much time in GC, strive for even more free space. |
| if (gc_time_fraction > garbage_collection_time_ratio_) { |
| t += (gc_time_fraction - garbage_collection_time_ratio_) / 100.0; |
| } |
| |
| // Number of pages we can allocate and still be within the desired growth |
| // ratio. |
| const intptr_t grow_pages = |
| (static_cast<intptr_t>(after.CombinedUsedInWords() / |
| desired_utilization_) - |
| (after.CombinedUsedInWords())) / |
| kOldPageSizeInWords; |
| if (garbage_ratio == 0) { |
| // No garbage in the previous cycle so it would be hard to compute a |
| // grow_heap size based on estimated garbage so we use growth ratio |
| // heuristics instead. |
| grow_heap = |
| Utils::Maximum(static_cast<intptr_t>(heap_growth_max_), grow_pages); |
| } else { |
| // Find minimum 'grow_heap' such that after increasing capacity by |
| // 'grow_heap' pages and filling them, we expect a GC to be worthwhile. |
| intptr_t max = heap_growth_max_; |
| intptr_t min = 0; |
| intptr_t local_grow_heap = 0; |
| while (min < max) { |
| local_grow_heap = (max + min) / 2; |
| const intptr_t limit = after.CombinedUsedInWords() + |
| (local_grow_heap * kOldPageSizeInWords); |
| const intptr_t allocated_before_next_gc = |
| limit - (after.CombinedUsedInWords()); |
| const double estimated_garbage = k * allocated_before_next_gc; |
| if (t <= estimated_garbage / limit) { |
| max = local_grow_heap - 1; |
| } else { |
| min = local_grow_heap + 1; |
| } |
| } |
| local_grow_heap = (max + min) / 2; |
| grow_heap = local_grow_heap; |
| ASSERT(grow_heap >= 0); |
| // If we are going to grow by heap_grow_max_ then ensure that we |
| // will be growing the heap at least by the growth ratio heuristics. |
| if (grow_heap >= heap_growth_max_) { |
| grow_heap = Utils::Maximum(grow_pages, grow_heap); |
| } |
| } |
| } else { |
| heap_->RecordData(PageSpace::kGarbageRatio, 100); |
| grow_heap = 0; |
| } |
| heap_->RecordData(PageSpace::kPageGrowth, grow_heap); |
| last_usage_ = after; |
| |
| intptr_t max_capacity_in_words = heap_->old_space()->max_capacity_in_words_; |
| if (max_capacity_in_words != 0) { |
| ASSERT(grow_heap >= 0); |
| // Fraction of asymptote used. |
| double f = static_cast<double>(after.CombinedUsedInWords() + |
| (kOldPageSizeInWords * grow_heap)) / |
| static_cast<double>(max_capacity_in_words); |
| ASSERT(f >= 0.0); |
| // Increase weight at the high end. |
| f = f * f; |
| // Fraction of asymptote available. |
| f = 1.0 - f; |
| ASSERT(f <= 1.0); |
| // Discount growth more the closer we get to the desired asymptote. |
| grow_heap = static_cast<intptr_t>(grow_heap * f); |
| // Minimum growth step after reaching the asymptote. |
| intptr_t min_step = (2 * MB) / kOldPageSize; |
| grow_heap = Utils::Maximum(min_step, grow_heap); |
| } |
| |
| RecordUpdate(before, after, grow_heap, "gc"); |
| } |
| |
| void PageSpaceController::EvaluateAfterLoading(SpaceUsage after) { |
| // Number of pages we can allocate and still be within the desired growth |
| // ratio. |
| intptr_t growth_in_pages; |
| if (desired_utilization_ == 0.0) { |
| growth_in_pages = heap_growth_max_; |
| } else { |
| growth_in_pages = (static_cast<intptr_t>(after.CombinedUsedInWords() / |
| desired_utilization_) - |
| (after.CombinedUsedInWords())) / |
| kOldPageSizeInWords; |
| } |
| |
| // Apply growth cap. |
| growth_in_pages = |
| Utils::Minimum(static_cast<intptr_t>(heap_growth_max_), growth_in_pages); |
| |
| RecordUpdate(after, after, growth_in_pages, "loaded"); |
| } |
| |
| void PageSpaceController::RecordUpdate(SpaceUsage before, |
| SpaceUsage after, |
| intptr_t growth_in_pages, |
| const char* reason) { |
| // Save final threshold compared before growing. |
| hard_gc_threshold_in_words_ = |
| after.CombinedUsedInWords() + (kOldPageSizeInWords * growth_in_pages); |
| |
| // Start concurrent marking when old-space has less than half of new-space |
| // available or less than 5% available. |
| #if defined(TARGET_ARCH_IA32) |
| const intptr_t headroom = 0; // No concurrent marking. |
| #else |
| // Note that heap_ can be null in some unit tests. |
| const intptr_t new_space = |
| heap_ == nullptr ? 0 : heap_->new_space()->CapacityInWords(); |
| const intptr_t headroom = |
| Utils::Maximum(new_space / 2, hard_gc_threshold_in_words_ / 20); |
| #endif |
| soft_gc_threshold_in_words_ = hard_gc_threshold_in_words_ - headroom; |
| |
| // Set a tight idle threshold. |
| idle_gc_threshold_in_words_ = |
| after.CombinedUsedInWords() + (2 * kOldPageSizeInWords); |
| |
| #if defined(SUPPORT_TIMELINE) |
| Thread* thread = Thread::Current(); |
| if (thread != nullptr) { |
| TIMELINE_FUNCTION_GC_DURATION(thread, "UpdateGrowthLimit"); |
| tbes.SetNumArguments(6); |
| tbes.CopyArgument(0, "Reason", reason); |
| tbes.FormatArgument(1, "Before.CombinedUsed (kB)", "%" Pd "", |
| RoundWordsToKB(before.CombinedUsedInWords())); |
| tbes.FormatArgument(2, "After.CombinedUsed (kB)", "%" Pd "", |
| RoundWordsToKB(after.CombinedUsedInWords())); |
| tbes.FormatArgument(3, "Hard Threshold (kB)", "%" Pd "", |
| RoundWordsToKB(hard_gc_threshold_in_words_)); |
| tbes.FormatArgument(4, "Soft Threshold (kB)", "%" Pd "", |
| RoundWordsToKB(soft_gc_threshold_in_words_)); |
| tbes.FormatArgument(5, "Idle Threshold (kB)", "%" Pd "", |
| RoundWordsToKB(idle_gc_threshold_in_words_)); |
| } |
| #endif |
| |
| if (FLAG_log_growth) { |
| THR_Print("%s: threshold=%" Pd "kB, idle_threshold=%" Pd "kB, reason=%s\n", |
| heap_->isolate_group()->source()->name, |
| hard_gc_threshold_in_words_ / KBInWords, |
| idle_gc_threshold_in_words_ / KBInWords, reason); |
| } |
| } |
| |
| void PageSpaceController::HintFreed(intptr_t size) { |
| intptr_t size_in_words = size << kWordSizeLog2; |
| if (size_in_words > idle_gc_threshold_in_words_) { |
| idle_gc_threshold_in_words_ = 0; |
| } else { |
| idle_gc_threshold_in_words_ -= size_in_words; |
| } |
| |
| // TODO(rmacnak): Hasten the soft threshold at some discount? |
| } |
| |
| void PageSpaceGarbageCollectionHistory::AddGarbageCollectionTime(int64_t start, |
| int64_t end) { |
| Entry entry; |
| entry.start = start; |
| entry.end = end; |
| history_.Add(entry); |
| } |
| |
| int PageSpaceGarbageCollectionHistory::GarbageCollectionTimeFraction() { |
| int64_t gc_time = 0; |
| int64_t total_time = 0; |
| for (int i = 0; i < history_.Size() - 1; i++) { |
| Entry current = history_.Get(i); |
| Entry previous = history_.Get(i + 1); |
| gc_time += current.end - current.start; |
| total_time += current.end - previous.end; |
| } |
| if (total_time == 0) { |
| return 0; |
| } else { |
| ASSERT(total_time >= gc_time); |
| int result = static_cast<int>( |
| (static_cast<double>(gc_time) / static_cast<double>(total_time)) * 100); |
| return result; |
| } |
| } |
| |
| } // namespace dart |