8170933: Cleanup Metaspace Chunk manager: Unify treatment of humongous and non-humongous chunks
authorstuefe
Wed, 14 Dec 2016 09:14:16 +0100
changeset 46333 cef2c78aaa56
parent 46332 cdb55474bc2e
child 46334 cfbf22d1d4b3
child 46335 da075aa9241f
8170933: Cleanup Metaspace Chunk manager: Unify treatment of humongous and non-humongous chunks Reviewed-by: mgerdin, coleenp
hotspot/src/share/vm/memory/metaspace.cpp
hotspot/test/native/memory/test_chunkManager.cpp
--- a/hotspot/src/share/vm/memory/metaspace.cpp	Thu Mar 16 09:43:26 2017 +0000
+++ b/hotspot/src/share/vm/memory/metaspace.cpp	Wed Dec 14 09:14:16 2016 +0100
@@ -74,6 +74,22 @@
   NumberOfInUseLists = 4
 };
 
+// Helper, returns a descriptive name for the given index.
+static const char* chunk_size_name(ChunkIndex index) {
+  switch (index) {
+    case SpecializedIndex:
+      return "specialized";
+    case SmallIndex:
+      return "small";
+    case MediumIndex:
+      return "medium";
+    case HumongousIndex:
+      return "humongous";
+    default:
+      return "Invalid index";
+  }
+}
+
 enum ChunkSizes {    // in words.
   ClassSpecializedChunk = 128,
   SpecializedChunk = 128,
@@ -102,20 +118,32 @@
   //   SpecializedChunk
   //   SmallChunk
   //   MediumChunk
-  //   HumongousChunk
   ChunkList _free_chunks[NumberOfFreeLists];
 
+  // Return non-humongous chunk list by its index.
+  ChunkList* free_chunks(ChunkIndex index);
+
+  // Returns non-humongous chunk list for the given chunk word size.
+  ChunkList* find_free_chunks_list(size_t word_size);
+
   //   HumongousChunk
   ChunkTreeDictionary _humongous_dictionary;
 
+  // Returns the humongous chunk dictionary.
+  ChunkTreeDictionary* humongous_dictionary() {
+    return &_humongous_dictionary;
+  }
+
   // ChunkManager in all lists of this type
   size_t _free_chunks_total;
   size_t _free_chunks_count;
 
   void dec_free_chunks_total(size_t v) {
-    assert(_free_chunks_count > 0 &&
-             _free_chunks_total > 0,
-             "About to go negative");
+    assert(_free_chunks_count > 0,
+      "ChunkManager::_free_chunks_count: about to go negative (" SIZE_FORMAT ").", _free_chunks_count);
+    assert(_free_chunks_total >= v,
+      "ChunkManager::_free_chunks_total: about to go negative"
+       "(now: " SIZE_FORMAT ", decrement value: " SIZE_FORMAT ").", _free_chunks_total, v);
     Atomic::add_ptr(-1, &_free_chunks_count);
     jlong minus_v = (jlong) - (jlong) v;
     Atomic::add_ptr(minus_v, &_free_chunks_total);
@@ -156,13 +184,20 @@
   // for special, small, medium, and humongous chunks.
   ChunkIndex list_index(size_t size);
 
-  // Remove the chunk from its freelist.  It is
-  // expected to be on one of the _free_chunks[] lists.
+  // Map a given index to the chunk size.
+  size_t size_by_index(ChunkIndex index);
+
+  // Take a chunk from the ChunkManager. The chunk is expected to be in
+  // the chunk manager (the freelist if non-humongous, the dictionary if
+  // humongous).
   void remove_chunk(Metachunk* chunk);
 
+  // Return a single chunk of type index to the ChunkManager.
+  void return_single_chunk(ChunkIndex index, Metachunk* chunk);
+
   // Add the simple linked list of chunks to the freelist of chunks
   // of type index.
-  void return_chunks(ChunkIndex index, Metachunk* chunks);
+  void return_chunk_list(ChunkIndex index, Metachunk* chunk);
 
   // Total of the space in the free chunks list
   size_t free_chunks_total_words();
@@ -175,14 +210,6 @@
     Atomic::add_ptr(count, &_free_chunks_count);
     Atomic::add_ptr(v, &_free_chunks_total);
   }
-  ChunkTreeDictionary* humongous_dictionary() {
-    return &_humongous_dictionary;
-  }
-
-  ChunkList* free_chunks(ChunkIndex index);
-
-  // Returns the list for the given chunk word size.
-  ChunkList* find_free_chunks_list(size_t word_size);
 
   // Remove from a list by size.  Selects list based on size of chunk.
   Metachunk* free_chunks_get(size_t chunk_word_size);
@@ -735,8 +762,6 @@
 
   Mutex* lock() const { return _lock; }
 
-  const char* chunk_size_name(ChunkIndex index) const;
-
  protected:
   void initialize();
 
@@ -1246,13 +1271,13 @@
   DEBUG_ONLY(verify_container_count();)
   for (int i = (int)MediumIndex; i >= (int)ZeroIndex; --i) {
     ChunkIndex index = (ChunkIndex)i;
-    size_t chunk_size = chunk_manager->free_chunks(index)->size();
+    size_t chunk_size = chunk_manager->size_by_index(index);
 
     while (free_words_in_vs() >= chunk_size) {
       Metachunk* chunk = get_chunk_vs(chunk_size);
       assert(chunk != NULL, "allocation should have been successful");
 
-      chunk_manager->return_chunks(index, chunk);
+      chunk_manager->return_single_chunk(index, chunk);
       chunk_manager->inc_free_chunks_total(chunk_size);
     }
     DEBUG_ONLY(verify_container_count();)
@@ -1743,6 +1768,28 @@
   return _free_chunks_count;
 }
 
+ChunkIndex ChunkManager::list_index(size_t size) {
+  if (size_by_index(SpecializedIndex) == size) {
+    return SpecializedIndex;
+  }
+  if (size_by_index(SmallIndex) == size) {
+    return SmallIndex;
+  }
+  const size_t med_size = size_by_index(MediumIndex);
+  if (med_size == size) {
+    return MediumIndex;
+  }
+
+  assert(size > med_size, "Not a humongous chunk");
+  return HumongousIndex;
+}
+
+size_t ChunkManager::size_by_index(ChunkIndex index) {
+  index_bounds_check(index);
+  assert(index != HumongousIndex, "Do not call for humongous chunks.");
+  return free_chunks(index)->size();
+}
+
 void ChunkManager::locked_verify_free_chunks_total() {
   assert_lock_strong(SpaceManager::expand_lock());
   assert(sum_free_chunks() == _free_chunks_total,
@@ -1923,6 +1970,73 @@
   return chunk;
 }
 
+void ChunkManager::return_single_chunk(ChunkIndex index, Metachunk* chunk) {
+  assert_lock_strong(SpaceManager::expand_lock());
+  assert(chunk != NULL, "Expected chunk.");
+  assert(chunk->container() != NULL, "Container should have been set.");
+  assert(chunk->is_tagged_free() == false, "Chunk should be in use.");
+  index_bounds_check(index);
+
+  // Note: mangle *before* returning the chunk to the freelist or dictionary. It does not
+  // matter for the freelist (non-humongous chunks), but the humongous chunk dictionary
+  // keeps tree node pointers in the chunk payload area which mangle will overwrite.
+  NOT_PRODUCT(chunk->mangle(badMetaWordVal);)
+
+  if (index != HumongousIndex) {
+    // Return non-humongous chunk to freelist.
+    ChunkList* list = free_chunks(index);
+    assert(list->size() == chunk->word_size(), "Wrong chunk type.");
+    list->return_chunk_at_head(chunk);
+    log_trace(gc, metaspace, freelist)("returned one %s chunk at " PTR_FORMAT " to freelist.",
+        chunk_size_name(index), p2i(chunk));
+  } else {
+    // Return humongous chunk to dictionary.
+    assert(chunk->word_size() > free_chunks(MediumIndex)->size(), "Wrong chunk type.");
+    assert(chunk->word_size() % free_chunks(SpecializedIndex)->size() == 0,
+           "Humongous chunk has wrong alignment.");
+    _humongous_dictionary.return_chunk(chunk);
+    log_trace(gc, metaspace, freelist)("returned one %s chunk at " PTR_FORMAT " (word size " SIZE_FORMAT ") to freelist.",
+        chunk_size_name(index), p2i(chunk), chunk->word_size());
+  }
+  chunk->container()->dec_container_count();
+  DEBUG_ONLY(chunk->set_is_tagged_free(true);)
+
+}
+
+void ChunkManager::return_chunk_list(ChunkIndex index, Metachunk* chunks) {
+  index_bounds_check(index);
+  if (chunks == NULL) {
+    return;
+  }
+  LogTarget(Trace, gc, metaspace, freelist) log;
+  if (log.is_enabled()) { // tracing
+    log.print("returning list of %s chunks...", chunk_size_name(index));
+  }
+  unsigned num_chunks_returned = 0;
+  size_t size_chunks_returned = 0;
+  Metachunk* cur = chunks;
+  while (cur != NULL) {
+    // Capture the next link before it is changed
+    // by the call to return_chunk_at_head();
+    Metachunk* next = cur->next();
+    if (log.is_enabled()) { // tracing
+      num_chunks_returned ++;
+      size_chunks_returned += cur->word_size();
+    }
+    return_single_chunk(index, cur);
+    cur = next;
+  }
+  if (log.is_enabled()) { // tracing
+    log.print("returned %u %s chunks to freelist, total word size " SIZE_FORMAT ".",
+        num_chunks_returned, chunk_size_name(index), size_chunks_returned);
+    if (index != HumongousIndex) {
+      log.print("updated freelist count: " SIZE_FORMAT ".", free_chunks(index)->size());
+    } else {
+      log.print("updated dictionary count " SIZE_FORMAT ".", _humongous_dictionary.total_count());
+    }
+  }
+}
+
 void ChunkManager::print_on(outputStream* out) const {
   const_cast<ChunkManager *>(this)->humongous_dictionary()->report_statistics(out);
 }
@@ -2256,32 +2370,6 @@
   log_trace(gc, metaspace, freelist)("SpaceManager(): " PTR_FORMAT, p2i(this));
 }
 
-void ChunkManager::return_chunks(ChunkIndex index, Metachunk* chunks) {
-  if (chunks == NULL) {
-    return;
-  }
-  ChunkList* list = free_chunks(index);
-  assert(list->size() == chunks->word_size(), "Mismatch in chunk sizes");
-  assert_lock_strong(SpaceManager::expand_lock());
-  Metachunk* cur = chunks;
-
-  // This returns chunks one at a time.  If a new
-  // class List can be created that is a base class
-  // of FreeList then something like FreeList::prepend()
-  // can be used in place of this loop
-  while (cur != NULL) {
-    assert(cur->container() != NULL, "Container should have been set");
-    cur->container()->dec_container_count();
-    // Capture the next link before it is changed
-    // by the call to return_chunk_at_head();
-    Metachunk* next = cur->next();
-    DEBUG_ONLY(cur->set_is_tagged_free(true);)
-    NOT_PRODUCT(cur->mangle(badMetaWordVal);)
-    list->return_chunk_at_head(cur);
-    cur = next;
-  }
-}
-
 SpaceManager::~SpaceManager() {
   // This call this->_lock which can't be done while holding expand_lock()
   assert(sum_capacity_in_chunks_in_use() == allocated_chunks_words(),
@@ -2317,42 +2405,12 @@
   // Follow each list of chunks-in-use and add them to the
   // free lists.  Each list is NULL terminated.
 
-  for (ChunkIndex i = ZeroIndex; i < HumongousIndex; i = next_chunk_index(i)) {
-    log.trace("returned " SIZE_FORMAT " %s chunks to freelist", sum_count_in_chunks_in_use(i), chunk_size_name(i));
+  for (ChunkIndex i = ZeroIndex; i <= HumongousIndex; i = next_chunk_index(i)) {
     Metachunk* chunks = chunks_in_use(i);
-    chunk_manager()->return_chunks(i, chunks);
+    chunk_manager()->return_chunk_list(i, chunks);
     set_chunks_in_use(i, NULL);
-    log.trace("updated freelist count " SSIZE_FORMAT " %s", chunk_manager()->free_chunks(i)->count(), chunk_size_name(i));
-    assert(i != HumongousIndex, "Humongous chunks are handled explicitly later");
   }
 
-  // The medium chunk case may be optimized by passing the head and
-  // tail of the medium chunk list to add_at_head().  The tail is often
-  // the current chunk but there are probably exceptions.
-
-  // Humongous chunks
-  log.trace("returned " SIZE_FORMAT " %s humongous chunks to dictionary",
-            sum_count_in_chunks_in_use(HumongousIndex), chunk_size_name(HumongousIndex));
-  log.trace("Humongous chunk dictionary: ");
-  // Humongous chunks are never the current chunk.
-  Metachunk* humongous_chunks = chunks_in_use(HumongousIndex);
-
-  while (humongous_chunks != NULL) {
-    DEBUG_ONLY(humongous_chunks->set_is_tagged_free(true);)
-    NOT_PRODUCT(humongous_chunks->mangle(badMetaWordVal);)
-    log.trace(PTR_FORMAT " (" SIZE_FORMAT ") ", p2i(humongous_chunks), humongous_chunks->word_size());
-    assert(humongous_chunks->word_size() == (size_t)
-           align_size_up(humongous_chunks->word_size(),
-                             smallest_chunk_size()),
-           "Humongous chunk size is wrong: word size " SIZE_FORMAT
-           " granularity " SIZE_FORMAT,
-           humongous_chunks->word_size(), smallest_chunk_size());
-    Metachunk* next_humongous_chunks = humongous_chunks->next();
-    humongous_chunks->container()->dec_container_count();
-    chunk_manager()->humongous_dictionary()->return_chunk(humongous_chunks);
-    humongous_chunks = next_humongous_chunks;
-  }
-  log.trace("updated dictionary count " SIZE_FORMAT " %s", chunk_manager()->humongous_dictionary()->total_count(), chunk_size_name(HumongousIndex));
   chunk_manager()->slow_locked_verify();
 
   if (_block_freelists != NULL) {
@@ -2360,36 +2418,6 @@
   }
 }
 
-const char* SpaceManager::chunk_size_name(ChunkIndex index) const {
-  switch (index) {
-    case SpecializedIndex:
-      return "Specialized";
-    case SmallIndex:
-      return "Small";
-    case MediumIndex:
-      return "Medium";
-    case HumongousIndex:
-      return "Humongous";
-    default:
-      return NULL;
-  }
-}
-
-ChunkIndex ChunkManager::list_index(size_t size) {
-  if (free_chunks(SpecializedIndex)->size() == size) {
-    return SpecializedIndex;
-  }
-  if (free_chunks(SmallIndex)->size() == size) {
-    return SmallIndex;
-  }
-  if (free_chunks(MediumIndex)->size() == size) {
-    return MediumIndex;
-  }
-
-  assert(size > free_chunks(MediumIndex)->size(), "Not a humongous chunk");
-  return HumongousIndex;
-}
-
 void SpaceManager::deallocate(MetaWord* p, size_t word_size) {
   assert_lock_strong(_lock);
   // Allocations and deallocations are in raw_word_size
@@ -4073,6 +4101,273 @@
   }
 }
 
+// ChunkManagerReturnTest stresses taking/returning chunks from the ChunkManager. It takes and
+// returns chunks from/to the ChunkManager while keeping track of the expected ChunkManager
+// content.
+class ChunkManagerReturnTestImpl {
+
+  VirtualSpaceNode _vsn;
+  ChunkManager _cm;
+
+  // The expected content of the chunk manager.
+  unsigned _chunks_in_chunkmanager;
+  size_t _words_in_chunkmanager;
+
+  // A fixed size pool of chunks. Chunks may be in the chunk manager (free) or not (in use).
+  static const int num_chunks = 256;
+  Metachunk* _pool[num_chunks];
+
+  // Helper, return a random position into the chunk pool.
+  static int get_random_position() {
+    return os::random() % num_chunks;
+  }
+
+  // Asserts that ChunkManager counters match expectations.
+  void assert_counters() {
+    assert(_vsn.container_count() == num_chunks - _chunks_in_chunkmanager, "vsn counter mismatch.");
+    assert(_cm.free_chunks_count() == _chunks_in_chunkmanager, "cm counter mismatch.");
+    assert(_cm.free_chunks_total_words() == _words_in_chunkmanager, "cm counter mismatch.");
+  }
+
+  // Get a random chunk size. Equal chance to get spec/med/small chunk size or
+  // a humongous chunk size. The latter itself is random in the range of [med+spec..4*med).
+  size_t get_random_chunk_size() {
+    const size_t sizes [] = { SpecializedChunk, SmallChunk, MediumChunk };
+    const int rand = os::random() % 4;
+    if (rand < 3) {
+      return sizes[rand];
+    } else {
+      // Note: this affects the max. size of space (see _vsn initialization in ctor).
+      return align_size_up(MediumChunk + 1 + (os::random() % (MediumChunk * 4)), SpecializedChunk);
+    }
+  }
+
+  // Starting at pool index <start>+1, find the next chunk tagged as either free or in use, depending
+  // on <is_free>. Search wraps. Returns its position, or -1 if no matching chunk was found.
+  int next_matching_chunk(int start, bool is_free) const {
+    assert(start >= 0 && start < num_chunks, "invalid parameter");
+    int pos = start;
+    do {
+      if (++pos == num_chunks) {
+        pos = 0;
+      }
+      if (_pool[pos]->is_tagged_free() == is_free) {
+        return pos;
+      }
+    } while (pos != start);
+    return -1;
+  }
+
+  // A structure to keep information about a chunk list including which
+  // chunks are part of this list. This is needed to keep information about a chunk list
+  // we will to return to the ChunkManager, because the original list will be destroyed.
+  struct AChunkList {
+    Metachunk* head;
+    Metachunk* all[num_chunks];
+    size_t size;
+    int num;
+    ChunkIndex index;
+  };
+
+  // Assemble, from the in-use chunks (not in the chunk manager) in the pool,
+  // a random chunk list of max. length <list_size> of chunks with the same
+  // ChunkIndex (chunk size).
+  // Returns false if list cannot be assembled. List is returned in the <out>
+  // structure. Returned list may be smaller than <list_size>.
+  bool assemble_random_chunklist(AChunkList* out, int list_size) {
+    // Choose a random in-use chunk from the pool...
+    const int headpos = next_matching_chunk(get_random_position(), false);
+    if (headpos == -1) {
+      return false;
+    }
+    Metachunk* const head = _pool[headpos];
+    out->all[0] = head;
+    assert(head->is_tagged_free() == false, "Chunk state mismatch");
+    // ..then go from there, chain it up with up to list_size - 1 number of other
+    // in-use chunks of the same index.
+    const ChunkIndex index = _cm.list_index(head->word_size());
+    int num_added = 1;
+    size_t size_added = head->word_size();
+    int pos = headpos;
+    Metachunk* tail = head;
+    do {
+      pos = next_matching_chunk(pos, false);
+      if (pos != headpos) {
+        Metachunk* c = _pool[pos];
+        assert(c->is_tagged_free() == false, "Chunk state mismatch");
+        if (index == _cm.list_index(c->word_size())) {
+          tail->set_next(c);
+          c->set_prev(tail);
+          tail = c;
+          out->all[num_added] = c;
+          num_added ++;
+          size_added += c->word_size();
+        }
+      }
+    } while (num_added < list_size && pos != headpos);
+    out->head = head;
+    out->index = index;
+    out->size = size_added;
+    out->num = num_added;
+    return true;
+  }
+
+  // Take a single random chunk from the ChunkManager.
+  bool take_single_random_chunk_from_chunkmanager() {
+    assert_counters();
+    _cm.locked_verify();
+    int pos = next_matching_chunk(get_random_position(), true);
+    if (pos == -1) {
+      return false;
+    }
+    Metachunk* c = _pool[pos];
+    assert(c->is_tagged_free(), "Chunk state mismatch");
+    // Note: instead of using ChunkManager::remove_chunk on this one chunk, we call
+    // ChunkManager::free_chunks_get() with this chunk's word size. We really want
+    // to exercise ChunkManager::free_chunks_get() because that one gets called for
+    // normal chunk allocation.
+    Metachunk* c2 = _cm.free_chunks_get(c->word_size());
+    assert(c2 != NULL, "Unexpected.");
+    assert(!c2->is_tagged_free(), "Chunk state mismatch");
+    assert(c2->next() == NULL && c2->prev() == NULL, "Chunk should be outside of a list.");
+    _chunks_in_chunkmanager --;
+    _words_in_chunkmanager -= c->word_size();
+    assert_counters();
+    _cm.locked_verify();
+    return true;
+  }
+
+  // Returns a single random chunk to the chunk manager. Returns false if that
+  // was not possible (all chunks are already in the chunk manager).
+  bool return_single_random_chunk_to_chunkmanager() {
+    assert_counters();
+    _cm.locked_verify();
+    int pos = next_matching_chunk(get_random_position(), false);
+    if (pos == -1) {
+      return false;
+    }
+    Metachunk* c = _pool[pos];
+    assert(c->is_tagged_free() == false, "wrong chunk information");
+    _cm.return_single_chunk(_cm.list_index(c->word_size()), c);
+    _chunks_in_chunkmanager ++;
+    _words_in_chunkmanager += c->word_size();
+    // (Note: until 8170520 is fixed, internal ChunkManager counters have to be updated
+    // by the caller - but only when returning chunks, ChunkManager->remove_chunk()
+    // already updates the counter.)
+    _cm.inc_free_chunks_total(c->word_size(), 1);
+    assert(c->is_tagged_free() == true, "wrong chunk information");
+    assert_counters();
+    _cm.locked_verify();
+    return true;
+  }
+
+  // Return a random chunk list to the chunk manager. Returns the length of the
+  // returned list.
+  int return_random_chunk_list_to_chunkmanager(int list_size) {
+    assert_counters();
+    _cm.locked_verify();
+    AChunkList aChunkList;
+    if (!assemble_random_chunklist(&aChunkList, list_size)) {
+      return 0;
+    }
+    // Before returning chunks are returned, they should be tagged in use.
+    for (int i = 0; i < aChunkList.num; i ++) {
+      assert(!aChunkList.all[i]->is_tagged_free(), "chunk state mismatch.");
+    }
+    _cm.return_chunk_list(aChunkList.index, aChunkList.head);
+    _chunks_in_chunkmanager += aChunkList.num;
+    _words_in_chunkmanager += aChunkList.size;
+    // (Note: until 8170520 is fixed, internal ChunkManager counters have to be updated
+    // by the caller - but only when returning chunks, ChunkManager->remove_chunk()
+    // already updates the counter.)
+    _cm.inc_free_chunks_total(aChunkList.size, aChunkList.num);
+    // After all chunks are returned, check that they are now tagged free.
+    for (int i = 0; i < aChunkList.num; i ++) {
+      assert(aChunkList.all[i]->is_tagged_free(), "chunk state mismatch.");
+    }
+    assert_counters();
+    _cm.locked_verify();
+    return aChunkList.num;
+  }
+
+public:
+
+  ChunkManagerReturnTestImpl()
+    : _vsn(align_size_up(MediumChunk * num_chunks * 5 * sizeof(MetaWord), Metaspace::reserve_alignment()))
+    , _cm(SpecializedChunk, SmallChunk, MediumChunk)
+    , _chunks_in_chunkmanager(0)
+    , _words_in_chunkmanager(0)
+  {
+    MutexLockerEx ml(SpaceManager::expand_lock(), Mutex::_no_safepoint_check_flag);
+    // Allocate virtual space and allocate random chunks. Keep these chunks in the _pool. These chunks are
+    // "in use", because not yet added to any chunk manager.
+    _vsn.initialize();
+    _vsn.expand_by(_vsn.reserved_words(), _vsn.reserved_words());
+    for (int i = 0; i < num_chunks; i ++) {
+      const size_t size = get_random_chunk_size();
+      _pool[i] = _vsn.get_chunk_vs(size);
+      assert(_pool[i] != NULL, "allocation failed");
+    }
+    assert_counters();
+    _cm.locked_verify();
+  }
+
+  // Test entry point.
+  // Return some chunks to the chunk manager (return phase). Take some chunks out (take phase). Repeat.
+  // Chunks are choosen randomly. Number of chunks to return or taken are choosen randomly, but affected
+  // by the <phase_length_factor> argument: a factor of 0.0 will cause the test to quickly alternate between
+  // returning and taking, whereas a factor of 1.0 will take/return all chunks from/to the
+  // chunks manager, thereby emptying or filling it completely.
+  void do_test(float phase_length_factor) {
+    MutexLockerEx ml(SpaceManager::expand_lock(), Mutex::_no_safepoint_check_flag);
+    assert_counters();
+    // Execute n operations, and operation being the move of a single chunk to/from the chunk manager.
+    const int num_max_ops = num_chunks * 100;
+    int num_ops = num_max_ops;
+    const int average_phase_length = (int)(phase_length_factor * num_chunks);
+    int num_ops_until_switch = MAX2(1, (int)(average_phase_length + os::random() % 8 - 4));
+    bool return_phase = true;
+    while (num_ops > 0) {
+      int chunks_moved = 0;
+      if (return_phase) {
+        // Randomly switch between returning a single chunk or a random length chunk list.
+        if (os::random() % 2 == 0) {
+          if (return_single_random_chunk_to_chunkmanager()) {
+            chunks_moved = 1;
+          }
+        } else {
+          const int list_length = MAX2(1, (int)(os::random() % num_ops_until_switch));
+          chunks_moved = return_random_chunk_list_to_chunkmanager(list_length);
+        }
+      } else {
+        // Breath out.
+        if (take_single_random_chunk_from_chunkmanager()) {
+          chunks_moved = 1;
+        }
+      }
+      num_ops -= chunks_moved;
+      num_ops_until_switch -= chunks_moved;
+      if (chunks_moved == 0 || num_ops_until_switch <= 0) {
+        return_phase = !return_phase;
+        num_ops_until_switch = MAX2(1, (int)(average_phase_length + os::random() % 8 - 4));
+      }
+    }
+  }
+};
+
+void* setup_chunkmanager_returntests() {
+  ChunkManagerReturnTestImpl* p = new ChunkManagerReturnTestImpl();
+  return p;
+}
+
+void teardown_chunkmanager_returntests(void* p) {
+  delete (ChunkManagerReturnTestImpl*) p;
+}
+
+void run_chunkmanager_returntests(void* p, float phase_length) {
+  ChunkManagerReturnTestImpl* test = (ChunkManagerReturnTestImpl*) p;
+  test->do_test(phase_length);
+}
 
 // The following test is placed here instead of a gtest / unittest file
 // because the ChunkManager class is only available in this file.
--- a/hotspot/test/native/memory/test_chunkManager.cpp	Thu Mar 16 09:43:26 2017 +0000
+++ b/hotspot/test/native/memory/test_chunkManager.cpp	Wed Dec 14 09:14:16 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2016, 2017 Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -34,6 +34,26 @@
   // The ChunkManager is only available in metaspace.cpp,
   // so the test code is located in that file.
   ChunkManager_test_list_index();
+
 }
 
+extern void* setup_chunkmanager_returntests();
+extern void teardown_chunkmanager_returntests(void*);
+extern void run_chunkmanager_returntests(void* p, float phase_length_factor);
+
+class ChunkManagerReturnTest : public ::testing::Test {
+protected:
+  void* _test;
+  virtual void SetUp() {
+    _test = setup_chunkmanager_returntests();
+  }
+  virtual void TearDown() {
+    teardown_chunkmanager_returntests(_test);
+  }
+};
+
+TEST_VM_F(ChunkManagerReturnTest, test00) { run_chunkmanager_returntests(_test, 0.0f); }
+TEST_VM_F(ChunkManagerReturnTest, test05) { run_chunkmanager_returntests(_test, 0.5f); }
+TEST_VM_F(ChunkManagerReturnTest, test10) { run_chunkmanager_returntests(_test, 1.0f); }
+
 #endif // ASSERT