| // Copyright 2011 Google Inc. All Rights Reserved. |
| |
| #include "heap.h" |
| |
| #include <vector> |
| |
| #include "UniquePtr.h" |
| #include "image.h" |
| #include "mark_sweep.h" |
| #include "object.h" |
| #include "space.h" |
| #include "stl_util.h" |
| #include "thread_list.h" |
| |
| namespace art { |
| |
| std::vector<Space*> Heap::spaces_; |
| |
| Space* Heap::boot_space_ = NULL; |
| |
| Space* Heap::alloc_space_ = NULL; |
| |
| size_t Heap::maximum_size_ = 0; |
| |
| size_t Heap::num_bytes_allocated_ = 0; |
| |
| size_t Heap::num_objects_allocated_ = 0; |
| |
| bool Heap::is_gc_running_ = false; |
| |
| HeapBitmap* Heap::mark_bitmap_ = NULL; |
| |
| HeapBitmap* Heap::live_bitmap_ = NULL; |
| |
| MemberOffset Heap::reference_referent_offset_ = MemberOffset(0); |
| MemberOffset Heap::reference_queue_offset_ = MemberOffset(0); |
| MemberOffset Heap::reference_queueNext_offset_ = MemberOffset(0); |
| MemberOffset Heap::reference_pendingNext_offset_ = MemberOffset(0); |
| MemberOffset Heap::finalizer_reference_zombie_offset_ = MemberOffset(0); |
| |
| Mutex* Heap::lock_ = NULL; |
| |
| bool Heap::verify_objects_ = false; |
| |
| class ScopedHeapLock { |
| public: |
| ScopedHeapLock() { |
| Heap::Lock(); |
| } |
| |
| ~ScopedHeapLock() { |
| Heap::Unlock(); |
| } |
| }; |
| |
| void Heap::Init(size_t initial_size, size_t maximum_size, |
| const char* boot_image_file_name, |
| std::vector<const char*>& image_file_names) { |
| Space* boot_space; |
| byte* requested_base; |
| if (boot_image_file_name == NULL) { |
| boot_space = NULL; |
| requested_base = NULL; |
| } else { |
| boot_space = Space::Create(boot_image_file_name); |
| if (boot_space == NULL) { |
| LOG(FATAL) << "Failed to create space from " << boot_image_file_name; |
| } |
| spaces_.push_back(boot_space); |
| requested_base = boot_space->GetBase() + RoundUp(boot_space->Size(), kPageSize); |
| } |
| |
| std::vector<Space*> image_spaces; |
| for (size_t i = 0; i < image_file_names.size(); i++) { |
| Space* space = Space::Create(image_file_names[i]); |
| if (space == NULL) { |
| LOG(FATAL) << "Failed to create space from " << image_file_names[i]; |
| } |
| image_spaces.push_back(space); |
| spaces_.push_back(space); |
| requested_base = space->GetBase() + RoundUp(space->Size(), kPageSize); |
| } |
| |
| Space* space = Space::Create(initial_size, maximum_size, requested_base); |
| if (space == NULL) { |
| LOG(FATAL) << "Failed to create alloc space"; |
| } |
| |
| if (boot_space == NULL) { |
| boot_space = space; |
| } |
| byte* base = std::min(boot_space->GetBase(), space->GetBase()); |
| byte* limit = std::max(boot_space->GetLimit(), space->GetLimit()); |
| DCHECK_LT(base, limit); |
| size_t num_bytes = limit - base; |
| |
| // Allocate the initial live bitmap. |
| UniquePtr<HeapBitmap> live_bitmap(HeapBitmap::Create(base, num_bytes)); |
| if (live_bitmap.get() == NULL) { |
| LOG(FATAL) << "Failed to create live bitmap"; |
| } |
| |
| // Allocate the initial mark bitmap. |
| UniquePtr<HeapBitmap> mark_bitmap(HeapBitmap::Create(base, num_bytes)); |
| if (mark_bitmap.get() == NULL) { |
| LOG(FATAL) << "Failed to create mark bitmap"; |
| } |
| |
| alloc_space_ = space; |
| spaces_.push_back(space); |
| maximum_size_ = maximum_size; |
| live_bitmap_ = live_bitmap.release(); |
| mark_bitmap_ = mark_bitmap.release(); |
| |
| num_bytes_allocated_ = 0; |
| num_objects_allocated_ = 0; |
| |
| // TODO: allocate the card table |
| |
| // Make objects in boot_space live (after live_bitmap_ is set) |
| if (boot_image_file_name != NULL) { |
| boot_space_ = boot_space; |
| RecordImageAllocations(boot_space); |
| } |
| for (size_t i = 0; i < image_spaces.size(); i++) { |
| RecordImageAllocations(image_spaces[i]); |
| } |
| |
| Heap::EnableObjectValidation(); |
| |
| // It's still to early to take a lock because there are no threads yet, |
| // but we can create the heap lock now. We don't create it earlier to |
| // make it clear that you can't use locks during heap initialization. |
| lock_ = new Mutex("Heap lock"); |
| } |
| |
| void Heap::Destroy() { |
| ScopedHeapLock lock; |
| STLDeleteElements(&spaces_); |
| if (mark_bitmap_ != NULL) { |
| delete mark_bitmap_; |
| mark_bitmap_ = NULL; |
| } |
| if (live_bitmap_ != NULL) { |
| delete live_bitmap_; |
| } |
| live_bitmap_ = NULL; |
| } |
| |
| Object* Heap::AllocObject(Class* klass, size_t num_bytes) { |
| ScopedHeapLock lock; |
| DCHECK(klass == NULL |
| || klass->GetDescriptor() == NULL |
| || (klass->IsClassClass() && num_bytes >= sizeof(Class)) |
| || (klass->IsVariableSize() || klass->GetObjectSize() == num_bytes)); |
| DCHECK(num_bytes >= sizeof(Object)); |
| Object* obj = AllocateLocked(num_bytes); |
| if (obj != NULL) { |
| obj->SetClass(klass); |
| } |
| return obj; |
| } |
| |
| bool Heap::IsHeapAddress(const Object* obj) { |
| // Note: we deliberately don't take the lock here, and mustn't test anything that would |
| // require taking the lock. |
| if (!IsAligned(obj, kObjectAlignment)) { |
| return false; |
| } |
| // TODO |
| return true; |
| } |
| |
| #if VERIFY_OBJECT_ENABLED |
| void Heap::VerifyObject(const Object* obj) { |
| if (!verify_objects_) { |
| return; |
| } |
| ScopedHeapLock lock; |
| Heap::VerifyObjectLocked(obj); |
| } |
| #endif |
| |
| void Heap::VerifyObjectLocked(const Object* obj) { |
| lock_->AssertHeld(); |
| if (obj != NULL) { |
| if (!IsAligned(obj, kObjectAlignment)) { |
| LOG(FATAL) << "Object isn't aligned: " << obj; |
| } else if (!live_bitmap_->Test(obj)) { |
| // TODO: we don't hold a lock here as it is assumed the live bit map |
| // isn't changing if the mutator is running. |
| LOG(FATAL) << "Object is dead: " << obj; |
| } |
| // Ignore early dawn of the universe verifications |
| if (num_objects_allocated_ > 10) { |
| const byte* raw_addr = reinterpret_cast<const byte*>(obj) + |
| Object::ClassOffset().Int32Value(); |
| const Class* c = *reinterpret_cast<Class* const *>(raw_addr); |
| if (c == NULL) { |
| LOG(FATAL) << "Null class" << " in object: " << obj; |
| } else if (!IsAligned(c, kObjectAlignment)) { |
| LOG(FATAL) << "Class isn't aligned: " << c << " in object: " << obj; |
| } else if (!live_bitmap_->Test(c)) { |
| LOG(FATAL) << "Class of object is dead: " << c << " in object: " << obj; |
| } |
| // Check obj.getClass().getClass() == obj.getClass().getClass().getClass() |
| // NB we don't use the accessors here as they have internal sanity checks |
| // that we don't want to run |
| raw_addr = reinterpret_cast<const byte*>(c) + |
| Object::ClassOffset().Int32Value(); |
| const Class* c_c = *reinterpret_cast<Class* const *>(raw_addr); |
| raw_addr = reinterpret_cast<const byte*>(c_c) + |
| Object::ClassOffset().Int32Value(); |
| const Class* c_c_c = *reinterpret_cast<Class* const *>(raw_addr); |
| CHECK_EQ(c_c, c_c_c); |
| } |
| } |
| } |
| |
| void Heap::VerificationCallback(Object* obj, void* arg) { |
| DCHECK(obj != NULL); |
| Heap::VerifyObjectLocked(obj); |
| } |
| |
| void Heap::VerifyHeap() { |
| ScopedHeapLock lock; |
| live_bitmap_->Walk(Heap::VerificationCallback, NULL); |
| } |
| |
| void Heap::RecordAllocationLocked(Space* space, const Object* obj) { |
| #ifndef NDEBUG |
| if (Runtime::Current()->IsStarted()) { |
| lock_->AssertHeld(); |
| } |
| #endif |
| size_t size = space->AllocationSize(obj); |
| DCHECK_NE(size, 0u); |
| num_bytes_allocated_ += size; |
| num_objects_allocated_ += 1; |
| |
| if (Runtime::Current()->HasStatsEnabled()) { |
| RuntimeStats* global_stats = Runtime::Current()->GetStats(); |
| RuntimeStats* thread_stats = Thread::Current()->GetStats(); |
| ++global_stats->allocated_objects; |
| ++thread_stats->allocated_objects; |
| global_stats->allocated_bytes += size; |
| thread_stats->allocated_bytes += size; |
| } |
| |
| live_bitmap_->Set(obj); |
| } |
| |
| void Heap::RecordFreeLocked(Space* space, const Object* obj) { |
| lock_->AssertHeld(); |
| size_t size = space->AllocationSize(obj); |
| DCHECK_NE(size, 0u); |
| if (size < num_bytes_allocated_) { |
| num_bytes_allocated_ -= size; |
| } else { |
| num_bytes_allocated_ = 0; |
| } |
| live_bitmap_->Clear(obj); |
| if (num_objects_allocated_ > 0) { |
| num_objects_allocated_ -= 1; |
| } |
| |
| if (Runtime::Current()->HasStatsEnabled()) { |
| RuntimeStats* global_stats = Runtime::Current()->GetStats(); |
| RuntimeStats* thread_stats = Thread::Current()->GetStats(); |
| ++global_stats->freed_objects; |
| ++thread_stats->freed_objects; |
| global_stats->freed_bytes += size; |
| thread_stats->freed_bytes += size; |
| } |
| } |
| |
| void Heap::RecordImageAllocations(Space* space) { |
| DCHECK(!Runtime::Current()->IsStarted()); |
| CHECK(space != NULL); |
| CHECK(live_bitmap_ != NULL); |
| byte* current = space->GetBase() + RoundUp(sizeof(ImageHeader), kObjectAlignment); |
| while (current < space->GetLimit()) { |
| DCHECK(IsAligned(current, kObjectAlignment)); |
| const Object* obj = reinterpret_cast<const Object*>(current); |
| live_bitmap_->Set(obj); |
| current += RoundUp(obj->SizeOf(), kObjectAlignment); |
| } |
| } |
| |
| Object* Heap::AllocateLocked(size_t size) { |
| lock_->AssertHeld(); |
| DCHECK(alloc_space_ != NULL); |
| Space* space = alloc_space_; |
| Object* obj = AllocateLocked(space, size); |
| if (obj != NULL) { |
| RecordAllocationLocked(space, obj); |
| } |
| return obj; |
| } |
| |
| Object* Heap::AllocateLocked(Space* space, size_t size) { |
| lock_->AssertHeld(); |
| |
| // Fail impossible allocations. TODO: collect soft references. |
| if (size > maximum_size_) { |
| return NULL; |
| } |
| |
| Object* ptr = space->AllocWithoutGrowth(size); |
| if (ptr != NULL) { |
| return ptr; |
| } |
| |
| // The allocation failed. If the GC is running, block until it |
| // completes and retry. |
| if (is_gc_running_) { |
| // The GC is concurrently tracing the heap. Release the heap |
| // lock, wait for the GC to complete, and retrying allocating. |
| WaitForConcurrentGcToComplete(); |
| ptr = space->AllocWithoutGrowth(size); |
| if (ptr != NULL) { |
| return ptr; |
| } |
| } |
| |
| // Another failure. Our thread was starved or there may be too many |
| // live objects. Try a foreground GC. This will have no effect if |
| // the concurrent GC is already running. |
| if (Runtime::Current()->HasStatsEnabled()) { |
| ++Runtime::Current()->GetStats()->gc_for_alloc_count; |
| ++Thread::Current()->GetStats()->gc_for_alloc_count; |
| } |
| UNIMPLEMENTED(FATAL) << "No implicit GC, use larger -Xms -Xmx"; |
| CollectGarbageInternal(); |
| ptr = space->AllocWithoutGrowth(size); |
| if (ptr != NULL) { |
| return ptr; |
| } |
| |
| // Even that didn't work; this is an exceptional state. |
| // Try harder, growing the heap if necessary. |
| ptr = space->AllocWithGrowth(size); |
| if (ptr != NULL) { |
| //size_t new_footprint = dvmHeapSourceGetIdealFootprint(); |
| size_t new_footprint = space->MaxAllowedFootprint(); |
| // TODO: may want to grow a little bit more so that the amount of |
| // free space is equal to the old free space + the |
| // utilization slop for the new allocation. |
| LOG(INFO) << "Grow heap (frag case) to " << new_footprint / MB |
| << "for " << size << "-byte allocation"; |
| return ptr; |
| } |
| |
| // Most allocations should have succeeded by now, so the heap is |
| // really full, really fragmented, 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 an OOME. |
| |
| // TODO: wait for the finalizers from the previous GC to finish |
| LOG(INFO) << "Forcing collection of SoftReferences for " |
| << size << "-byte allocation"; |
| CollectGarbageInternal(); |
| ptr = space->AllocWithGrowth(size); |
| if (ptr != NULL) { |
| return ptr; |
| } |
| |
| LOG(ERROR) << "Out of memory on a " << size << " byte allocation"; |
| |
| // TODO: tell the HeapSource to dump its state |
| // TODO: dump stack traces for all threads |
| |
| return NULL; |
| } |
| |
| int64_t Heap::GetMaxMemory() { |
| UNIMPLEMENTED(WARNING); |
| return 0; |
| } |
| |
| int64_t Heap::GetTotalMemory() { |
| UNIMPLEMENTED(WARNING); |
| return 0; |
| } |
| |
| int64_t Heap::GetFreeMemory() { |
| UNIMPLEMENTED(WARNING); |
| return 0; |
| } |
| |
| class InstanceCounter { |
| public: |
| InstanceCounter(Class* c, bool count_assignable) |
| : class_(c), count_assignable_(count_assignable), count_(0) { |
| } |
| |
| size_t GetCount() { |
| return count_; |
| } |
| |
| static void Callback(Object* o, void* arg) { |
| reinterpret_cast<InstanceCounter*>(arg)->VisitInstance(o); |
| } |
| |
| private: |
| void VisitInstance(Object* o) { |
| Class* instance_class = o->GetClass(); |
| if (count_assignable_) { |
| if (instance_class == class_) { |
| ++count_; |
| } |
| } else { |
| if (instance_class != NULL && class_->IsAssignableFrom(instance_class)) { |
| ++count_; |
| } |
| } |
| } |
| |
| Class* class_; |
| bool count_assignable_; |
| size_t count_; |
| }; |
| |
| int64_t Heap::CountInstances(Class* c, bool count_assignable) { |
| ScopedHeapLock lock; |
| InstanceCounter counter(c, count_assignable); |
| live_bitmap_->Walk(InstanceCounter::Callback, &counter); |
| return counter.GetCount(); |
| } |
| |
| void Heap::CollectGarbage() { |
| ScopedHeapLock lock; |
| CollectGarbageInternal(); |
| } |
| |
| void Heap::CollectGarbageInternal() { |
| lock_->AssertHeld(); |
| |
| ThreadList* thread_list = Runtime::Current()->GetThreadList(); |
| thread_list->SuspendAll(); |
| { |
| MarkSweep mark_sweep; |
| |
| mark_sweep.Init(); |
| |
| mark_sweep.MarkRoots(); |
| |
| // Push marked roots onto the mark stack |
| |
| // TODO: if concurrent |
| // unlock heap |
| // thread_list->ResumeAll(); |
| |
| mark_sweep.RecursiveMark(); |
| |
| // TODO: if concurrent |
| // lock heap |
| // thread_list->SuspendAll(); |
| // re-mark root set |
| // scan dirty objects |
| |
| mark_sweep.ProcessReferences(false); |
| |
| // TODO: swap bitmaps |
| |
| mark_sweep.Sweep(); |
| } |
| |
| GrowForUtilization(); |
| thread_list->ResumeAll(); |
| } |
| |
| void Heap::WaitForConcurrentGcToComplete() { |
| lock_->AssertHeld(); |
| } |
| |
| // Given the current contents of the active heap, increase the allowed |
| // heap footprint to match the target utilization ratio. This should |
| // only be called immediately after a full garbage collection. |
| void Heap::GrowForUtilization() { |
| lock_->AssertHeld(); |
| UNIMPLEMENTED(ERROR); |
| } |
| |
| void Heap::Lock() { |
| // TODO: grab the lock, but put ourselves into Thread::kVmWait if it looks like |
| // we're going to have to wait on the mutex. |
| lock_->Lock(); |
| } |
| |
| void Heap::Unlock() { |
| lock_->Unlock(); |
| } |
| |
| } // namespace art |