Add fallback for non moving space being full.

When the non moving space becomes full, we disable moving GC and
make the main space the new non moving space.

Also added a runtime option for changing the non moving space size:
-XX:NonMovingSpaceCapacity.

Bug: 17189964
Change-Id: I04d00d0f457f6c1f5724bf32932b6b6ce4d341b1
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 8247911..47e1709 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -86,7 +86,6 @@
 static constexpr bool kUseFreeListSpaceForLOS = false;
 // Whether or not we compact the zygote in PreZygoteFork.
 static constexpr bool kCompactZygote = kMovingCollector;
-static constexpr size_t kNonMovingSpaceCapacity = 64 * MB;
 // How many reserve entries are at the end of the allocation stack, these are only needed if the
 // allocation stack overflows.
 static constexpr size_t kAllocationStackReserveSize = 1024;
@@ -99,10 +98,11 @@
 static constexpr size_t kGSSBumpPointerSpaceCapacity = 32 * MB;
 
 Heap::Heap(size_t initial_size, size_t growth_limit, size_t min_free, size_t max_free,
-           double target_utilization, double foreground_heap_growth_multiplier, size_t capacity,
-           const std::string& image_file_name, const InstructionSet image_instruction_set,
-           CollectorType foreground_collector_type, CollectorType background_collector_type,
-           size_t parallel_gc_threads, size_t conc_gc_threads, bool low_memory_mode,
+           double target_utilization, double foreground_heap_growth_multiplier,
+           size_t capacity, size_t non_moving_space_capacity, const std::string& image_file_name,
+           const InstructionSet image_instruction_set, CollectorType foreground_collector_type,
+           CollectorType background_collector_type, 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, bool use_tlab,
            bool verify_pre_gc_heap, bool verify_pre_sweeping_heap, bool verify_post_gc_heap,
@@ -215,7 +215,7 @@
   }
   /*
   requested_alloc_space_begin ->     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
-                                     +-  nonmoving space (kNonMovingSpaceCapacity) +-
+                                     +-  nonmoving space (non_moving_space_capacity)+-
                                      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
                                      +-main alloc space / bump space 1 (capacity_) +-
                                      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
@@ -242,7 +242,7 @@
   std::unique_ptr<MemMap> main_mem_map_2;
   byte* request_begin = requested_alloc_space_begin;
   if (request_begin != nullptr && separate_non_moving_space) {
-    request_begin += kNonMovingSpaceCapacity;
+    request_begin += non_moving_space_capacity;
   }
   std::string error_str;
   std::unique_ptr<MemMap> non_moving_space_mem_map;
@@ -251,7 +251,7 @@
     // address.
     non_moving_space_mem_map.reset(
         MemMap::MapAnonymous("non moving space", requested_alloc_space_begin,
-                             kNonMovingSpaceCapacity, PROT_READ | PROT_WRITE, true, &error_str));
+                             non_moving_space_capacity, PROT_READ | PROT_WRITE, true, &error_str));
     CHECK(non_moving_space_mem_map != nullptr) << error_str;
   }
   // Attempt to create 2 mem maps at or after the requested begin.
@@ -272,7 +272,7 @@
     // active rosalloc spaces.
     const size_t size = non_moving_space_mem_map->Size();
     non_moving_space_ = space::DlMallocSpace::CreateFromMemMap(
-        non_moving_space_mem_map.release(), "zygote / non moving space", initial_size,
+        non_moving_space_mem_map.release(), "zygote / non moving space", kDefaultStartingSize,
         initial_size, size, size, false);
     non_moving_space_->SetFootprintLimit(non_moving_space_->Capacity());
     CHECK(non_moving_space_ != nullptr) << "Failed creating non moving space "
@@ -492,14 +492,33 @@
   }
 }
 
-void Heap::DisableCompaction() {
+void Heap::DisableMovingGc() {
   if (IsMovingGc(foreground_collector_type_)) {
-    foreground_collector_type_  = kCollectorTypeCMS;
+    foreground_collector_type_ = kCollectorTypeCMS;
   }
   if (IsMovingGc(background_collector_type_)) {
     background_collector_type_ = foreground_collector_type_;
   }
   TransitionCollector(foreground_collector_type_);
+  ThreadList* tl = Runtime::Current()->GetThreadList();
+  Thread* self = Thread::Current();
+  ScopedThreadStateChange tsc(self, kSuspended);
+  tl->SuspendAll();
+  // Something may have caused the transition to fail.
+  if (!IsMovingGc(foreground_collector_type_) && non_moving_space_ != main_space_) {
+    CHECK(main_space_ != nullptr);
+    // The allocation stack may have non movable objects in it. We need to flush it since the GC
+    // can't only handle marking allocation stack objects of one non moving space and one main
+    // space.
+    {
+      WriterMutexLock mu(self, *Locks::heap_bitmap_lock_);
+      FlushAllocStack();
+    }
+    main_space_->DisableMovingObjects();
+    non_moving_space_ = main_space_;
+    CHECK(!non_moving_space_->CanMoveObjects());
+  }
+  tl->ResumeAll();
 }
 
 std::string Heap::SafeGetClassDescriptor(mirror::Class* klass) {
@@ -1279,41 +1298,73 @@
     return nullptr;
   }
   ptr = TryToAllocate<true, true>(self, allocator, alloc_size, bytes_allocated, usable_size);
-  if (ptr == nullptr && use_homogeneous_space_compaction_for_oom_) {
+  if (ptr == nullptr) {
     const uint64_t current_time = NanoTime();
-    if ((allocator == kAllocatorTypeRosAlloc || allocator == kAllocatorTypeDlMalloc) &&
-        current_time - last_time_homogeneous_space_compaction_by_oom_ >
-        min_interval_homogeneous_space_compaction_by_oom_) {
-      last_time_homogeneous_space_compaction_by_oom_ = current_time;
-      HomogeneousSpaceCompactResult result = PerformHomogeneousSpaceCompact();
-      switch (result) {
-        case HomogeneousSpaceCompactResult::kSuccess:
-          // If the allocation succeeded, we delayed an oom.
-          ptr = TryToAllocate<true, true>(self, allocator, alloc_size, bytes_allocated, usable_size);
-          if (ptr != nullptr) {
-            count_delayed_oom_++;
+    switch (allocator) {
+      case kAllocatorTypeRosAlloc:
+        // Fall-through.
+      case kAllocatorTypeDlMalloc: {
+        if (use_homogeneous_space_compaction_for_oom_ &&
+            current_time - last_time_homogeneous_space_compaction_by_oom_ >
+            min_interval_homogeneous_space_compaction_by_oom_) {
+          last_time_homogeneous_space_compaction_by_oom_ = current_time;
+          HomogeneousSpaceCompactResult result = PerformHomogeneousSpaceCompact();
+          switch (result) {
+            case HomogeneousSpaceCompactResult::kSuccess:
+              // If the allocation succeeded, we delayed an oom.
+              ptr = TryToAllocate<true, true>(self, allocator, alloc_size, bytes_allocated,
+                                              usable_size);
+              if (ptr != nullptr) {
+                count_delayed_oom_++;
+              }
+              break;
+            case HomogeneousSpaceCompactResult::kErrorReject:
+              // Reject due to disabled moving GC.
+              break;
+            case HomogeneousSpaceCompactResult::kErrorVMShuttingDown:
+              // Throw OOM by default.
+              break;
+            default: {
+              LOG(FATAL) << "Unimplemented homogeneous space compaction result "
+                         << static_cast<size_t>(result);
+            }
           }
-          break;
-        case HomogeneousSpaceCompactResult::kErrorReject:
-          // Reject due to disabled moving GC.
-          break;
-        case HomogeneousSpaceCompactResult::kErrorVMShuttingDown:
-          // Throw OOM by default.
-          break;
-        default: {
-          LOG(FATAL) << "Unimplemented homogeneous space compaction result " << static_cast<size_t>(result);
+          // Always print that we ran homogeneous space compation since this can cause jank.
+          VLOG(heap) << "Ran heap homogeneous space compaction, "
+                    << " requested defragmentation "
+                    << count_requested_homogeneous_space_compaction_.LoadSequentiallyConsistent()
+                    << " performed defragmentation "
+                    << count_performed_homogeneous_space_compaction_.LoadSequentiallyConsistent()
+                    << " ignored homogeneous space compaction "
+                    << count_ignored_homogeneous_space_compaction_.LoadSequentiallyConsistent()
+                    << " delayed count = "
+                    << count_delayed_oom_.LoadSequentiallyConsistent();
         }
+        break;
       }
-      // Always print that we ran homogeneous space compation since this can cause jank.
-      VLOG(heap) << "Ran heap homogeneous space compaction, "
-                << " requested defragmentation "
-                << count_requested_homogeneous_space_compaction_.LoadSequentiallyConsistent()
-                << " performed defragmentation "
-                << count_performed_homogeneous_space_compaction_.LoadSequentiallyConsistent()
-                << " ignored homogeneous space compaction "
-                << count_ignored_homogeneous_space_compaction_.LoadSequentiallyConsistent()
-                << " delayed count = "
-                << count_delayed_oom_.LoadSequentiallyConsistent();
+      case kAllocatorTypeNonMoving: {
+        // Try to transition the heap if the allocation failure was due to the space being full.
+        if (!IsOutOfMemoryOnAllocation<false>(allocator, alloc_size)) {
+          // If we aren't out of memory then the OOM was probably from the non moving space being
+          // full. Attempt to disable compaction and turn the main space into a non moving space.
+          DisableMovingGc();
+          // If we are still a moving GC then something must have caused the transition to fail.
+          if (IsMovingGc(collector_type_)) {
+            MutexLock mu(self, *gc_complete_lock_);
+            // If we couldn't disable moving GC, just throw OOME and return null.
+            LOG(WARNING) << "Couldn't disable moving GC with disable GC count "
+                         << disable_moving_gc_count_;
+          } else {
+            LOG(WARNING) << "Disabled moving GC due to the non moving space being full";
+            ptr = TryToAllocate<true, true>(self, allocator, alloc_size, bytes_allocated,
+                                            usable_size);
+          }
+        }
+        break;
+      }
+      default: {
+        // Do nothing for others allocators.
+      }
     }
   }
   // If the allocation hasn't succeeded by this point, throw an OOM error.
@@ -1490,9 +1541,10 @@
     WaitForGcToCompleteLocked(kGcCauseHomogeneousSpaceCompact, self);
     // Homogeneous space compaction is a copying transition, can't run it if the moving GC disable count
     // is non zero.
-    // If the collecotr type changed to something which doesn't benefit from homogeneous space compaction,
+    // If the collector type changed to something which doesn't benefit from homogeneous space compaction,
     // exit.
-    if (disable_moving_gc_count_ != 0 || IsMovingGc(collector_type_)) {
+    if (disable_moving_gc_count_ != 0 || IsMovingGc(collector_type_) ||
+        !main_space_->CanMoveObjects()) {
       return HomogeneousSpaceCompactResult::kErrorReject;
     }
     collector_type_running_ = kCollectorTypeHomogeneousSpaceCompact;
@@ -1551,8 +1603,9 @@
   Thread* const self = Thread::Current();
   ScopedThreadStateChange tsc(self, kWaitingPerformingGc);
   Locks::mutator_lock_->AssertNotHeld(self);
-  const bool copying_transition =
-      IsMovingGc(background_collector_type_) || IsMovingGc(foreground_collector_type_);
+  // Currently we only need a heap transition if we switch from a moving collector to a non moving
+  // one, or visa versa.
+  const bool copying_transition = IsMovingGc(collector_type_) != IsMovingGc(collector_type);
   // Busy wait until we can GC (StartGC can fail if we have a non-zero
   // compacting_gc_disable_count_, this should rarely occurs).
   for (;;) {
@@ -1851,9 +1904,9 @@
   // The end of the non-moving space may be protected, unprotect it so that we can copy the zygote
   // there.
   non_moving_space_->GetMemMap()->Protect(PROT_READ | PROT_WRITE);
-  // Change the collector to the post zygote one.
-  bool same_space = non_moving_space_ == main_space_;
+  const bool same_space = non_moving_space_ == main_space_;
   if (kCompactZygote) {
+    // Can't compact if the non moving space is the same as the main space.
     DCHECK(semi_space_collector_ != nullptr);
     // Temporarily disable rosalloc verification because the zygote
     // compaction will mess up the rosalloc internal metadata.
@@ -1898,6 +1951,7 @@
     non_moving_space_->SetLimit(target_space.Limit());
     VLOG(heap) << "Zygote space size " << non_moving_space_->Size() << " bytes";
   }
+  // Change the collector to the post zygote one.
   ChangeCollector(foreground_collector_type_);
   // Save the old space so that we can remove it after we complete creating the zygote space.
   space::MallocSpace* old_alloc_space = non_moving_space_;
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index d91f5af..c2ba9f8 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -134,6 +134,7 @@
   static constexpr size_t kDefaultStartingSize = kPageSize;
   static constexpr size_t kDefaultInitialSize = 2 * MB;
   static constexpr size_t kDefaultMaximumSize = 256 * MB;
+  static constexpr size_t kDefaultNonMovingSpaceCapacity = 64 * MB;
   static constexpr size_t kDefaultMaxFree = 2 * MB;
   static constexpr size_t kDefaultMinFree = kDefaultMaxFree / 4;
   static constexpr size_t kDefaultLongPauseLogThreshold = MsToNs(5);
@@ -156,6 +157,7 @@
   explicit Heap(size_t initial_size, size_t growth_limit, size_t min_free,
                 size_t max_free, double target_utilization,
                 double foreground_heap_growth_multiplier, size_t capacity,
+                size_t non_moving_space_capacity,
                 const std::string& original_image_file_name,
                 InstructionSet image_instruction_set,
                 CollectorType foreground_collector_type, CollectorType background_collector_type,
@@ -255,7 +257,7 @@
       SHARED_LOCKS_REQUIRED(Locks::heap_bitmap_lock_, Locks::mutator_lock_);
 
   // Returns true if there is any chance that the object (obj) will move.
-  bool IsMovableObject(const mirror::Object* obj) const;
+  bool IsMovableObject(const mirror::Object* obj) const SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
 
   // Enables us to compacting GC until objects are released.
   void IncrementDisableMovingGC(Thread* self);
@@ -514,8 +516,8 @@
   // Assumes there is only one image space.
   space::ImageSpace* GetImageSpace() const;
 
-  // Permenantly disable compaction.
-  void DisableCompaction();
+  // Permenantly disable moving garbage collection.
+  void DisableMovingGc();
 
   space::DlMallocSpace* GetDlMallocSpace() const {
     return dlmalloc_space_;
diff --git a/runtime/gc/space/malloc_space.h b/runtime/gc/space/malloc_space.h
index a52b92b..bace3f6 100644
--- a/runtime/gc/space/malloc_space.h
+++ b/runtime/gc/space/malloc_space.h
@@ -133,6 +133,10 @@
     return can_move_objects_;
   }
 
+  void DisableMovingObjects() {
+    can_move_objects_ = false;
+  }
+
  protected:
   MallocSpace(const std::string& name, MemMap* mem_map, byte* begin, byte* end,
               byte* limit, size_t growth_limit, bool create_bitmaps, bool can_move_objects,
@@ -175,7 +179,7 @@
   size_t growth_limit_;
 
   // True if objects in the space are movable.
-  const bool can_move_objects_;
+  bool can_move_objects_;
 
   // Starting and initial sized, used when you reset the space.
   const size_t starting_size_;
diff --git a/runtime/parsed_options.cc b/runtime/parsed_options.cc
index dd5a6c9..9818385 100644
--- a/runtime/parsed_options.cc
+++ b/runtime/parsed_options.cc
@@ -182,6 +182,7 @@
   heap_maximum_size_ = gc::Heap::kDefaultMaximumSize;
   heap_min_free_ = gc::Heap::kDefaultMinFree;
   heap_max_free_ = gc::Heap::kDefaultMaxFree;
+  heap_non_moving_space_capacity_ = gc::Heap::kDefaultNonMovingSpaceCapacity;
   heap_target_utilization_ = gc::Heap::kDefaultTargetUtilization;
   foreground_heap_growth_multiplier_ = gc::Heap::kDefaultHeapGrowthMultiplier;
   heap_growth_limit_ = 0;  // 0 means no growth limit .
@@ -353,6 +354,14 @@
         return false;
       }
       heap_max_free_ = size;
+    } else if (StartsWith(option, "-XX:NonMovingSpaceCapacity=")) {
+      size_t size = ParseMemoryOption(
+          option.substr(strlen("-XX:NonMovingSpaceCapacity=")).c_str(), 1024);
+      if (size == 0) {
+        Usage("Failed to parse memory option %s\n", option.c_str());
+        return false;
+      }
+      heap_non_moving_space_capacity_ = size;
     } else if (StartsWith(option, "-XX:HeapTargetUtilization=")) {
       if (!ParseDouble(option, '=', 0.1, 0.9, &heap_target_utilization_)) {
         return false;
@@ -755,6 +764,7 @@
   UsageMessage(stream, "  -XX:HeapGrowthLimit=N\n");
   UsageMessage(stream, "  -XX:HeapMinFree=N\n");
   UsageMessage(stream, "  -XX:HeapMaxFree=N\n");
+  UsageMessage(stream, "  -XX:NonMovingSpaceCapacity=N\n");
   UsageMessage(stream, "  -XX:HeapTargetUtilization=doublevalue\n");
   UsageMessage(stream, "  -XX:ForegroundHeapGrowthMultiplier=doublevalue\n");
   UsageMessage(stream, "  -XX:LowMemoryMode\n");
diff --git a/runtime/parsed_options.h b/runtime/parsed_options.h
index 0377e7d..f484e78 100644
--- a/runtime/parsed_options.h
+++ b/runtime/parsed_options.h
@@ -69,6 +69,7 @@
   size_t heap_growth_limit_;
   size_t heap_min_free_;
   size_t heap_max_free_;
+  size_t heap_non_moving_space_capacity_;
   double heap_target_utilization_;
   double foreground_heap_growth_multiplier_;
   unsigned int parallel_gc_threads_;
diff --git a/runtime/runtime.cc b/runtime/runtime.cc
index 855effa..0f419b1 100644
--- a/runtime/runtime.cc
+++ b/runtime/runtime.cc
@@ -596,6 +596,7 @@
                        options->heap_target_utilization_,
                        options->foreground_heap_growth_multiplier_,
                        options->heap_maximum_size_,
+                       options->heap_non_moving_space_capacity_,
                        options->image_,
                        options->image_isa_,
                        options->collector_type_,