Add clamp growth limit

Clamp growth limit shrinks the space memmaps to the current growth
limit. This reduces virtual memory usage for apps with small heaps.

Bug: 18387825
Bug: 17131630

Change-Id: I4a8fdc335d2c40492e991708adabcc46299efb7d
diff --git a/runtime/gc/accounting/space_bitmap.h b/runtime/gc/accounting/space_bitmap.h
index e73166b..7bc83ef 100644
--- a/runtime/gc/accounting/space_bitmap.h
+++ b/runtime/gc/accounting/space_bitmap.h
@@ -160,6 +160,12 @@
     return IndexToOffset<uint64_t>(Size() / sizeof(intptr_t));
   }
 
+  void SetHeapSize(size_t bytes) {
+    // TODO: Un-map the end of the mem map.
+    bitmap_size_ = OffsetToIndex(bytes) * sizeof(intptr_t);
+    CHECK_EQ(HeapSize(), bytes);
+  }
+
   uintptr_t HeapBegin() const {
     return heap_begin_;
   }
diff --git a/runtime/gc/heap.cc b/runtime/gc/heap.cc
index 2575676..db287d3 100644
--- a/runtime/gc/heap.cc
+++ b/runtime/gc/heap.cc
@@ -2978,6 +2978,20 @@
   }
 }
 
+void Heap::ClampGrowthLimit() {
+  capacity_ = growth_limit_;
+  for (const auto& space : continuous_spaces_) {
+    if (space->IsMallocSpace()) {
+      gc::space::MallocSpace* malloc_space = space->AsMallocSpace();
+      malloc_space->ClampGrowthLimit();
+    }
+  }
+  // This space isn't added for performance reasons.
+  if (main_space_backup_.get() != nullptr) {
+    main_space_backup_->ClampGrowthLimit();
+  }
+}
+
 void Heap::ClearGrowthLimit() {
   growth_limit_ = capacity_;
   for (const auto& space : continuous_spaces_) {
diff --git a/runtime/gc/heap.h b/runtime/gc/heap.h
index 1738124..fc61fc5 100644
--- a/runtime/gc/heap.h
+++ b/runtime/gc/heap.h
@@ -302,6 +302,10 @@
   // implement dalvik.system.VMRuntime.clearGrowthLimit.
   void ClearGrowthLimit();
 
+  // Make the current growth limit the new maximum capacity, unmaps pages at the end of spaces
+  // which will never be used. Used to implement dalvik.system.VMRuntime.clampGrowthLimit.
+  void ClampGrowthLimit();
+
   // Target ideal heap utilization ratio, implements
   // dalvik.system.VMRuntime.getTargetHeapUtilization.
   double GetTargetHeapUtilization() const {
@@ -902,7 +906,7 @@
   collector::GcType next_gc_type_;
 
   // Maximum size that the heap can reach.
-  const size_t capacity_;
+  size_t capacity_;
 
   // The size the heap is limited to. This is initially smaller than capacity, but for largeHeap
   // programs it is "cleared" making it the same as capacity.
diff --git a/runtime/gc/space/malloc_space.cc b/runtime/gc/space/malloc_space.cc
index 7905bb4..9bbbb3c 100644
--- a/runtime/gc/space/malloc_space.cc
+++ b/runtime/gc/space/malloc_space.cc
@@ -248,6 +248,16 @@
   context->freed.bytes += space->FreeList(self, num_ptrs, ptrs);
 }
 
+void MallocSpace::ClampGrowthLimit() {
+  size_t new_capacity = Capacity();
+  CHECK_LE(new_capacity, NonGrowthLimitCapacity());
+  GetLiveBitmap()->SetHeapSize(new_capacity);
+  GetMarkBitmap()->SetHeapSize(new_capacity);
+  GetMemMap()->SetSize(new_capacity);
+  limit_ = Begin() + new_capacity;
+  CHECK(temp_bitmap_.get() == nullptr);
+}
+
 }  // namespace space
 }  // namespace gc
 }  // namespace art
diff --git a/runtime/gc/space/malloc_space.h b/runtime/gc/space/malloc_space.h
index 2fbd5f0..06239e5 100644
--- a/runtime/gc/space/malloc_space.h
+++ b/runtime/gc/space/malloc_space.h
@@ -110,6 +110,10 @@
     return GetMemMap()->Size();
   }
 
+  // Change the non growth limit capacity by shrinking or expanding the map. Currently, only
+  // shrinking is supported.
+  void ClampGrowthLimit();
+
   void Dump(std::ostream& os) const;
 
   void SetGrowthLimit(size_t growth_limit);
diff --git a/runtime/mem_map.cc b/runtime/mem_map.cc
index 8303f84..a722813 100644
--- a/runtime/mem_map.cc
+++ b/runtime/mem_map.cc
@@ -665,6 +665,19 @@
   maps_ = nullptr;
 }
 
+void MemMap::SetSize(size_t new_size) {
+  if (new_size == base_size_) {
+    return;
+  }
+  CHECK_ALIGNED(new_size, kPageSize);
+  CHECK_EQ(base_size_, size_) << "Unsupported";
+  CHECK_LE(new_size, base_size_);
+  CHECK_EQ(munmap(reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(BaseBegin()) + new_size),
+                  base_size_ - new_size), 0) << new_size << " " << base_size_;
+  base_size_ = new_size;
+  size_ = new_size;
+}
+
 std::ostream& operator<<(std::ostream& os, const MemMap& mem_map) {
   os << StringPrintf("[MemMap: %p-%p prot=0x%x %s]",
                      mem_map.BaseBegin(), mem_map.BaseEnd(), mem_map.GetProtect(),
diff --git a/runtime/mem_map.h b/runtime/mem_map.h
index 9b003aa..dc337e0 100644
--- a/runtime/mem_map.h
+++ b/runtime/mem_map.h
@@ -107,6 +107,9 @@
     return size_;
   }
 
+  // Resize the mem-map by unmapping pages at the end. Currently only supports shrinking.
+  void SetSize(size_t new_size);
+
   uint8_t* End() const {
     return Begin() + Size();
   }
diff --git a/runtime/native/dalvik_system_VMRuntime.cc b/runtime/native/dalvik_system_VMRuntime.cc
index f503b35..471aa9c 100644
--- a/runtime/native/dalvik_system_VMRuntime.cc
+++ b/runtime/native/dalvik_system_VMRuntime.cc
@@ -134,6 +134,10 @@
   Runtime::Current()->GetHeap()->ClearGrowthLimit();
 }
 
+static void VMRuntime_clampGrowthLimit(JNIEnv*, jobject) {
+  Runtime::Current()->GetHeap()->ClampGrowthLimit();
+}
+
 static jboolean VMRuntime_isDebuggerActive(JNIEnv*, jobject) {
   return Dbg::IsDebuggerActive();
 }
@@ -577,6 +581,7 @@
 static JNINativeMethod gMethods[] = {
   NATIVE_METHOD(VMRuntime, addressOf, "!(Ljava/lang/Object;)J"),
   NATIVE_METHOD(VMRuntime, bootClassPath, "()Ljava/lang/String;"),
+  NATIVE_METHOD(VMRuntime, clampGrowthLimit, "()V"),
   NATIVE_METHOD(VMRuntime, classPath, "()Ljava/lang/String;"),
   NATIVE_METHOD(VMRuntime, clearGrowthLimit, "()V"),
   NATIVE_METHOD(VMRuntime, concurrentGC, "()V"),