Added support for -XX:HeapGrowthLimit switch

Runtime can now take in a growth limit for the heap, which can be
smaller than the maximum size of the heap, and will prevent the heap
from growing beyond that size. The growth limit can also be cleared to
increase the size of the heap to the specified maximum size. This gives
apps some control of the size of the heap, but should be removed when we
have better ways to resize the heap.

Change-Id: I338655bccd20bfd32e2318365f0f3283dbaaab1e
diff --git a/build/Android.common.mk b/build/Android.common.mk
index 096350a..4fb0187 100644
--- a/build/Android.common.mk
+++ b/build/Android.common.mk
@@ -248,6 +248,7 @@
 	ExceptionHandle \
 	ExceptionTest \
 	Fibonacci \
+	GrowthLimit \
 	HelloWorld \
 	IntMath \
 	Interfaces \
diff --git a/build/Android.oattest.mk b/build/Android.oattest.mk
index 7233f08..d9113d0 100644
--- a/build/Android.oattest.mk
+++ b/build/Android.oattest.mk
@@ -77,6 +77,7 @@
 
 # The rest are alphabetical
 $(eval $(call declare-test-test-target,ExceptionTest,))
+$(eval $(call declare-test-test-target,GrowthLimit,))
 $(eval $(call declare-test-test-target,IntMath,))
 $(eval $(call declare-test-test-target,Invoke,))
 $(eval $(call declare-test-test-target,MemUsage,))
diff --git a/src/heap.cc b/src/heap.cc
index 9fe7750..6fd4ade 100644
--- a/src/heap.cc
+++ b/src/heap.cc
@@ -27,6 +27,8 @@
 
 size_t Heap::maximum_size_ = 0;
 
+size_t Heap::growth_size_ = 0;
+
 size_t Heap::num_bytes_allocated_ = 0;
 
 size_t Heap::num_objects_allocated_ = 0;
@@ -64,7 +66,7 @@
 };
 
 void Heap::Init(bool is_verbose_heap, bool is_verbose_gc,
-                size_t initial_size, size_t maximum_size,
+                size_t initial_size, size_t maximum_size, size_t growth_size,
                 const std::vector<std::string>& image_file_names) {
   is_verbose_heap_ = is_verbose_heap;
   is_verbose_gc_ = is_verbose_gc;
@@ -98,7 +100,7 @@
     limit = std::max(limit, space->GetLimit());
   }
 
-  alloc_space_ = Space::Create("alloc space", initial_size, maximum_size, requested_base);
+  alloc_space_ = Space::Create("alloc space", initial_size, maximum_size, growth_size, requested_base);
   if (alloc_space_ == NULL) {
     LOG(FATAL) << "Failed to create alloc space";
   }
@@ -121,6 +123,7 @@
 
   spaces_.push_back(alloc_space_);
   maximum_size_ = maximum_size;
+  growth_size_ = growth_size;
   live_bitmap_ = live_bitmap.release();
   mark_bitmap_ = mark_bitmap.release();
 
@@ -329,7 +332,7 @@
   DCHECK_EQ(Thread::Current()->GetState(), Thread::kRunnable);
 
   // Fail impossible allocations.  TODO: collect soft references.
-  if (size > maximum_size_) {
+  if (size > growth_size_) {
     return NULL;
   }
 
@@ -405,7 +408,7 @@
 }
 
 int64_t Heap::GetMaxMemory() {
-  return maximum_size_;
+  return growth_size_;
 }
 
 int64_t Heap::GetTotalMemory() {
@@ -563,12 +566,12 @@
 //
 void Heap::SetIdealFootprint(size_t max_allowed_footprint)
 {
-  if (max_allowed_footprint > Heap::maximum_size_) {
+  if (max_allowed_footprint > Heap::growth_size_) {
     if (is_verbose_gc_) {
       LOG(INFO) << "Clamp target GC heap from " << max_allowed_footprint
-                << " to " << Heap::maximum_size_;
+                << " to " << Heap::growth_size_;
     }
-    max_allowed_footprint = Heap::maximum_size_;
+    max_allowed_footprint = Heap::growth_size_;
   }
 
   alloc_space_->SetMaxAllowedFootprint(max_allowed_footprint);
@@ -602,6 +605,14 @@
   SetIdealFootprint(target_size);
 }
 
+void Heap::ClearGrowthLimit() {
+  ScopedHeapLock lock;
+  WaitForConcurrentGcToComplete();
+  CHECK_GE(maximum_size_, growth_size_);
+  growth_size_ = maximum_size_;
+  alloc_space_->ClearGrowthLimit();
+}
+
 pid_t Heap::GetLockOwner() {
   return lock_->GetOwner();
 }
diff --git a/src/heap.h b/src/heap.h
index 2e80571..5b71d18 100644
--- a/src/heap.h
+++ b/src/heap.h
@@ -46,7 +46,7 @@
   // image_file_names names specify Spaces to load based on
   // ImageWriter output.
   static void Init(bool is_verbose_heap, bool is_verbose_gc,
-                   size_t starting_size, size_t maximum_size,
+                   size_t starting_size, size_t maximum_size, size_t growth_size,
                    const std::vector<std::string>& image_file_names);
 
   static void Destroy();
@@ -91,9 +91,8 @@
   static int64_t CountInstances(Class* c, bool count_assignable);
 
   // Implements dalvik.system.VMRuntime.clearGrowthLimit.
-  static void ClearGrowthLimit() {
-    UNIMPLEMENTED(WARNING);
-  }
+  static void ClearGrowthLimit();
+
   // Implements dalvik.system.VMRuntime.getTargetHeapUtilization.
   static float GetTargetHeapUtilization() {
     return target_utilization_;
@@ -215,6 +214,13 @@
   // The maximum size of the heap in bytes.
   static size_t maximum_size_;
 
+  // The largest size the heap may grow. This value allows the app to limit the
+  // growth below the maximum size. This is a work around until we can
+  // dynamically set the maximum size. This value can range between the starting
+  // size and the maximum size but should never be set below the current
+  // footprint of the heap.
+  static size_t growth_size_;
+
   // True while the garbage collector is running.
   static bool is_gc_running_;
 
diff --git a/src/heap_test.cc b/src/heap_test.cc
index addbc0b..77b98e4 100644
--- a/src/heap_test.cc
+++ b/src/heap_test.cc
@@ -6,6 +6,16 @@
 
 class HeapTest : public CommonTest {};
 
+TEST_F(HeapTest, ClearGrowthLimit) {
+  int64_t max_memory_before = Heap::GetMaxMemory();
+  int64_t total_memory_before = Heap::GetTotalMemory();
+  Heap::ClearGrowthLimit();
+  int64_t max_memory_after = Heap::GetMaxMemory();
+  int64_t total_memory_after = Heap::GetTotalMemory();
+  EXPECT_GE(max_memory_after, max_memory_before);
+  EXPECT_GE(total_memory_after, total_memory_before);
+}
+
 TEST_F(HeapTest, GarbageCollectClassLinkerInit) {
   // garbage is created during ClassLinker::Init
 
diff --git a/src/runtime.cc b/src/runtime.cc
index 7aa4f51..9c923fb 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -238,6 +238,7 @@
 
   parsed->heap_initial_size_ = Heap::kInitialSize;
   parsed->heap_maximum_size_ = Heap::kMaximumSize;
+  parsed->heap_growth_limit_ = 0;  // 0 means no growth limit
   parsed->stack_size_ = Thread::kDefaultStackSize;
 
   parsed->is_zygote_ = false;
@@ -300,6 +301,17 @@
         return NULL;
       }
       parsed->heap_maximum_size_ = size;
+    } else if (option.starts_with("-XX:HeapGrowthLimit=")) {
+      size_t size = ParseMemoryOption(option.substr(strlen("-XX:HeapGrowthLimit=")).data(), 1024);
+      if (size == 0) {
+        if (ignore_unrecognized) {
+          continue;
+        }
+        // TODO: usage
+        LOG(FATAL) << "Failed to parse " << option;
+        return NULL;
+      }
+      parsed->heap_growth_limit_ = size;
     } else if (option.starts_with("-Xss")) {
       size_t size = ParseMemoryOption(option.substr(strlen("-Xss")).data(), 1);
       if (size == 0) {
@@ -357,6 +369,9 @@
   if (!compiler && parsed->images_.empty()) {
     parsed->images_.push_back("/data/art-cache/boot.art");
   }
+  if (parsed->heap_growth_limit_ == 0) {
+    parsed->heap_growth_limit_ = parsed->heap_maximum_size_;
+  }
 
   LOG(INFO) << "CheckJNI is " << (parsed->check_jni_ ? "on" : "off");
 
@@ -516,6 +531,7 @@
              options->IsVerbose("gc"),
              options->heap_initial_size_,
              options->heap_maximum_size_,
+             options->heap_growth_limit_,
              options->images_);
 
   BlockSignals();
diff --git a/src/runtime.h b/src/runtime.h
index d29bd3a..504f2fe 100644
--- a/src/runtime.h
+++ b/src/runtime.h
@@ -55,6 +55,7 @@
     bool is_zygote_;
     size_t heap_initial_size_;
     size_t heap_maximum_size_;
+    size_t heap_growth_limit_;
     size_t stack_size_;
     size_t jni_globals_max_;
     size_t lock_profiling_threshold_;
diff --git a/src/space.cc b/src/space.cc
index 9acb54e..593132a 100644
--- a/src/space.cc
+++ b/src/space.cc
@@ -13,9 +13,9 @@
 
 namespace art {
 
-Space* Space::Create(const std::string& name, size_t initial_size, size_t maximum_size, byte* requested_base) {
+Space* Space::Create(const std::string& name, size_t initial_size, size_t maximum_size, size_t growth_size, byte* requested_base) {
   UniquePtr<Space> space(new Space(name));
-  bool success = space->Init(initial_size, maximum_size, requested_base);
+  bool success = space->Init(initial_size, maximum_size, growth_size, requested_base);
   if (!success) {
     return NULL;
   } else {
@@ -56,17 +56,23 @@
   return msp;
 }
 
-bool Space::Init(size_t initial_size, size_t maximum_size, byte* requested_base) {
+bool Space::Init(size_t initial_size, size_t maximum_size, size_t growth_size, byte* requested_base) {
   const Runtime* runtime = Runtime::Current();
   if (runtime->IsVerboseStartup()) {
     LOG(INFO) << "Space::Init entering"
               << " initial_size=" << initial_size
               << " maximum_size=" << maximum_size
+              << " growth_size=" << growth_size
               << " requested_base=" << reinterpret_cast<void*>(requested_base);
   }
-  if (!(initial_size <= maximum_size)) {
-    LOG(WARNING) << "Failed to create space with initial size > maximum size ("
-                 << initial_size << ">" << maximum_size << ")";
+  if (initial_size > growth_size) {
+    LOG(ERROR) << "Failed to create space with initial size > growth size ("
+               << initial_size << ">" << growth_size << ")";
+    return false;
+  }
+  if (growth_size > maximum_size) {
+    LOG(ERROR) << "Failed to create space with growth size > maximum size ("
+               << growth_size << ">" << maximum_size << ")";
     return false;
   }
   size_t length = RoundUp(maximum_size, kPageSize);
@@ -78,6 +84,9 @@
   }
   Init(mem_map.release());
   maximum_size_ = maximum_size;
+  size_t growth_length = RoundUp(growth_size, kPageSize);
+  growth_size_ = growth_size;
+  growth_limit_ = base_ + growth_length;
   mspace_ = CreateMallocSpace(base_, initial_size, maximum_size);
   if (mspace_ == NULL) {
     LOG(WARNING) << "Failed to create mspace for space";
@@ -152,6 +161,7 @@
   runtime->SetCalleeSaveMethod(down_cast<Method*>(callee_save_method), Runtime::kRefsAndArgs);
 
   Init(map.release());
+  growth_limit_ = limit_;
   if (runtime->IsVerboseStartup()) {
     LOG(INFO) << "Space::InitFromImage exiting";
   }
@@ -166,7 +176,7 @@
 Object* Space::AllocWithGrowth(size_t num_bytes) {
   DCHECK(mspace_ != NULL);
   // Grow as much as possible within the mspace.
-  size_t max_allowed = maximum_size_;
+  size_t max_allowed = growth_size_;
   mspace_set_max_allowed_footprint(mspace_, max_allowed);
   // Try the allocation.
   void* ptr = AllocWithoutGrowth(num_bytes);
diff --git a/src/space.h b/src/space.h
index 7ebc654..84d679a 100644
--- a/src/space.h
+++ b/src/space.h
@@ -37,8 +37,8 @@
   // base address is not guaranteed to be granted, if it is required,
   // the caller should call GetBase on the returned space to confirm
   // the request was granted.
-  static Space* Create(const std::string& name,
-      size_t initial_size, size_t maximum_size, byte* requested_base);
+  static Space* Create(const std::string& name, size_t initial_size,
+      size_t maximum_size, size_t growth_size, byte* requested_base);
 
   // create a Space from an image file. cannot be used for future allocation or collected.
   static Space* CreateFromImage(const std::string& image);
@@ -63,7 +63,7 @@
   }
 
   byte* GetLimit() const {
-    return limit_;
+    return growth_limit_;
   }
 
   const std::string& GetName() const {
@@ -71,7 +71,7 @@
   }
 
   size_t Size() const {
-    return limit_ - base_;
+    return growth_limit_ - base_;
   }
 
   bool IsImageSpace() const {
@@ -90,6 +90,13 @@
 
   size_t AllocationSize(const Object* obj);
 
+  void ClearGrowthLimit() {
+    CHECK_GE(maximum_size_, growth_size_);
+    CHECK_GE(limit_, growth_limit_);
+    growth_size_ = maximum_size_;
+    growth_limit_ = limit_;
+  }
+
  private:
   // The boundary tag overhead.
   static const size_t kChunkOverhead = kWordSize;
@@ -98,11 +105,12 @@
   static Space* Create(MemMap* mem_map);
 
   explicit Space(const std::string& name)
-      : name_(name), mspace_(NULL), maximum_size_(0), image_header_(NULL), base_(0), limit_(0) {
+      : name_(name), mspace_(NULL), maximum_size_(0), growth_size_(0),
+        image_header_(NULL), base_(0), limit_(0), growth_limit_(0) {
   }
 
   // Initializes the space and underlying storage.
-  bool Init(size_t initial_size, size_t maximum_size, byte* requested_base);
+  bool Init(size_t initial_size, size_t maximum_size, size_t growth_size, byte* requested_base);
 
   // Initializes the space from existing storage, taking ownership of the storage.
   void Init(MemMap* map);
@@ -119,6 +127,7 @@
   // TODO: have a Space subclass for non-image Spaces with mspace_ and maximum_size_
   void* mspace_;
   size_t maximum_size_;
+  size_t growth_size_;
 
   // TODO: have a Space subclass for image Spaces with image_header_
   ImageHeader* image_header_;
@@ -126,8 +135,8 @@
   UniquePtr<MemMap> mem_map_;
 
   byte* base_;
-
   byte* limit_;
+  byte* growth_limit_;
 
   DISALLOW_COPY_AND_ASSIGN(Space);
 };
diff --git a/src/space_test.cc b/src/space_test.cc
index 0a6067c..c54a584 100644
--- a/src/space_test.cc
+++ b/src/space_test.cc
@@ -12,24 +12,44 @@
 
 TEST_F(SpaceTest, Init) {
   {
-    // Less than
-    UniquePtr<Space> space(Space::Create("test", 16 * MB, 32 * MB, NULL));
+    // Init < max == growth
+    UniquePtr<Space> space(Space::Create("test", 16 * MB, 32 * MB, 32 * MB, NULL));
     EXPECT_TRUE(space.get() != NULL);
   }
   {
-    // Equal to
-    UniquePtr<Space> space(Space::Create("test", 16 * MB, 16 * MB, NULL));
+    // Init == max == growth
+    UniquePtr<Space> space(Space::Create("test", 16 * MB, 16 * MB, 16 * MB, NULL));
     EXPECT_TRUE(space.get() != NULL);
   }
   {
-    // Greater than
-    UniquePtr<Space> space(Space::Create("test", 32 * MB, 16 * MB, NULL));
+    // Init > max == growth
+    UniquePtr<Space> space(Space::Create("test", 32 * MB, 16 * MB, 16 * MB, NULL));
+    EXPECT_TRUE(space.get() == NULL);
+  }
+  {
+    // Growth == init < max
+    UniquePtr<Space> space(Space::Create("test", 16 * MB, 32 * MB, 16 * MB, NULL));
+    EXPECT_TRUE(space.get() != NULL);
+  }
+  {
+    // Growth < init < max
+    UniquePtr<Space> space(Space::Create("test", 16 * MB, 32 * MB, 8 * MB, NULL));
+    EXPECT_TRUE(space.get() == NULL);
+  }
+  {
+    // Init < growth < max
+    UniquePtr<Space> space(Space::Create("test", 8 * MB, 32 * MB, 16 * MB, NULL));
+    EXPECT_TRUE(space.get() != NULL);
+  }
+  {
+    // Init < max < growth
+    UniquePtr<Space> space(Space::Create("test", 8 * MB, 16 * MB, 32 * MB, NULL));
     EXPECT_TRUE(space.get() == NULL);
   }
 }
 
 TEST_F(SpaceTest, AllocAndFree) {
-  UniquePtr<Space> space(Space::Create("test", 4 * MB, 16 * MB, NULL));
+  UniquePtr<Space> space(Space::Create("test", 4 * MB, 16 * MB, 16 * MB, NULL));
   ASSERT_TRUE(space.get() != NULL);
 
   // Succeeds, fits without adjusting the max allowed footprint.
diff --git a/test/GrowthLimit/GrowthLimit.java b/test/GrowthLimit/GrowthLimit.java
new file mode 100644
index 0000000..32537d6
--- /dev/null
+++ b/test/GrowthLimit/GrowthLimit.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class GrowthLimit {
+
+    public static void main(String[] args) throws Exception {
+
+        int alloc1 = 1;
+        try {
+            List<byte[]> l = new ArrayList<byte[]>();
+            while (true) {
+                // Allocate a MB at a time
+                l.add(new byte[1048576]);
+                alloc1++;
+            }
+        } catch (OutOfMemoryError e) {
+        }
+        // Expand the heap to the maximum size
+        dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
+
+        int alloc2 = 1;
+        try {
+            List<byte[]> l = new ArrayList<byte[]>();
+            while (true) {
+                // Allocate a MB at a time
+                l.add(new byte[1048576]);
+                alloc2++;
+            }
+        } catch (OutOfMemoryError e2) {
+            if (alloc1 > alloc2) {
+                System.out.println("ERROR: Allocated less memory after growth" +
+                    "limit cleared (" + alloc1 + " MBs > " + alloc2 + " MBs");
+                System.exit(1);
+            }
+        }
+    }
+}