summaryrefslogtreecommitdiffstats
path: root/runtime/gc/heap.cc
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/gc/heap.cc')
-rw-r--r--runtime/gc/heap.cc909
1 files changed, 567 insertions, 342 deletions
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index de3ab0eb9d..69ca6202f9 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -30,11 +30,14 @@
#include "gc/accounting/atomic_stack.h"
#include "gc/accounting/card_table-inl.h"
#include "gc/accounting/heap_bitmap-inl.h"
+#include "gc/accounting/mod_union_table.h"
#include "gc/accounting/mod_union_table-inl.h"
#include "gc/accounting/space_bitmap-inl.h"
#include "gc/collector/mark_sweep-inl.h"
#include "gc/collector/partial_mark_sweep.h"
+#include "gc/collector/semi_space.h"
#include "gc/collector/sticky_mark_sweep.h"
+#include "gc/space/bump_pointer_space.h"
#include "gc/space/dlmalloc_space-inl.h"
#include "gc/space/image_space.h"
#include "gc/space/large_object_space.h"
@@ -70,9 +73,8 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
bool concurrent_gc, size_t parallel_gc_threads, size_t conc_gc_threads,
bool low_memory_mode, size_t long_pause_log_threshold, size_t long_gc_log_threshold,
bool ignore_max_footprint)
- : alloc_space_(NULL),
- card_table_(NULL),
- concurrent_gc_(concurrent_gc),
+ : non_moving_space_(NULL),
+ concurrent_gc_(!kMovingCollector && concurrent_gc),
parallel_gc_threads_(parallel_gc_threads),
conc_gc_threads_(conc_gc_threads),
low_memory_mode_(low_memory_mode),
@@ -92,6 +94,7 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
max_allowed_footprint_(initial_size),
native_footprint_gc_watermark_(initial_size),
native_footprint_limit_(2 * initial_size),
+ native_need_to_run_finalization_(false),
activity_thread_class_(NULL),
application_thread_class_(NULL),
activity_thread_(NULL),
@@ -122,7 +125,9 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
* searching.
*/
max_allocation_stack_size_(kGCALotMode ? kGcAlotInterval
- : (kDesiredHeapVerification > kNoHeapVerification) ? KB : MB),
+ : (kDesiredHeapVerification > kVerifyAllFast) ? KB : MB),
+ bump_pointer_space_(nullptr),
+ temp_space_(nullptr),
reference_referent_offset_(0),
reference_queue_offset_(0),
reference_queueNext_offset_(0),
@@ -134,6 +139,7 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
total_wait_time_(0),
total_allocation_time_(0),
verify_object_mode_(kHeapVerificationNotPermitted),
+ gc_disable_count_(0),
running_on_valgrind_(RUNNING_ON_VALGRIND) {
if (VLOG_IS_ON(heap) || VLOG_IS_ON(startup)) {
LOG(INFO) << "Heap() entering";
@@ -147,7 +153,7 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
if (!image_file_name.empty()) {
space::ImageSpace* image_space = space::ImageSpace::Create(image_file_name.c_str());
CHECK(image_space != NULL) << "Failed to create space for " << image_file_name;
- AddContinuousSpace(image_space);
+ AddSpace(image_space);
// Oat files referenced by image files immediately follow them in memory, ensure alloc space
// isn't going to get in the middle
byte* oat_file_end_addr = image_space->GetImageHeader().GetOatFileEnd();
@@ -159,13 +165,28 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
}
}
- alloc_space_ = space::DlMallocSpace::Create(Runtime::Current()->IsZygote() ? "zygote space" : "alloc space",
- initial_size,
- growth_limit, capacity,
- requested_alloc_space_begin);
- CHECK(alloc_space_ != NULL) << "Failed to create alloc space";
- alloc_space_->SetFootprintLimit(alloc_space_->Capacity());
- AddContinuousSpace(alloc_space_);
+ const char* name = Runtime::Current()->IsZygote() ? "zygote space" : "alloc space";
+ non_moving_space_ = space::DlMallocSpace::Create(name, initial_size, growth_limit, capacity,
+ requested_alloc_space_begin);
+
+ if (kMovingCollector) {
+ // TODO: Place bump-pointer spaces somewhere to minimize size of card table.
+ // TODO: Having 3+ spaces as big as the large heap size can cause virtual memory fragmentation
+ // issues.
+ const size_t bump_pointer_space_size = std::min(non_moving_space_->Capacity(), 128 * MB);
+ bump_pointer_space_ = space::BumpPointerSpace::Create("Bump pointer space",
+ bump_pointer_space_size, nullptr);
+ CHECK(bump_pointer_space_ != nullptr) << "Failed to create bump pointer space";
+ AddSpace(bump_pointer_space_);
+ temp_space_ = space::BumpPointerSpace::Create("Bump pointer space 2", bump_pointer_space_size,
+ nullptr);
+ CHECK(temp_space_ != nullptr) << "Failed to create bump pointer space";
+ AddSpace(temp_space_);
+ }
+
+ CHECK(non_moving_space_ != NULL) << "Failed to create non-moving space";
+ non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
+ AddSpace(non_moving_space_);
// Allocate the large object space.
const bool kUseFreeListSpaceForLOS = false;
@@ -175,22 +196,23 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
large_object_space_ = space::LargeObjectMapSpace::Create("large object space");
}
CHECK(large_object_space_ != NULL) << "Failed to create large object space";
- AddDiscontinuousSpace(large_object_space_);
+ AddSpace(large_object_space_);
// Compute heap capacity. Continuous spaces are sorted in order of Begin().
+ CHECK(!continuous_spaces_.empty());
+ // Relies on the spaces being sorted.
byte* heap_begin = continuous_spaces_.front()->Begin();
- size_t heap_capacity = continuous_spaces_.back()->End() - continuous_spaces_.front()->Begin();
- if (continuous_spaces_.back()->IsDlMallocSpace()) {
- heap_capacity += continuous_spaces_.back()->AsDlMallocSpace()->NonGrowthLimitCapacity();
- }
+ byte* heap_end = continuous_spaces_.back()->Limit();
+ size_t heap_capacity = heap_end - heap_begin;
// Allocate the card table.
card_table_.reset(accounting::CardTable::Create(heap_begin, heap_capacity));
CHECK(card_table_.get() != NULL) << "Failed to create card table";
+ // Card cache for now since it makes it easier for us to update the references to the copying
+ // spaces.
accounting::ModUnionTable* mod_union_table =
- new accounting::ModUnionTableToZygoteAllocspace("Image mod-union table", this,
- GetImageSpace());
+ new accounting::ModUnionTableCardCache("Image mod-union table", this, GetImageSpace());
CHECK(mod_union_table != nullptr) << "Failed to create image mod-union table";
AddModUnionTable(mod_union_table);
@@ -223,19 +245,23 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
if (ignore_max_footprint_) {
SetIdealFootprint(std::numeric_limits<size_t>::max());
- concurrent_start_bytes_ = max_allowed_footprint_;
+ concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
}
+ CHECK_NE(max_allowed_footprint_, 0U);
// Create our garbage collectors.
- for (size_t i = 0; i < 2; ++i) {
- const bool concurrent = i != 0;
- mark_sweep_collectors_.push_back(new collector::MarkSweep(this, concurrent));
- mark_sweep_collectors_.push_back(new collector::PartialMarkSweep(this, concurrent));
- mark_sweep_collectors_.push_back(new collector::StickyMarkSweep(this, concurrent));
+ if (!kMovingCollector) {
+ for (size_t i = 0; i < 2; ++i) {
+ const bool concurrent = i != 0;
+ garbage_collectors_.push_back(new collector::MarkSweep(this, concurrent));
+ garbage_collectors_.push_back(new collector::PartialMarkSweep(this, concurrent));
+ garbage_collectors_.push_back(new collector::StickyMarkSweep(this, concurrent));
+ }
+ } else {
+ semi_space_collector_ = new collector::SemiSpace(this);
+ garbage_collectors_.push_back(semi_space_collector_);
}
- CHECK_NE(max_allowed_footprint_, 0U);
-
if (running_on_valgrind_) {
Runtime::Current()->GetInstrumentation()->InstrumentQuickAllocEntryPoints();
}
@@ -245,6 +271,41 @@ Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max
}
}
+bool Heap::IsCompilingBoot() const {
+ for (const auto& space : continuous_spaces_) {
+ if (space->IsImageSpace()) {
+ return false;
+ } else if (space->IsZygoteSpace()) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Heap::HasImageSpace() const {
+ for (const auto& space : continuous_spaces_) {
+ if (space->IsImageSpace()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void Heap::IncrementDisableGC(Thread* self) {
+ // Need to do this holding the lock to prevent races where the GC is about to run / running when
+ // we attempt to disable it.
+ ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);
+ MutexLock mu(self, *gc_complete_lock_);
+ WaitForGcToCompleteLocked(self);
+ ++gc_disable_count_;
+}
+
+void Heap::DecrementDisableGC(Thread* self) {
+ MutexLock mu(self, *gc_complete_lock_);
+ CHECK_GE(gc_disable_count_, 0U);
+ --gc_disable_count_;
+}
+
void Heap::CreateThreadPool() {
const size_t num_threads = std::max(parallel_gc_threads_, conc_gc_threads_);
if (num_threads != 0) {
@@ -252,12 +313,49 @@ void Heap::CreateThreadPool() {
}
}
+void Heap::VisitObjects(ObjectVisitorCallback callback, void* arg) {
+ // Visit objects in bump pointer space.
+ Thread* self = Thread::Current();
+ // TODO: Use reference block.
+ std::vector<SirtRef<mirror::Object>*> saved_refs;
+ if (bump_pointer_space_ != nullptr) {
+ // Need to put all these in sirts since the callback may trigger a GC. TODO: Use a better data
+ // structure.
+ mirror::Object* obj = reinterpret_cast<mirror::Object*>(bump_pointer_space_->Begin());
+ const mirror::Object* end = reinterpret_cast<const mirror::Object*>(
+ bump_pointer_space_->End());
+ while (obj < end) {
+ saved_refs.push_back(new SirtRef<mirror::Object>(self, obj));
+ obj = space::BumpPointerSpace::GetNextObject(obj);
+ }
+ }
+ // TODO: Switch to standard begin and end to use ranged a based loop.
+ for (mirror::Object** it = allocation_stack_->Begin(), **end = allocation_stack_->End();
+ it < end; ++it) {
+ mirror::Object* obj = *it;
+ // Objects in the allocation stack might be in a movable space.
+ saved_refs.push_back(new SirtRef<mirror::Object>(self, obj));
+ }
+ GetLiveBitmap()->Walk(callback, arg);
+ for (const auto& ref : saved_refs) {
+ callback(ref->get(), arg);
+ }
+ // Need to free the sirts in reverse order they were allocated.
+ for (size_t i = saved_refs.size(); i != 0; --i) {
+ delete saved_refs[i - 1];
+ }
+}
+
+void Heap::MarkAllocStackAsLive(accounting::ObjectStack* stack) {
+ MarkAllocStack(non_moving_space_->GetLiveBitmap(), large_object_space_->GetLiveObjects(), stack);
+}
+
void Heap::DeleteThreadPool() {
thread_pool_.reset(nullptr);
}
static bool ReadStaticInt(JNIEnvExt* env, jclass clz, const char* name, int* out_value) {
- CHECK(out_value != NULL);
+ DCHECK(out_value != NULL);
jfieldID field = env->GetStaticFieldID(clz, name, "I");
if (field == NULL) {
env->ExceptionClear();
@@ -374,62 +472,71 @@ void Heap::ListenForProcessStateChange() {
}
}
-void Heap::AddContinuousSpace(space::ContinuousSpace* space) {
- WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
+void Heap::AddSpace(space::Space* space) {
DCHECK(space != NULL);
- DCHECK(space->GetLiveBitmap() != NULL);
- live_bitmap_->AddContinuousSpaceBitmap(space->GetLiveBitmap());
- DCHECK(space->GetMarkBitmap() != NULL);
- mark_bitmap_->AddContinuousSpaceBitmap(space->GetMarkBitmap());
- continuous_spaces_.push_back(space);
- if (space->IsDlMallocSpace() && !space->IsLargeObjectSpace()) {
- alloc_space_ = space->AsDlMallocSpace();
- }
-
- // Ensure that spaces remain sorted in increasing order of start address (required for CMS finger)
- std::sort(continuous_spaces_.begin(), continuous_spaces_.end(),
- [](const space::ContinuousSpace* a, const space::ContinuousSpace* b) {
- return a->Begin() < b->Begin();
- });
-
- // Ensure that ImageSpaces < ZygoteSpaces < AllocSpaces so that we can do address based checks to
- // avoid redundant marking.
- bool seen_zygote = false, seen_alloc = false;
- for (const auto& space : continuous_spaces_) {
- if (space->IsImageSpace()) {
- DCHECK(!seen_zygote);
- DCHECK(!seen_alloc);
- } else if (space->IsZygoteSpace()) {
- DCHECK(!seen_alloc);
- seen_zygote = true;
- } else if (space->IsDlMallocSpace()) {
- seen_alloc = true;
+ WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
+ if (space->IsContinuousSpace()) {
+ DCHECK(!space->IsDiscontinuousSpace());
+ space::ContinuousSpace* continuous_space = space->AsContinuousSpace();
+ // Continuous spaces don't necessarily have bitmaps.
+ accounting::SpaceBitmap* live_bitmap = continuous_space->GetLiveBitmap();
+ accounting::SpaceBitmap* mark_bitmap = continuous_space->GetMarkBitmap();
+ if (live_bitmap != nullptr) {
+ DCHECK(mark_bitmap != nullptr);
+ live_bitmap_->AddContinuousSpaceBitmap(live_bitmap);
+ mark_bitmap_->AddContinuousSpaceBitmap(mark_bitmap);
+ }
+
+ continuous_spaces_.push_back(continuous_space);
+ if (continuous_space->IsDlMallocSpace()) {
+ non_moving_space_ = continuous_space->AsDlMallocSpace();
+ }
+
+ // Ensure that spaces remain sorted in increasing order of start address.
+ std::sort(continuous_spaces_.begin(), continuous_spaces_.end(),
+ [](const space::ContinuousSpace* a, const space::ContinuousSpace* b) {
+ return a->Begin() < b->Begin();
+ });
+ // Ensure that ImageSpaces < ZygoteSpaces < AllocSpaces so that we can do address based checks to
+ // avoid redundant marking.
+ bool seen_zygote = false, seen_alloc = false;
+ for (const auto& space : continuous_spaces_) {
+ if (space->IsImageSpace()) {
+ CHECK(!seen_zygote);
+ CHECK(!seen_alloc);
+ } else if (space->IsZygoteSpace()) {
+ CHECK(!seen_alloc);
+ seen_zygote = true;
+ } else if (space->IsDlMallocSpace()) {
+ seen_alloc = true;
+ }
}
+ } else {
+ DCHECK(space->IsDiscontinuousSpace());
+ space::DiscontinuousSpace* discontinuous_space = space->AsDiscontinuousSpace();
+ DCHECK(discontinuous_space->GetLiveObjects() != nullptr);
+ live_bitmap_->AddDiscontinuousObjectSet(discontinuous_space->GetLiveObjects());
+ DCHECK(discontinuous_space->GetMarkObjects() != nullptr);
+ mark_bitmap_->AddDiscontinuousObjectSet(discontinuous_space->GetMarkObjects());
+ discontinuous_spaces_.push_back(discontinuous_space);
+ }
+ if (space->IsAllocSpace()) {
+ alloc_spaces_.push_back(space->AsAllocSpace());
}
}
void Heap::RegisterGCAllocation(size_t bytes) {
- if (this != NULL) {
+ if (this != nullptr) {
gc_memory_overhead_.fetch_add(bytes);
}
}
void Heap::RegisterGCDeAllocation(size_t bytes) {
- if (this != NULL) {
+ if (this != nullptr) {
gc_memory_overhead_.fetch_sub(bytes);
}
}
-void Heap::AddDiscontinuousSpace(space::DiscontinuousSpace* space) {
- WriterMutexLock mu(Thread::Current(), *Locks::heap_bitmap_lock_);
- DCHECK(space != NULL);
- DCHECK(space->GetLiveObjects() != NULL);
- live_bitmap_->AddDiscontinuousObjectSet(space->GetLiveObjects());
- DCHECK(space->GetMarkObjects() != NULL);
- mark_bitmap_->AddDiscontinuousObjectSet(space->GetMarkObjects());
- discontinuous_spaces_.push_back(space);
-}
-
void Heap::DumpGcPerformanceInfo(std::ostream& os) {
// Dump cumulative timings.
os << "Dumping cumulative Gc timings\n";
@@ -437,7 +544,7 @@ void Heap::DumpGcPerformanceInfo(std::ostream& os) {
// Dump cumulative loggers for each GC type.
uint64_t total_paused_time = 0;
- for (const auto& collector : mark_sweep_collectors_) {
+ for (const auto& collector : garbage_collectors_) {
CumulativeLogger& logger = collector->GetCumulativeTimings();
if (logger.GetTotalNs() != 0) {
os << Dumpable<CumulativeLogger>(logger);
@@ -480,17 +587,14 @@ void Heap::DumpGcPerformanceInfo(std::ostream& os) {
}
Heap::~Heap() {
+ VLOG(heap) << "Starting ~Heap()";
if (kDumpGcPerformanceOnShutdown) {
DumpGcPerformanceInfo(LOG(INFO));
}
-
- STLDeleteElements(&mark_sweep_collectors_);
-
- // If we don't reset then the mark stack complains in it's destructor.
+ STLDeleteElements(&garbage_collectors_);
+ // If we don't reset then the mark stack complains in its destructor.
allocation_stack_->Reset();
live_stack_->Reset();
-
- VLOG(heap) << "~Heap()";
STLDeleteValues(&mod_union_tables_);
STLDeleteElements(&continuous_spaces_);
STLDeleteElements(&discontinuous_spaces_);
@@ -499,6 +603,7 @@ Heap::~Heap() {
delete weak_ref_queue_lock_;
delete finalizer_ref_queue_lock_;
delete phantom_ref_queue_lock_;
+ VLOG(heap) << "Finished ~Heap()";
}
space::ContinuousSpace* Heap::FindContinuousSpaceFromObject(const mirror::Object* obj,
@@ -579,7 +684,7 @@ inline bool Heap::TryAllocLargeObjectInstrumented(Thread* self, mirror::Class* c
mirror::Object* obj = AllocateInstrumented(self, large_object_space_, byte_count, bytes_allocated);
// Make sure that our large object didn't get placed anywhere within the space interval or else
// it breaks the immune range.
- DCHECK(obj == NULL ||
+ DCHECK(obj == nullptr ||
reinterpret_cast<byte*>(obj) < continuous_spaces_.front()->Begin() ||
reinterpret_cast<byte*>(obj) >= continuous_spaces_.back()->End());
*obj_ptr = obj;
@@ -587,16 +692,59 @@ inline bool Heap::TryAllocLargeObjectInstrumented(Thread* self, mirror::Class* c
return large_object_allocation;
}
-mirror::Object* Heap::AllocObjectInstrumented(Thread* self, mirror::Class* c, size_t byte_count) {
- DebugCheckPreconditionsForAllobObject(c, byte_count);
+mirror::Object* Heap::AllocMovableObjectInstrumented(Thread* self, mirror::Class* c,
+ size_t byte_count) {
+ DebugCheckPreconditionsForAllocObject(c, byte_count);
+ mirror::Object* obj;
+ AllocationTimer alloc_timer(this, &obj);
+ byte_count = RoundUp(byte_count, 8);
+ if (UNLIKELY(IsOutOfMemoryOnAllocation(byte_count, false))) {
+ CollectGarbageInternal(collector::kGcTypeFull, kGcCauseForAlloc, false);
+ if (UNLIKELY(IsOutOfMemoryOnAllocation(byte_count, true))) {
+ CollectGarbageInternal(collector::kGcTypeFull, kGcCauseForAlloc, true);
+ }
+ }
+ obj = bump_pointer_space_->AllocNonvirtual(byte_count);
+ if (LIKELY(obj != NULL)) {
+ obj->SetClass(c);
+ DCHECK(!obj->IsClass());
+ // Record allocation after since we want to use the atomic add for the atomic fence to guard
+ // the SetClass since we do not want the class to appear NULL in another thread.
+ num_bytes_allocated_.fetch_add(byte_count);
+ if (Runtime::Current()->HasStatsEnabled()) {
+ RuntimeStats* thread_stats = Thread::Current()->GetStats();
+ ++thread_stats->allocated_objects;
+ thread_stats->allocated_bytes += byte_count;
+ RuntimeStats* global_stats = Runtime::Current()->GetStats();
+ ++global_stats->allocated_objects;
+ global_stats->allocated_bytes += byte_count;
+ }
+ if (Dbg::IsAllocTrackingEnabled()) {
+ Dbg::RecordAllocation(c, byte_count);
+ }
+ if (kDesiredHeapVerification > kNoHeapVerification) {
+ VerifyObject(obj);
+ }
+ } else {
+ ThrowOutOfMemoryError(self, byte_count, false);
+ }
+ if (kIsDebugBuild) {
+ self->VerifyStack();
+ }
+ return obj;
+}
+
+mirror::Object* Heap::AllocNonMovableObjectInstrumented(Thread* self, mirror::Class* c,
+ size_t byte_count) {
+ DebugCheckPreconditionsForAllocObject(c, byte_count);
mirror::Object* obj;
size_t bytes_allocated;
AllocationTimer alloc_timer(this, &obj);
- bool large_object_allocation = TryAllocLargeObjectInstrumented(self, c, byte_count,
- &obj, &bytes_allocated);
+ bool large_object_allocation = TryAllocLargeObjectInstrumented(self, c, byte_count, &obj,
+ &bytes_allocated);
if (LIKELY(!large_object_allocation)) {
// Non-large object allocation.
- obj = AllocateInstrumented(self, alloc_space_, byte_count, &bytes_allocated);
+ obj = AllocateInstrumented(self, non_moving_space_, byte_count, &bytes_allocated);
// Ensure that we did not allocate into a zygote space.
DCHECK(obj == NULL || !have_zygote_space_ || !FindSpaceFromObject(obj, false)->IsZygoteSpace());
}
@@ -612,28 +760,66 @@ mirror::Object* Heap::AllocObjectInstrumented(Thread* self, mirror::Class* c, si
if (kDesiredHeapVerification > kNoHeapVerification) {
VerifyObject(obj);
}
- return obj;
+ } else {
+ ThrowOutOfMemoryError(self, byte_count, large_object_allocation);
}
- ThrowOutOfMemoryError(self, byte_count, large_object_allocation);
- return NULL;
+ if (kIsDebugBuild) {
+ self->VerifyStack();
+ }
+ return obj;
}
-bool Heap::IsHeapAddress(const mirror::Object* obj) {
- // Note: we deliberately don't take the lock here, and mustn't test anything that would
- // require taking the lock.
- if (obj == NULL) {
+void Heap::Trim() {
+ uint64_t start_ns = NanoTime();
+ // Trim the managed spaces.
+ uint64_t total_alloc_space_allocated = 0;
+ uint64_t total_alloc_space_size = 0;
+ uint64_t managed_reclaimed = 0;
+ for (const auto& space : continuous_spaces_) {
+ if (space->IsDlMallocSpace() && !space->IsZygoteSpace()) {
+ gc::space::DlMallocSpace* alloc_space = space->AsDlMallocSpace();
+ total_alloc_space_size += alloc_space->Size();
+ managed_reclaimed += alloc_space->Trim();
+ }
+ }
+ total_alloc_space_allocated = GetBytesAllocated() - large_object_space_->GetBytesAllocated() -
+ bump_pointer_space_->GetBytesAllocated();
+ const float managed_utilization = static_cast<float>(total_alloc_space_allocated) /
+ static_cast<float>(total_alloc_space_size);
+ uint64_t gc_heap_end_ns = NanoTime();
+ // Trim the native heap.
+ dlmalloc_trim(0);
+ size_t native_reclaimed = 0;
+ dlmalloc_inspect_all(DlmallocMadviseCallback, &native_reclaimed);
+ uint64_t end_ns = NanoTime();
+ VLOG(heap) << "Heap trim of managed (duration=" << PrettyDuration(gc_heap_end_ns - start_ns)
+ << ", advised=" << PrettySize(managed_reclaimed) << ") and native (duration="
+ << PrettyDuration(end_ns - gc_heap_end_ns) << ", advised=" << PrettySize(native_reclaimed)
+ << ") heaps. Managed heap utilization of " << static_cast<int>(100 * managed_utilization)
+ << "%.";
+}
+
+bool Heap::IsValidObjectAddress(const mirror::Object* obj) const {
+ // Note: we deliberately don't take the lock here, and mustn't test anything that would require
+ // taking the lock.
+ if (obj == nullptr) {
return true;
}
- if (UNLIKELY(!IsAligned<kObjectAlignment>(obj))) {
- return false;
+ return IsAligned<kObjectAlignment>(obj) && IsHeapAddress(obj);
+}
+
+bool Heap::IsHeapAddress(const mirror::Object* obj) const {
+ if (kMovingCollector && bump_pointer_space_->HasAddress(obj)) {
+ return true;
}
- return FindSpaceFromObject(obj, true) != NULL;
+ // TODO: This probably doesn't work for large objects.
+ return FindSpaceFromObject(obj, true) != nullptr;
}
bool Heap::IsLiveObjectLocked(const mirror::Object* obj, bool search_allocation_stack,
bool search_live_stack, bool sorted) {
// Locks::heap_bitmap_lock_->AssertReaderHeld(Thread::Current());
- if (obj == NULL || UNLIKELY(!IsAligned<kObjectAlignment>(obj))) {
+ if (obj == nullptr || UNLIKELY(!IsAligned<kObjectAlignment>(obj))) {
return false;
}
space::ContinuousSpace* c_space = FindContinuousSpaceFromObject(obj, true);
@@ -642,6 +828,8 @@ bool Heap::IsLiveObjectLocked(const mirror::Object* obj, bool search_allocation_
if (c_space->GetLiveBitmap()->Test(obj)) {
return true;
}
+ } else if (bump_pointer_space_->Contains(obj) || temp_space_->Contains(obj)) {
+ return true;
} else {
d_space = FindDiscontinuousSpaceFromObject(obj, true);
if (d_space != NULL) {
@@ -699,16 +887,20 @@ void Heap::VerifyObjectImpl(const mirror::Object* obj) {
VerifyObjectBody(obj);
}
-void Heap::DumpSpaces() {
+void Heap::DumpSpaces(std::ostream& stream) {
for (const auto& space : continuous_spaces_) {
accounting::SpaceBitmap* live_bitmap = space->GetLiveBitmap();
accounting::SpaceBitmap* mark_bitmap = space->GetMarkBitmap();
- LOG(INFO) << space << " " << *space << "\n"
- << live_bitmap << " " << *live_bitmap << "\n"
- << mark_bitmap << " " << *mark_bitmap;
+ stream << space << " " << *space << "\n";
+ if (live_bitmap != nullptr) {
+ stream << live_bitmap << " " << *live_bitmap << "\n";
+ }
+ if (mark_bitmap != nullptr) {
+ stream << mark_bitmap << " " << *mark_bitmap << "\n";
+ }
}
for (const auto& space : discontinuous_spaces_) {
- LOG(INFO) << space << " " << *space << "\n";
+ stream << space << " " << *space << "\n";
}
}
@@ -735,7 +927,7 @@ void Heap::VerifyObjectBody(const mirror::Object* obj) {
const mirror::Class* c_c_c = *reinterpret_cast<mirror::Class* const *>(raw_addr);
CHECK_EQ(c_c, c_c_c);
- if (verify_object_mode_ != kVerifyAllFast) {
+ if (verify_object_mode_ > kVerifyAllFast) {
// TODO: the bitmap tests below are racy if VerifyObjectBody is called without the
// heap_bitmap_lock_.
if (!IsLiveObjectLocked(obj)) {
@@ -811,7 +1003,7 @@ inline mirror::Object* Heap::TryToAllocateInstrumented(Thread* self, space::Allo
inline mirror::Object* Heap::TryToAllocateInstrumented(Thread* self, space::DlMallocSpace* space, size_t alloc_size,
bool grow, size_t* bytes_allocated) {
if (UNLIKELY(IsOutOfMemoryOnAllocation(alloc_size, grow))) {
- return NULL;
+ return nullptr;
}
if (LIKELY(!running_on_valgrind_)) {
return space->AllocNonvirtual(self, alloc_size, bytes_allocated);
@@ -841,7 +1033,7 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* sp
// The allocation failed. If the GC is running, block until it completes, and then retry the
// allocation.
- collector::GcType last_gc = WaitForConcurrentGcToComplete(self);
+ collector::GcType last_gc = WaitForGcToComplete(self);
if (last_gc != collector::kGcTypeNone) {
// A GC was in progress and we blocked, retry allocation now that memory has been freed.
ptr = TryToAllocateInstrumented(self, space, alloc_size, false, bytes_allocated);
@@ -857,9 +1049,10 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* sp
collector::GcType gc_type = static_cast<collector::GcType>(i);
switch (gc_type) {
case collector::kGcTypeSticky: {
- const size_t alloc_space_size = alloc_space_->Size();
+ const size_t alloc_space_size = non_moving_space_->Size();
run_gc = alloc_space_size > min_alloc_space_size_for_sticky_gc_ &&
- alloc_space_->Capacity() - alloc_space_size >= min_remaining_space_for_sticky_gc_;
+ non_moving_space_->Capacity() - alloc_space_size >=
+ min_remaining_space_for_sticky_gc_;
break;
}
case collector::kGcTypePartial:
@@ -869,7 +1062,7 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* sp
run_gc = true;
break;
default:
- break;
+ LOG(FATAL) << "Invalid GC type";
}
if (run_gc) {
@@ -897,11 +1090,11 @@ mirror::Object* Heap::AllocateInternalWithGc(Thread* self, space::AllocSpace* sp
// or the requested size is really big. Do another GC, collecting SoftReferences this time. The
// VM spec requires that all SoftReferences have been collected and cleared before throwing OOME.
- // OLD-TODO: wait for the finalizers from the previous GC to finish
+ // TODO: Run finalization, but this can cause more allocations to occur.
VLOG(gc) << "Forcing collection of SoftReferences for " << PrettySize(alloc_size)
<< " allocation";
- // We don't need a WaitForConcurrentGcToComplete here either.
+ // We don't need a WaitForGcToComplete here either.
CollectGarbageInternal(collector::kGcTypeFull, kGcCauseForAlloc, true);
return TryToAllocateInstrumented(self, space, alloc_size, true, bytes_allocated);
}
@@ -914,51 +1107,24 @@ void Heap::SetTargetHeapUtilization(float target) {
size_t Heap::GetObjectsAllocated() const {
size_t total = 0;
- typedef std::vector<space::ContinuousSpace*>::const_iterator It;
- for (It it = continuous_spaces_.begin(), end = continuous_spaces_.end(); it != end; ++it) {
- space::ContinuousSpace* space = *it;
- if (space->IsDlMallocSpace()) {
- total += space->AsDlMallocSpace()->GetObjectsAllocated();
- }
- }
- typedef std::vector<space::DiscontinuousSpace*>::const_iterator It2;
- for (It2 it = discontinuous_spaces_.begin(), end = discontinuous_spaces_.end(); it != end; ++it) {
- space::DiscontinuousSpace* space = *it;
- total += space->AsLargeObjectSpace()->GetObjectsAllocated();
+ for (space::AllocSpace* space : alloc_spaces_) {
+ total += space->GetObjectsAllocated();
}
return total;
}
size_t Heap::GetObjectsAllocatedEver() const {
size_t total = 0;
- typedef std::vector<space::ContinuousSpace*>::const_iterator It;
- for (It it = continuous_spaces_.begin(), end = continuous_spaces_.end(); it != end; ++it) {
- space::ContinuousSpace* space = *it;
- if (space->IsDlMallocSpace()) {
- total += space->AsDlMallocSpace()->GetTotalObjectsAllocated();
- }
- }
- typedef std::vector<space::DiscontinuousSpace*>::const_iterator It2;
- for (It2 it = discontinuous_spaces_.begin(), end = discontinuous_spaces_.end(); it != end; ++it) {
- space::DiscontinuousSpace* space = *it;
- total += space->AsLargeObjectSpace()->GetTotalObjectsAllocated();
+ for (space::AllocSpace* space : alloc_spaces_) {
+ total += space->GetTotalObjectsAllocated();
}
return total;
}
size_t Heap::GetBytesAllocatedEver() const {
size_t total = 0;
- typedef std::vector<space::ContinuousSpace*>::const_iterator It;
- for (It it = continuous_spaces_.begin(), end = continuous_spaces_.end(); it != end; ++it) {
- space::ContinuousSpace* space = *it;
- if (space->IsDlMallocSpace()) {
- total += space->AsDlMallocSpace()->GetTotalBytesAllocated();
- }
- }
- typedef std::vector<space::DiscontinuousSpace*>::const_iterator It2;
- for (It2 it = discontinuous_spaces_.begin(), end = discontinuous_spaces_.end(); it != end; ++it) {
- space::DiscontinuousSpace* space = *it;
- total += space->AsLargeObjectSpace()->GetTotalBytesAllocated();
+ for (space::AllocSpace* space : alloc_spaces_) {
+ total += space->GetTotalBytesAllocated();
}
return total;
}
@@ -1056,8 +1222,8 @@ class ReferringObjectsFinder {
// For bitmap Visit.
// TODO: Fix lock analysis to not use NO_THREAD_SAFETY_ANALYSIS, requires support for
// annotalysis on visitors.
- void operator()(mirror::Object* obj) const NO_THREAD_SAFETY_ANALYSIS {
- collector::MarkSweep::VisitObjectReferences(obj, *this, true);
+ void operator()(const mirror::Object* o) const NO_THREAD_SAFETY_ANALYSIS {
+ collector::MarkSweep::VisitObjectReferences(const_cast<mirror::Object*>(o), *this, true);
}
// For MarkSweep::VisitObjectReferences.
@@ -1093,56 +1259,69 @@ void Heap::GetReferringObjects(mirror::Object* o, int32_t max_count,
void Heap::CollectGarbage(bool clear_soft_references) {
// Even if we waited for a GC we still need to do another GC since weaks allocated during the
// last GC will not have necessarily been cleared.
- Thread* self = Thread::Current();
- WaitForConcurrentGcToComplete(self);
CollectGarbageInternal(collector::kGcTypeFull, kGcCauseExplicit, clear_soft_references);
}
void Heap::PreZygoteFork() {
static Mutex zygote_creation_lock_("zygote creation lock", kZygoteCreationLock);
- // Do this before acquiring the zygote creation lock so that we don't get lock order violations.
- CollectGarbage(false);
Thread* self = Thread::Current();
MutexLock mu(self, zygote_creation_lock_);
-
// Try to see if we have any Zygote spaces.
if (have_zygote_space_) {
return;
}
-
- VLOG(heap) << "Starting PreZygoteFork with alloc space size " << PrettySize(alloc_space_->Size());
-
- {
- // Flush the alloc stack.
- WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
- FlushAllocStack();
+ VLOG(heap) << "Starting PreZygoteFork";
+ // Do this before acquiring the zygote creation lock so that we don't get lock order violations.
+ CollectGarbageInternal(collector::kGcTypeFull, kGcCauseBackground, false);
+ // Trim the pages at the end of the non moving space.
+ non_moving_space_->Trim();
+ non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
+ // Create a new bump pointer space which we will compact into.
+ if (semi_space_collector_ != nullptr) {
+ space::BumpPointerSpace target_space("zygote bump space", non_moving_space_->End(),
+ non_moving_space_->Limit());
+ // Compact the bump pointer space to a new zygote bump pointer space.
+ temp_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
+ Compact(&target_space, bump_pointer_space_);
+ CHECK_EQ(temp_space_->GetBytesAllocated(), 0U);
+ total_objects_freed_ever_ += semi_space_collector_->GetFreedObjects();
+ total_bytes_freed_ever_ += semi_space_collector_->GetFreedBytes();
+ // Update the end and write out image.
+ non_moving_space_->SetEnd(target_space.End());
+ non_moving_space_->SetLimit(target_space.Limit());
+ accounting::SpaceBitmap* bitmap = non_moving_space_->GetLiveBitmap();
+ // Record the allocations in the bitmap.
+ VLOG(heap) << "Recording zygote allocations";
+ mirror::Object* obj = reinterpret_cast<mirror::Object*>(target_space.Begin());
+ const mirror::Object* end = reinterpret_cast<const mirror::Object*>(target_space.End());
+ while (obj < end) {
+ bitmap->Set(obj);
+ obj = space::BumpPointerSpace::GetNextObject(obj);
+ }
}
-
- // Turns the current alloc space into a Zygote space and obtain the new alloc space composed
- // of the remaining available heap memory.
- space::DlMallocSpace* zygote_space = alloc_space_;
- alloc_space_ = zygote_space->CreateZygoteSpace("alloc space");
- alloc_space_->SetFootprintLimit(alloc_space_->Capacity());
-
+ // Turn the current alloc space into a zygote space and obtain the new alloc space composed of
+ // the remaining available heap memory.
+ space::DlMallocSpace* zygote_space = non_moving_space_;
+ non_moving_space_ = zygote_space->CreateZygoteSpace("alloc space");
+ non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
// Change the GC retention policy of the zygote space to only collect when full.
zygote_space->SetGcRetentionPolicy(space::kGcRetentionPolicyFullCollect);
- AddContinuousSpace(alloc_space_);
+ AddSpace(non_moving_space_);
have_zygote_space_ = true;
-
+ zygote_space->InvalidateMSpace();
// Create the zygote space mod union table.
accounting::ModUnionTable* mod_union_table =
new accounting::ModUnionTableCardCache("zygote space mod-union table", this, zygote_space);
CHECK(mod_union_table != nullptr) << "Failed to create zygote space mod-union table";
AddModUnionTable(mod_union_table);
-
// Reset the cumulative loggers since we now have a few additional timing phases.
- for (const auto& collector : mark_sweep_collectors_) {
+ for (const auto& collector : garbage_collectors_) {
collector->ResetCumulativeStatistics();
}
}
void Heap::FlushAllocStack() {
- MarkAllocStack(alloc_space_->GetLiveBitmap(), large_object_space_->GetLiveObjects(),
+ MarkAllocStack(non_moving_space_->GetLiveBitmap(), large_object_space_->GetLiveObjects(),
allocation_stack_.get());
allocation_stack_->Reset();
}
@@ -1161,86 +1340,111 @@ void Heap::MarkAllocStack(accounting::SpaceBitmap* bitmap, accounting::SpaceSetM
}
}
+const char* PrettyCause(GcCause cause) {
+ switch (cause) {
+ case kGcCauseForAlloc: return "Alloc";
+ case kGcCauseBackground: return "Background";
+ case kGcCauseExplicit: return "Explicit";
+ default:
+ LOG(FATAL) << "Unreachable";
+ }
+ return "";
+}
+
+void Heap::SwapSemiSpaces() {
+ // Swap the spaces so we allocate into the space which we just evacuated.
+ std::swap(bump_pointer_space_, temp_space_);
+}
-const char* gc_cause_and_type_strings[3][4] = {
- {"", "GC Alloc Sticky", "GC Alloc Partial", "GC Alloc Full"},
- {"", "GC Background Sticky", "GC Background Partial", "GC Background Full"},
- {"", "GC Explicit Sticky", "GC Explicit Partial", "GC Explicit Full"}};
+void Heap::Compact(space::ContinuousMemMapAllocSpace* target_space,
+ space::ContinuousMemMapAllocSpace* source_space) {
+ CHECK(kMovingCollector);
+ CHECK_NE(target_space, source_space) << "In-place compaction unsupported";
+ if (target_space != source_space) {
+ semi_space_collector_->SetFromSpace(source_space);
+ semi_space_collector_->SetToSpace(target_space);
+ semi_space_collector_->Run(false);
+ }
+}
collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, GcCause gc_cause,
bool clear_soft_references) {
Thread* self = Thread::Current();
-
+ Runtime* runtime = Runtime::Current();
ScopedThreadStateChange tsc(self, kWaitingPerformingGc);
Locks::mutator_lock_->AssertNotHeld(self);
-
if (self->IsHandlingStackOverflow()) {
LOG(WARNING) << "Performing GC on a thread that is handling a stack overflow.";
}
-
- // Ensure there is only one GC at a time.
- bool start_collect = false;
- while (!start_collect) {
- {
- MutexLock mu(self, *gc_complete_lock_);
- if (!is_gc_running_) {
- is_gc_running_ = true;
- start_collect = true;
- }
- }
- if (!start_collect) {
- // TODO: timinglog this.
- WaitForConcurrentGcToComplete(self);
-
- // TODO: if another thread beat this one to do the GC, perhaps we should just return here?
- // Not doing at the moment to ensure soft references are cleared.
+ {
+ gc_complete_lock_->AssertNotHeld(self);
+ MutexLock mu(self, *gc_complete_lock_);
+ // Ensure there is only one GC at a time.
+ WaitForGcToCompleteLocked(self);
+ // TODO: if another thread beat this one to do the GC, perhaps we should just return here?
+ // Not doing at the moment to ensure soft references are cleared.
+ // GC can be disabled if someone has a used GetPrimitiveArrayCritical.
+ if (gc_disable_count_ != 0) {
+ LOG(WARNING) << "Skipping GC due to disable count " << gc_disable_count_;
+ return collector::kGcTypeNone;
}
+ is_gc_running_ = true;
}
- gc_complete_lock_->AssertNotHeld(self);
- if (gc_cause == kGcCauseForAlloc && Runtime::Current()->HasStatsEnabled()) {
- ++Runtime::Current()->GetStats()->gc_for_alloc_count;
- ++Thread::Current()->GetStats()->gc_for_alloc_count;
+ if (gc_cause == kGcCauseForAlloc && runtime->HasStatsEnabled()) {
+ ++runtime->GetStats()->gc_for_alloc_count;
+ ++self->GetStats()->gc_for_alloc_count;
}
uint64_t gc_start_time_ns = NanoTime();
uint64_t gc_start_size = GetBytesAllocated();
// Approximate allocation rate in bytes / second.
- if (UNLIKELY(gc_start_time_ns == last_gc_time_ns_)) {
- LOG(WARNING) << "Timers are broken (gc_start_time == last_gc_time_).";
- }
uint64_t ms_delta = NsToMs(gc_start_time_ns - last_gc_time_ns_);
- if (ms_delta != 0) {
+ // Back to back GCs can cause 0 ms of wait time in between GC invocations.
+ if (LIKELY(ms_delta != 0)) {
allocation_rate_ = ((gc_start_size - last_gc_size_) * 1000) / ms_delta;
VLOG(heap) << "Allocation rate: " << PrettySize(allocation_rate_) << "/s";
}
if (gc_type == collector::kGcTypeSticky &&
- alloc_space_->Size() < min_alloc_space_size_for_sticky_gc_) {
+ non_moving_space_->Size() < min_alloc_space_size_for_sticky_gc_) {
gc_type = collector::kGcTypePartial;
}
DCHECK_LT(gc_type, collector::kGcTypeMax);
DCHECK_NE(gc_type, collector::kGcTypeNone);
- DCHECK_LE(gc_cause, kGcCauseExplicit);
- ATRACE_BEGIN(gc_cause_and_type_strings[gc_cause][gc_type]);
-
- collector::MarkSweep* collector = NULL;
- for (const auto& cur_collector : mark_sweep_collectors_) {
- if (cur_collector->IsConcurrent() == concurrent_gc_ && cur_collector->GetGcType() == gc_type) {
+ collector::GarbageCollector* collector = nullptr;
+ if (kMovingCollector) {
+ gc_type = semi_space_collector_->GetGcType();
+ CHECK_EQ(temp_space_->GetObjectsAllocated(), 0U);
+ semi_space_collector_->SetFromSpace(bump_pointer_space_);
+ semi_space_collector_->SetToSpace(temp_space_);
+ mprotect(temp_space_->Begin(), temp_space_->Capacity(), PROT_READ | PROT_WRITE);
+ }
+ for (const auto& cur_collector : garbage_collectors_) {
+ if (cur_collector->IsConcurrent() == concurrent_gc_ &&
+ cur_collector->GetGcType() == gc_type) {
collector = cur_collector;
break;
}
}
+ if (kMovingCollector) {
+ gc_type = collector::kGcTypeFull;
+ }
CHECK(collector != NULL)
<< "Could not find garbage collector with concurrent=" << concurrent_gc_
<< " and type=" << gc_type;
- collector->clear_soft_references_ = clear_soft_references;
- collector->Run();
+ ATRACE_BEGIN(StringPrintf("%s %s GC", PrettyCause(gc_cause), collector->GetName()).c_str());
+
+ collector->Run(clear_soft_references);
total_objects_freed_ever_ += collector->GetFreedObjects();
total_bytes_freed_ever_ += collector->GetFreedBytes();
+
+ // Grow the heap so that we know when to perform the next GC.
+ GrowForUtilization(gc_type, collector->GetDurationNs());
+
if (care_about_pause_times_) {
const size_t duration = collector->GetDurationNs();
std::vector<uint64_t> pauses = collector->GetPauseTimes();
@@ -1252,7 +1456,6 @@ collector::GcType Heap::CollectGarbageInternal(collector::GcType gc_type, GcCaus
was_slow = was_slow || pause > long_pause_log_threshold_;
}
}
-
if (was_slow) {
const size_t percent_free = GetPercentFree();
const size_t current_heap_size = GetBytesAllocated();
@@ -1327,7 +1530,6 @@ class VerifyReferenceVisitor {
accounting::CardTable* card_table = heap_->GetCardTable();
accounting::ObjectStack* alloc_stack = heap_->allocation_stack_.get();
accounting::ObjectStack* live_stack = heap_->live_stack_.get();
-
if (!failed_) {
// Print message on only on first failure to prevent spam.
LOG(ERROR) << "!!!!!!!!!!!!!!Heap corruption detected!!!!!!!!!!!!!!!!!!!";
@@ -1337,7 +1539,7 @@ class VerifyReferenceVisitor {
byte* card_addr = card_table->CardFromAddr(obj);
LOG(ERROR) << "Object " << obj << " references dead object " << ref << " at offset "
<< offset << "\n card value = " << static_cast<int>(*card_addr);
- if (heap_->IsHeapAddress(obj->GetClass())) {
+ if (heap_->IsValidObjectAddress(obj->GetClass())) {
LOG(ERROR) << "Obj type " << PrettyTypeOf(obj);
} else {
LOG(ERROR) << "Object " << obj << " class(" << obj->GetClass() << ") not a heap address";
@@ -1345,7 +1547,7 @@ class VerifyReferenceVisitor {
// Attmept to find the class inside of the recently freed objects.
space::ContinuousSpace* ref_space = heap_->FindContinuousSpaceFromObject(ref, true);
- if (ref_space->IsDlMallocSpace()) {
+ if (ref_space != nullptr && ref_space->IsDlMallocSpace()) {
space::DlMallocSpace* space = ref_space->AsDlMallocSpace();
mirror::Class* ref_class = space->FindRecentFreedObject(ref);
if (ref_class != nullptr) {
@@ -1356,7 +1558,7 @@ class VerifyReferenceVisitor {
}
}
- if (ref->GetClass() != nullptr && heap_->IsHeapAddress(ref->GetClass()) &&
+ if (ref->GetClass() != nullptr && heap_->IsValidObjectAddress(ref->GetClass()) &&
ref->GetClass()->IsClass()) {
LOG(ERROR) << "Ref type " << PrettyTypeOf(ref);
} else {
@@ -1427,17 +1629,25 @@ class VerifyObjectVisitor {
public:
explicit VerifyObjectVisitor(Heap* heap) : heap_(heap), failed_(false) {}
- void operator()(const mirror::Object* obj) const
+ void operator()(mirror::Object* obj) const
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) {
// Note: we are verifying the references in obj but not obj itself, this is because obj must
// be live or else how did we find it in the live bitmap?
VerifyReferenceVisitor visitor(heap_);
// The class doesn't count as a reference but we should verify it anyways.
- visitor(obj, obj->GetClass(), MemberOffset(0), false);
- collector::MarkSweep::VisitObjectReferences(const_cast<mirror::Object*>(obj), visitor, true);
+ collector::MarkSweep::VisitObjectReferences(obj, visitor, true);
+ if (obj->GetClass()->IsReferenceClass()) {
+ visitor(obj, heap_->GetReferenceReferent(obj), MemberOffset(0), false);
+ }
failed_ = failed_ || visitor.Failed();
}
+ static void VisitCallback(mirror::Object* obj, void* arg)
+ SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) {
+ VerifyObjectVisitor* visitor = reinterpret_cast<VerifyObjectVisitor*>(arg);
+ visitor->operator()(obj);
+ }
+
bool Failed() const {
return failed_;
}
@@ -1453,18 +1663,15 @@ bool Heap::VerifyHeapReferences() {
// Lets sort our allocation stacks so that we can efficiently binary search them.
allocation_stack_->Sort();
live_stack_->Sort();
- // Perform the verification.
VerifyObjectVisitor visitor(this);
- Runtime::Current()->VisitRoots(VerifyReferenceVisitor::VerifyRoots, &visitor, false, false);
- GetLiveBitmap()->Visit(visitor);
// Verify objects in the allocation stack since these will be objects which were:
// 1. Allocated prior to the GC (pre GC verification).
// 2. Allocated during the GC (pre sweep GC verification).
- for (mirror::Object** it = allocation_stack_->Begin(); it != allocation_stack_->End(); ++it) {
- visitor(*it);
- }
// We don't want to verify the objects in the live stack since they themselves may be
// pointing to dead objects if they are not reachable.
+ VisitObjects(VerifyObjectVisitor::VisitCallback, &visitor);
+ // Verify the roots:
+ Runtime::Current()->VisitRoots(VerifyReferenceVisitor::VerifyRoots, &visitor, false, false);
if (visitor.Failed()) {
// Dump mod-union tables.
for (const auto& table_pair : mod_union_tables_) {
@@ -1557,7 +1764,7 @@ class VerifyLiveStackReferences {
void operator()(mirror::Object* obj) const
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_, Locks::heap_bitmap_lock_) {
VerifyReferenceCardVisitor visitor(heap_, const_cast<bool*>(&failed_));
- collector::MarkSweep::VisitObjectReferences(obj, visitor, true);
+ collector::MarkSweep::VisitObjectReferences(const_cast<mirror::Object*>(obj), visitor, true);
}
bool Failed() const {
@@ -1610,10 +1817,14 @@ void Heap::ProcessCards(base::TimingLogger& timings) {
"ImageModUnionClearCards";
base::TimingLogger::ScopedSplit split(name, &timings);
table->ClearCards();
- } else {
+ } else if (space->GetType() != space::kSpaceTypeBumpPointerSpace) {
base::TimingLogger::ScopedSplit split("AllocSpaceClearCards", &timings);
// No mod union table for the AllocSpace. Age the cards so that the GC knows that these cards
// were dirty before the GC started.
+ // TODO: Don't need to use atomic.
+ // The races are we either end up with: Aged card, unaged card. Since we have the checkpoint
+ // roots and then we scan / update mod union tables after. We will always scan either card.//
+ // If we end up with the non aged card, we scan it it in the pause.
card_table_->ModifyCardsAtomic(space->Begin(), space->End(), AgeCardVisitor(), VoidFunctor());
}
}
@@ -1692,36 +1903,27 @@ void Heap::PostGcVerification(collector::GarbageCollector* gc) {
}
}
-collector::GcType Heap::WaitForConcurrentGcToComplete(Thread* self) {
+collector::GcType Heap::WaitForGcToComplete(Thread* self) {
+ ScopedThreadStateChange tsc(self, kWaitingForGcToComplete);
+ MutexLock mu(self, *gc_complete_lock_);
+ return WaitForGcToCompleteLocked(self);
+}
+
+collector::GcType Heap::WaitForGcToCompleteLocked(Thread* self) {
collector::GcType last_gc_type = collector::kGcTypeNone;
- if (concurrent_gc_) {
- ATRACE_BEGIN("GC: Wait For Concurrent");
- bool do_wait;
- uint64_t wait_start = NanoTime();
- {
- // Check if GC is running holding gc_complete_lock_.
- MutexLock mu(self, *gc_complete_lock_);
- do_wait = is_gc_running_;
- }
- if (do_wait) {
- uint64_t wait_time;
- // We must wait, change thread state then sleep on gc_complete_cond_;
- ScopedThreadStateChange tsc(Thread::Current(), kWaitingForGcToComplete);
- {
- MutexLock mu(self, *gc_complete_lock_);
- while (is_gc_running_) {
- gc_complete_cond_->Wait(self);
- }
- last_gc_type = last_gc_type_;
- wait_time = NanoTime() - wait_start;
- total_wait_time_ += wait_time;
- }
- if (wait_time > long_pause_log_threshold_) {
- LOG(INFO) << "WaitForConcurrentGcToComplete blocked for " << PrettyDuration(wait_time);
- }
- }
+ uint64_t wait_start = NanoTime();
+ while (is_gc_running_) {
+ ATRACE_BEGIN("GC: Wait For Completion");
+ // We must wait, change thread state then sleep on gc_complete_cond_;
+ gc_complete_cond_->Wait(self);
+ last_gc_type = last_gc_type_;
ATRACE_END();
}
+ uint64_t wait_time = NanoTime() - wait_start;
+ total_wait_time_ += wait_time;
+ if (wait_time > long_pause_log_threshold_) {
+ LOG(INFO) << "WaitForGcToComplete blocked for " << PrettyDuration(wait_time);
+ }
return last_gc_type;
}
@@ -1744,6 +1946,23 @@ void Heap::SetIdealFootprint(size_t max_allowed_footprint) {
max_allowed_footprint_ = max_allowed_footprint;
}
+bool Heap::IsMovableObject(const mirror::Object* obj) const {
+ if (kMovingCollector) {
+ DCHECK(!IsInTempSpace(obj));
+ if (bump_pointer_space_->HasAddress(obj)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Heap::IsInTempSpace(const mirror::Object* obj) const {
+ if (temp_space_->HasAddress(obj) && !temp_space_->Contains(obj)) {
+ return true;
+ }
+ return false;
+}
+
void Heap::UpdateMaxNativeFootprint() {
size_t native_size = native_bytes_allocated_;
// TODO: Tune the native heap utilization to be a value other than the java heap utilization.
@@ -1773,6 +1992,7 @@ void Heap::GrowForUtilization(collector::GcType gc_type, uint64_t gc_duration) {
} else if (target_size < bytes_allocated + min_free_) {
target_size = bytes_allocated + min_free_;
}
+ native_need_to_run_finalization_ = true;
next_gc_type_ = collector::kGcTypeSticky;
} else {
// Based on how close the current heap size is to the target size, decide
@@ -1796,7 +2016,6 @@ void Heap::GrowForUtilization(collector::GcType gc_type, uint64_t gc_duration) {
if (concurrent_gc_) {
// Calculate when to perform the next ConcurrentGC.
-
// Calculate the estimated GC duration.
double gc_duration_seconds = NsToMs(gc_duration) / 1000.0;
// Estimate how many remaining bytes we will have when we need to start the next GC.
@@ -1817,13 +2036,11 @@ void Heap::GrowForUtilization(collector::GcType gc_type, uint64_t gc_duration) {
DCHECK_LE(max_allowed_footprint_, growth_limit_);
}
}
-
- UpdateMaxNativeFootprint();
}
void Heap::ClearGrowthLimit() {
growth_limit_ = capacity_;
- alloc_space_->ClearGrowthLimit();
+ non_moving_space_->ClearGrowthLimit();
}
void Heap::SetReferenceOffsets(MemberOffset reference_referent_offset,
@@ -1843,6 +2060,12 @@ void Heap::SetReferenceOffsets(MemberOffset reference_referent_offset,
CHECK_NE(finalizer_reference_zombie_offset_.Uint32Value(), 0U);
}
+void Heap::SetReferenceReferent(mirror::Object* reference, mirror::Object* referent) {
+ DCHECK(reference != NULL);
+ DCHECK_NE(reference_referent_offset_.Uint32Value(), 0U);
+ reference->SetFieldObject(reference_referent_offset_, referent, true);
+}
+
mirror::Object* Heap::GetReferenceReferent(mirror::Object* reference) {
DCHECK(reference != NULL);
DCHECK_NE(reference_referent_offset_.Uint32Value(), 0U);
@@ -1852,7 +2075,7 @@ mirror::Object* Heap::GetReferenceReferent(mirror::Object* reference) {
void Heap::ClearReferenceReferent(mirror::Object* reference) {
DCHECK(reference != NULL);
DCHECK_NE(reference_referent_offset_.Uint32Value(), 0U);
- reference->SetFieldObject(reference_referent_offset_, NULL, true);
+ reference->SetFieldObject(reference_referent_offset_, nullptr, true);
}
// Returns true if the reference object has not yet been enqueued.
@@ -1924,19 +2147,41 @@ void Heap::AddFinalizerReference(Thread* self, mirror::Object* object) {
arg_array.GetArray(), arg_array.GetNumBytes(), &result, 'V');
}
+void Heap::PrintReferenceQueue(std::ostream& os, mirror::Object** queue) {
+ os << "Refernece queue " << queue << "\n";
+ if (queue != nullptr) {
+ mirror::Object* list = *queue;
+ if (list != nullptr) {
+ mirror::Object* cur = list;
+ do {
+ mirror::Object* pending_next =
+ cur->GetFieldObject<mirror::Object*>(reference_pendingNext_offset_, false);
+ os << "PendingNext=" << pending_next;
+ if (cur->GetClass()->IsFinalizerReferenceClass()) {
+ os << " Zombie=" <<
+ cur->GetFieldObject<mirror::Object*>(finalizer_reference_zombie_offset_, false);
+ }
+ os << "\n";
+ cur = pending_next;
+ } while (cur != list);
+ }
+ }
+}
+
void Heap::EnqueueClearedReferences(mirror::Object** cleared) {
- DCHECK(cleared != NULL);
- if (*cleared != NULL) {
+ DCHECK(cleared != nullptr);
+ mirror::Object* list = *cleared;
+ if (list != nullptr) {
// When a runtime isn't started there are no reference queues to care about so ignore.
if (LIKELY(Runtime::Current()->IsStarted())) {
ScopedObjectAccess soa(Thread::Current());
JValue result;
ArgArray arg_array(NULL, 0);
- arg_array.Append(reinterpret_cast<uint32_t>(*cleared));
+ arg_array.Append(reinterpret_cast<uint32_t>(list));
soa.DecodeMethod(WellKnownClasses::java_lang_ref_ReferenceQueue_add)->Invoke(soa.Self(),
arg_array.GetArray(), arg_array.GetNumBytes(), &result, 'V');
}
- *cleared = NULL;
+ *cleared = nullptr;
}
}
@@ -1944,42 +2189,27 @@ void Heap::RequestConcurrentGC(Thread* self) {
// Make sure that we can do a concurrent GC.
Runtime* runtime = Runtime::Current();
DCHECK(concurrent_gc_);
- if (runtime == NULL || !runtime->IsFinishedStarting() ||
- !runtime->IsConcurrentGcEnabled()) {
- return;
- }
- {
- MutexLock mu(self, *Locks::runtime_shutdown_lock_);
- if (runtime->IsShuttingDown()) {
- return;
- }
- }
- if (self->IsHandlingStackOverflow()) {
+ if (runtime == NULL || !runtime->IsFinishedStarting() || runtime->IsShuttingDown(self) ||
+ self->IsHandlingStackOverflow()) {
return;
}
-
// We already have a request pending, no reason to start more until we update
// concurrent_start_bytes_.
concurrent_start_bytes_ = std::numeric_limits<size_t>::max();
-
JNIEnv* env = self->GetJniEnv();
- DCHECK(WellKnownClasses::java_lang_Daemons != NULL);
- DCHECK(WellKnownClasses::java_lang_Daemons_requestGC != NULL);
+ DCHECK(WellKnownClasses::java_lang_Daemons != nullptr);
+ DCHECK(WellKnownClasses::java_lang_Daemons_requestGC != nullptr);
env->CallStaticVoidMethod(WellKnownClasses::java_lang_Daemons,
WellKnownClasses::java_lang_Daemons_requestGC);
CHECK(!env->ExceptionCheck());
}
void Heap::ConcurrentGC(Thread* self) {
- {
- MutexLock mu(self, *Locks::runtime_shutdown_lock_);
- if (Runtime::Current()->IsShuttingDown()) {
- return;
- }
+ if (Runtime::Current()->IsShuttingDown(self)) {
+ return;
}
-
// Wait for any GCs currently running to finish.
- if (WaitForConcurrentGcToComplete(self) == collector::kGcTypeNone) {
+ if (WaitForGcToComplete(self) == collector::kGcTypeNone) {
CollectGarbageInternal(next_gc_type_, kGcCauseBackground, false);
}
}
@@ -1998,26 +2228,18 @@ void Heap::RequestHeapTrim() {
// We could try mincore(2) but that's only a measure of how many pages we haven't given away,
// not how much use we're making of those pages.
uint64_t ms_time = MilliTime();
- // Note the large object space's bytes allocated is equal to its capacity.
- uint64_t los_bytes_allocated = large_object_space_->GetBytesAllocated();
- float utilization = static_cast<float>(GetBytesAllocated() - los_bytes_allocated) /
- (GetTotalMemory() - los_bytes_allocated);
- if ((utilization > 0.75f && !IsLowMemoryMode()) || ((ms_time - last_trim_time_ms_) < 2 * 1000)) {
- // Don't bother trimming the alloc space if it's more than 75% utilized and low memory mode is
- // not enabled, or if a heap trim occurred in the last two seconds.
+ // Don't bother trimming the alloc space if a heap trim occurred in the last two seconds.
+ if (ms_time - last_trim_time_ms_ < 2 * 1000) {
return;
}
Thread* self = Thread::Current();
- {
- MutexLock mu(self, *Locks::runtime_shutdown_lock_);
- Runtime* runtime = Runtime::Current();
- if (runtime == NULL || !runtime->IsFinishedStarting() || runtime->IsShuttingDown()) {
- // Heap trimming isn't supported without a Java runtime or Daemons (such as at dex2oat time)
- // Also: we do not wish to start a heap trim if the runtime is shutting down (a racy check
- // as we don't hold the lock while requesting the trim).
- return;
- }
+ Runtime* runtime = Runtime::Current();
+ if (runtime == nullptr || !runtime->IsFinishedStarting() || runtime->IsShuttingDown(self)) {
+ // Heap trimming isn't supported without a Java runtime or Daemons (such as at dex2oat time)
+ // Also: we do not wish to start a heap trim if the runtime is shutting down (a racy check
+ // as we don't hold the lock while requesting the trim).
+ return;
}
last_trim_time_ms_ = ms_time;
@@ -2034,50 +2256,55 @@ void Heap::RequestHeapTrim() {
}
}
-size_t Heap::Trim() {
- // Handle a requested heap trim on a thread outside of the main GC thread.
- return alloc_space_->Trim();
-}
-
bool Heap::IsGCRequestPending() const {
return concurrent_start_bytes_ != std::numeric_limits<size_t>::max();
}
+void Heap::RunFinalization(JNIEnv* env) {
+ // Can't do this in WellKnownClasses::Init since System is not properly set up at that point.
+ if (WellKnownClasses::java_lang_System_runFinalization == nullptr) {
+ CHECK(WellKnownClasses::java_lang_System != nullptr);
+ WellKnownClasses::java_lang_System_runFinalization =
+ CacheMethod(env, WellKnownClasses::java_lang_System, true, "runFinalization", "()V");
+ CHECK(WellKnownClasses::java_lang_System_runFinalization != nullptr);
+ }
+ env->CallStaticVoidMethod(WellKnownClasses::java_lang_System,
+ WellKnownClasses::java_lang_System_runFinalization);
+}
+
void Heap::RegisterNativeAllocation(JNIEnv* env, int bytes) {
+ Thread* self = ThreadForEnv(env);
+ if (native_need_to_run_finalization_) {
+ RunFinalization(env);
+ UpdateMaxNativeFootprint();
+ native_need_to_run_finalization_ = false;
+ }
// Total number of native bytes allocated.
native_bytes_allocated_.fetch_add(bytes);
if (static_cast<size_t>(native_bytes_allocated_) > native_footprint_gc_watermark_) {
// The second watermark is higher than the gc watermark. If you hit this it means you are
// allocating native objects faster than the GC can keep up with.
if (static_cast<size_t>(native_bytes_allocated_) > native_footprint_limit_) {
- // Can't do this in WellKnownClasses::Init since System is not properly set up at that
- // point.
- if (UNLIKELY(WellKnownClasses::java_lang_System_runFinalization == NULL)) {
- DCHECK(WellKnownClasses::java_lang_System != NULL);
- WellKnownClasses::java_lang_System_runFinalization =
- CacheMethod(env, WellKnownClasses::java_lang_System, true, "runFinalization", "()V");
- CHECK(WellKnownClasses::java_lang_System_runFinalization != NULL);
- }
- if (WaitForConcurrentGcToComplete(ThreadForEnv(env)) != collector::kGcTypeNone) {
- // Just finished a GC, attempt to run finalizers.
- env->CallStaticVoidMethod(WellKnownClasses::java_lang_System,
- WellKnownClasses::java_lang_System_runFinalization);
- CHECK(!env->ExceptionCheck());
- }
-
- // If we still are over the watermark, attempt a GC for alloc and run finalizers.
- if (static_cast<size_t>(native_bytes_allocated_) > native_footprint_limit_) {
- CollectGarbageInternal(collector::kGcTypePartial, kGcCauseForAlloc, false);
- env->CallStaticVoidMethod(WellKnownClasses::java_lang_System,
- WellKnownClasses::java_lang_System_runFinalization);
- CHECK(!env->ExceptionCheck());
- }
- // We have just run finalizers, update the native watermark since it is very likely that
- // finalizers released native managed allocations.
- UpdateMaxNativeFootprint();
- } else {
- if (!IsGCRequestPending()) {
- RequestConcurrentGC(ThreadForEnv(env));
+ if (WaitForGcToComplete(self) != collector::kGcTypeNone) {
+ // Just finished a GC, attempt to run finalizers.
+ RunFinalization(env);
+ CHECK(!env->ExceptionCheck());
+ }
+ // If we still are over the watermark, attempt a GC for alloc and run finalizers.
+ if (static_cast<size_t>(native_bytes_allocated_) > native_footprint_limit_) {
+ CollectGarbageInternal(collector::kGcTypePartial, kGcCauseForAlloc, false);
+ RunFinalization(env);
+ native_need_to_run_finalization_ = false;
+ CHECK(!env->ExceptionCheck());
+ }
+ // We have just run finalizers, update the native watermark since it is very likely that
+ // finalizers released native managed allocations.
+ UpdateMaxNativeFootprint();
+ } else if (!IsGCRequestPending()) {
+ if (concurrent_gc_) {
+ RequestConcurrentGC(self);
+ } else {
+ CollectGarbageInternal(collector::kGcTypePartial, kGcCauseForAlloc, false);
}
}
}
@@ -2086,26 +2313,24 @@ void Heap::RegisterNativeAllocation(JNIEnv* env, int bytes) {
void Heap::RegisterNativeFree(JNIEnv* env, int bytes) {
int expected_size, new_size;
do {
- expected_size = native_bytes_allocated_.load();
- new_size = expected_size - bytes;
- if (UNLIKELY(new_size < 0)) {
- ScopedObjectAccess soa(env);
- env->ThrowNew(WellKnownClasses::java_lang_RuntimeException,
- StringPrintf("Attempted to free %d native bytes with only %d native bytes "
- "registered as allocated", bytes, expected_size).c_str());
- break;
- }
+ expected_size = native_bytes_allocated_.load();
+ new_size = expected_size - bytes;
+ if (UNLIKELY(new_size < 0)) {
+ ScopedObjectAccess soa(env);
+ env->ThrowNew(WellKnownClasses::java_lang_RuntimeException,
+ StringPrintf("Attempted to free %d native bytes with only %d native bytes "
+ "registered as allocated", bytes, expected_size).c_str());
+ break;
+ }
} while (!native_bytes_allocated_.compare_and_swap(expected_size, new_size));
}
int64_t Heap::GetTotalMemory() const {
int64_t ret = 0;
for (const auto& space : continuous_spaces_) {
- if (space->IsImageSpace()) {
- // Currently don't include the image space.
- } else if (space->IsDlMallocSpace()) {
- // Zygote or alloc space
- ret += space->AsDlMallocSpace()->GetFootprint();
+ // Currently don't include the image space.
+ if (!space->IsImageSpace()) {
+ ret += space->Size();
}
}
for (const auto& space : discontinuous_spaces_) {