< prev index next >

test/hotspot/gtest/metaspace/test_metaspacearena.cpp

Print this page
rev 60811 : imported patch jep387-all.patch
rev 60812 : [mq]: diff1

@@ -1,8 +1,8 @@
 /*
- * Copyright (c) 2018, 2020, Oracle and/or its affiliates. All rights reserved.
- * Copyright (c) 2018, 2020 SAP SE. All rights reserved.
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2020 SAP SE. 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
  * under the terms of the GNU General Public License version 2 only, as
  * published by the Free Software Foundation.

@@ -23,67 +23,99 @@
  *
  */
 
 #include "precompiled.hpp"
 
-//#define LOG_PLEASE
-
-#include "metaspace/metaspaceTestsCommon.hpp"
-#include "metaspace/metaspaceTestContexts.hpp"
-#include "metaspace/metaspace_sparsearray.hpp"
-#include "utilities/ostream.hpp"
+#include "memory/metaspace/msArena.hpp"
+#include "memory/metaspace/msArenaGrowthPolicy.hpp"
+#include "memory/metaspace/msCommitLimiter.hpp"
+#include "memory/metaspace/msCounter.hpp"
+#include "memory/metaspace/msInternalStats.hpp"
+#include "memory/metaspace/msSettings.hpp"
+#include "memory/metaspace/msStatistics.hpp"
+#include "runtime/mutex.hpp"
+#include "runtime/mutexLocker.hpp"
+#include "utilities/debug.hpp"
+#include "utilities/globalDefinitions.hpp"
 
+//#define LOG_PLEASE
+#include "metaspaceGtestCommon.hpp"
+#include "metaspaceGtestContexts.hpp"
+#include "metaspaceGtestRangeHelpers.hpp"
+
+using metaspace::ArenaGrowthPolicy;
+using metaspace::CommitLimiter;
+using metaspace::InternalStats;
+using metaspace::MemRangeCounter;
+using metaspace::MetaspaceArena;
+using metaspace::SizeAtomicCounter;
+using metaspace::Settings;
+using metaspace::ArenaStats;
+
+// See metaspaceArena.cpp : needed for predicting commit sizes.
+namespace metaspace {
+  extern size_t get_raw_word_size_for_requested_word_size(size_t net_word_size);
+}
 
-// TODO: this class is very similar to MetaspaceArenaTestBed in test_metaspacearena_stress.cpp.
-// should be unified.
 class MetaspaceArenaTestHelper {
 
-  MetaspaceTestContext& _helper;
+  MetaspaceGtestContext& _context;
 
   Mutex* _lock;
   const ArenaGrowthPolicy* _growth_policy;
   SizeAtomicCounter _used_words_counter;
   MetaspaceArena* _arena;
 
-public:
-
-  MetaspaceArenaTestHelper(MetaspaceTestContext& helper, Metaspace::MetaspaceType space_type, bool is_class,
-                         const char* name = "gtest-MetaspaceArena")
-    : _helper(helper),
-      _lock(NULL),
-      _growth_policy(NULL),
-      _used_words_counter(),
-      _arena(NULL)
-  {
-    _growth_policy = ArenaGrowthPolicy::policy_for_space_type(space_type, is_class);
+  void initialize(const ArenaGrowthPolicy* growth_policy, const char* name = "gtest-MetaspaceArena") {
+    _growth_policy = growth_policy;
     _lock = new Mutex(Monitor::native, "gtest-MetaspaceArenaTest-lock", false, Monitor::_safepoint_check_never);
     // Lock during space creation, since this is what happens in the VM too
     //  (see ClassLoaderData::metaspace_non_null(), which we mimick here).
     {
       MutexLocker ml(_lock,  Mutex::_no_safepoint_check_flag);
-      _arena = new MetaspaceArena(&_helper.cm(), _growth_policy, _lock, &_used_words_counter, name);
+      _arena = new MetaspaceArena(&_context.cm(), _growth_policy, _lock, &_used_words_counter, name);
+    }
+    DEBUG_ONLY(_arena->verify());
+
+  }
+
+public:
+
+  // Create a helper; growth policy for arena is determined by the given spacetype|class tupel
+  MetaspaceArenaTestHelper(MetaspaceGtestContext& helper,
+                            Metaspace::MetaspaceType space_type, bool is_class,
+                            const char* name = "gtest-MetaspaceArena")
+    :_context(helper)
+  {
+    initialize(ArenaGrowthPolicy::policy_for_space_type(space_type, is_class), name);
     }
-    DEBUG_ONLY(_arena->verify(true));
+
+  // Create a helper; growth policy is directly specified
+  MetaspaceArenaTestHelper(MetaspaceGtestContext& helper, const ArenaGrowthPolicy* growth_policy,
+                            const char* name = "gtest-MetaspaceArena")
+    :_context(helper)
+  {
+    initialize(growth_policy, name);
   }
 
   ~MetaspaceArenaTestHelper() {
     delete_arena_with_tests();
     delete _lock;
   }
 
-  const CommitLimiter& limiter() const { return _helper.commit_limiter(); }
+  const CommitLimiter& limiter() const { return _context.commit_limiter(); }
   MetaspaceArena* arena() const { return _arena; }
   SizeAtomicCounter& used_words_counter() { return _used_words_counter; }
 
   // Note: all test functions return void due to gtests limitation that we cannot use ASSERT
   // in non-void returning tests.
 
   void delete_arena_with_tests() {
     if (_arena != NULL) {
       size_t used_words_before = _used_words_counter.get();
       size_t committed_words_before = limiter().committed_words();
-      DEBUG_ONLY(_arena->verify(true));
+      DEBUG_ONLY(_arena->verify());
       delete _arena;
       _arena = NULL;
       size_t used_words_after = _used_words_counter.get();
       size_t committed_words_after = limiter().committed_words();
       ASSERT_0(used_words_after);

@@ -137,11 +169,11 @@
 
     size_t possible_expansion = limiter().possible_expansion_words();
 
     MetaWord* p = _arena->allocate(word_size);
 
-    SOMETIMES(DEBUG_ONLY(_arena->verify(true);))
+    SOMETIMES(DEBUG_ONLY(_arena->verify();))
 
     size_t used2 = 0, committed2 = 0, capacity2 = 0;
     usage_numbers_with_test(&used2, &committed2, &capacity2);
 
     if (p == NULL) {

@@ -174,18 +206,17 @@
   void allocate_from_arena_with_tests(size_t word_size) {
     MetaWord* dummy = NULL;
     allocate_from_arena_with_tests(&dummy, word_size);
   }
 
-
   void deallocate_with_tests(MetaWord* p, size_t word_size) {
     size_t used = 0, committed = 0, capacity = 0;
     usage_numbers_with_test(&used, &committed, &capacity);
 
     _arena->deallocate(p, word_size);
 
-    SOMETIMES(DEBUG_ONLY(_arena->verify(true);))
+    SOMETIMES(DEBUG_ONLY(_arena->verify();))
 
     size_t used2 = 0, committed2 = 0, capacity2 = 0;
     usage_numbers_with_test(&used2, &committed2, &capacity2);
 
     // Nothing should have changed. Deallocated blocks are added to the free block list

@@ -193,17 +224,26 @@
     ASSERT_EQ(used2, used);
     ASSERT_EQ(committed2, committed);
     ASSERT_EQ(capacity2, capacity);
   }
 
+  ArenaStats get_arena_statistics() const {
+    ArenaStats stats;
+    _arena->add_to_statistics(&stats);
+    return stats;
+  }
 
-};
+  // Convenience method to return number of chunks in arena (including current chunk)
+  int get_number_of_chunks() const {
+    return get_arena_statistics().totals()._num;
+  }
 
+};
 
 static void test_basics(size_t commit_limit, bool is_micro) {
-  MetaspaceTestContext msthelper(commit_limit);
-  MetaspaceArenaTestHelper helper(msthelper, is_micro ? Metaspace::ReflectionMetaspaceType : Metaspace::StandardMetaspaceType, false);
+  MetaspaceGtestContext context(commit_limit);
+  MetaspaceArenaTestHelper helper(context, is_micro ? Metaspace::ReflectionMetaspaceType : Metaspace::StandardMetaspaceType, false);
 
   helper.allocate_from_arena_with_tests(1);
   helper.allocate_from_arena_with_tests(128);
   helper.allocate_from_arena_with_tests(128 * K);
   helper.allocate_from_arena_with_tests(1);

@@ -225,75 +265,171 @@
 
 TEST_VM(metaspace, MetaspaceArena_basics_standard_limit) {
   test_basics(256 * K, false);
 }
 
+// Test chunk enlargement:
+//  A single MetaspaceArena, left undisturbed with place to grow. Slowly fill arena up.
+//  We should see at least some occurrences of chunk-in-place enlargement.
+static void test_chunk_enlargment_simple(Metaspace::MetaspaceType spacetype, bool is_class) {
+
+  MetaspaceGtestContext context;
+  MetaspaceArenaTestHelper helper(context, (Metaspace::MetaspaceType)spacetype, is_class);
+
+  uint64_t n1 = metaspace::InternalStats::num_chunks_enlarged();
+
+  size_t allocated = 0;
+  while (allocated <= MAX_CHUNK_WORD_SIZE &&
+         metaspace::InternalStats::num_chunks_enlarged() == n1) {
+    size_t s = IntRange(32, 128).random_value();
+    helper.allocate_from_arena_with_tests_expect_success(s);
+    allocated += metaspace::get_raw_word_size_for_requested_word_size(s);
+  }
+
+  EXPECT_GT(metaspace::InternalStats::num_chunks_enlarged(), n1);
+
+}
+
+// Do this test for some of the standard types; don't do it for the boot loader type
+//  since that one starts out with max chunk size so we would not see any enlargement.
 
-// Test: in a single undisturbed MetaspaceArena (so, we should have chunks enlarged in place)
-// we allocate a small amount, then the full amount possible. The sum of first and second
-// allocation bring us above root chunk size. This should work - chunk enlargement should
-// fail and a new root chunk should be allocated instead.
-TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place) {
+TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_standard_c) {
+  test_chunk_enlargment_simple(Metaspace::StandardMetaspaceType, true);
+}
+
+TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_standard_nc) {
+  test_chunk_enlargment_simple(Metaspace::StandardMetaspaceType, false);
+}
+
+TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_micro_c) {
+  test_chunk_enlargment_simple(Metaspace::ReflectionMetaspaceType, true);
+}
+
+TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_micro_nc) {
+  test_chunk_enlargment_simple(Metaspace::ReflectionMetaspaceType, false);
+}
+
+// Test chunk enlargement:
+// A single MetaspaceArena, left undisturbed with place to grow. Slowly fill arena up.
+//  We should see occurrences of chunk-in-place enlargement.
+//  Here, we give it an ideal policy which should enable the initial chunk to grow unmolested
+//  until finish.
+TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_2) {
 
   if (Settings::use_allocation_guard()) {
     return;
   }
 
-  MetaspaceTestContext msthelper;
-  MetaspaceArenaTestHelper helper(msthelper, Metaspace::StandardMetaspaceType, false);
-  helper.allocate_from_arena_with_tests_expect_success(1);
-  helper.allocate_from_arena_with_tests_expect_success(MAX_CHUNK_WORD_SIZE);
-  helper.allocate_from_arena_with_tests_expect_success(MAX_CHUNK_WORD_SIZE / 2);
-  helper.allocate_from_arena_with_tests_expect_success(MAX_CHUNK_WORD_SIZE);
+  // Note: internally, chunk in-place enlargement is disallowed if growing the chunk
+  //  would cause the arena to claim more memory than its growth policy allows. This
+  //  is done to prevent the arena to grow too fast.
+  //
+  // In order to test in-place growth here without that restriction I give it an
+  //  artificial growth policy which starts out with a tiny chunk size, then balloons
+  //  right up to max chunk size. This will cause the initial chunk to be tiny, and
+  //  then the arena is able to grow it without violating growth policy.
+  chunklevel_t growth[] = { HIGHEST_CHUNK_LEVEL, ROOT_CHUNK_LEVEL };
+  ArenaGrowthPolicy growth_policy(growth, 2);
+
+  MetaspaceGtestContext context;
+  MetaspaceArenaTestHelper helper(context, &growth_policy);
+
+  uint64_t n1 = metaspace::InternalStats::num_chunks_enlarged();
+
+  size_t allocated = 0;
+  while (allocated <= MAX_CHUNK_WORD_SIZE) {
+    size_t s = IntRange(32, 128).random_value();
+    helper.allocate_from_arena_with_tests_expect_success(s);
+    allocated += metaspace::get_raw_word_size_for_requested_word_size(s);
+    if (allocated <= MAX_CHUNK_WORD_SIZE) {
+      // Chunk should have been enlarged in place
+      ASSERT_EQ(1, helper.get_number_of_chunks());
+    } else {
+      // Next chunk should have started
+      ASSERT_EQ(2, helper.get_number_of_chunks());
+    }
+  }
+
+  int times_chunk_were_enlarged = metaspace::InternalStats::num_chunks_enlarged() - n1;
+  LOG("chunk was enlarged %d times.", times_chunk_were_enlarged);
+
+  ASSERT_GT0(times_chunk_were_enlarged);
+
 }
 
-// Test allocating from smallest to largest chunk size, and one step beyond.
-// The first n allocations should happen in place, the ladder should open a new chunk.
-TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_ladder_1) {
+// Regression test: Given a single MetaspaceArena, left undisturbed with place to grow,
+//  test that in place enlargement correctly fails if growing the chunk would bring us
+//  beyond the max. size of a chunk.
+TEST_VM(metaspace, MetaspaceArena_test_failing_to_enlarge_in_place_max_chunk_size) {
 
   if (Settings::use_allocation_guard()) {
     return;
   }
 
-  MetaspaceTestContext msthelper;
-  MetaspaceArenaTestHelper helper(msthelper, Metaspace::StandardMetaspaceType, false);
-  size_t size = MIN_CHUNK_WORD_SIZE;
-  while (size <= MAX_CHUNK_WORD_SIZE) {
-    helper.allocate_from_arena_with_tests_expect_success(size);
-    size *= 2;
-  }
-  helper.allocate_from_arena_with_tests_expect_success(MAX_CHUNK_WORD_SIZE);
+  MetaspaceGtestContext context;
+
+  for (size_t first_allocation_size = 1; first_allocation_size <= MAX_CHUNK_WORD_SIZE / 2; first_allocation_size *= 2) {
+
+    MetaspaceArenaTestHelper helper(context, Metaspace::StandardMetaspaceType, false);
+
+    // we allocate first a small amount, then the full amount possible.
+    // The sum of first and second allocation should bring us above root chunk size.
+    // This should work, we should not see any problems, but no chunk enlargement should
+    // happen.
+    int n1 = metaspace::InternalStats::num_chunks_enlarged();
+
+    helper.allocate_from_arena_with_tests_expect_success(first_allocation_size);
+    EXPECT_EQ(helper.get_number_of_chunks(), 1);
+
+    helper.allocate_from_arena_with_tests_expect_success(MAX_CHUNK_WORD_SIZE - first_allocation_size + 1);
+    EXPECT_EQ(helper.get_number_of_chunks(), 2);
+
+    int times_chunk_were_enlarged = metaspace::InternalStats::num_chunks_enlarged() - n1;
+    LOG("chunk was enlarged %d times.", times_chunk_were_enlarged);
+
+    EXPECT_0(times_chunk_were_enlarged);
+
+  }
 }
 
-// Same as MetaspaceArena_test_enlarge_in_place_ladder_1, but increase in *4 step size;
-// this way chunk-in-place-enlargement does not work and we should have new chunks at each allocation.
-TEST_VM(metaspace, MetaspaceArena_test_enlarge_in_place_ladder_2) {
+// Regression test: Given a single MetaspaceArena, left undisturbed with place to grow,
+//  test that in place enlargement correctly fails if growing the chunk would cause more
+//  than doubling its size
+TEST_VM(metaspace, MetaspaceArena_test_failing_to_enlarge_in_place_doubling_chunk_size) {
 
   if (Settings::use_allocation_guard()) {
     return;
   }
 
-  MetaspaceTestContext msthelper;
-  MetaspaceArenaTestHelper helper(msthelper, Metaspace::StandardMetaspaceType, false);
-  size_t size = MIN_CHUNK_WORD_SIZE;
-  while (size <= MAX_CHUNK_WORD_SIZE) {
-    helper.allocate_from_arena_with_tests_expect_success(size);
-    size *= 4;
-  }
-  helper.allocate_from_arena_with_tests_expect_success(MAX_CHUNK_WORD_SIZE);
+  MetaspaceGtestContext context;
+  MetaspaceArenaTestHelper helper(context, Metaspace::StandardMetaspaceType, false);
+
+  int n1 = metaspace::InternalStats::num_chunks_enlarged();
+
+  helper.allocate_from_arena_with_tests_expect_success(1000);
+  EXPECT_EQ(helper.get_number_of_chunks(), 1);
+
+  helper.allocate_from_arena_with_tests_expect_success(4000);
+  EXPECT_EQ(helper.get_number_of_chunks(), 2);
+
+  int times_chunk_were_enlarged = metaspace::InternalStats::num_chunks_enlarged() - n1;
+  LOG("chunk was enlarged %d times.", times_chunk_were_enlarged);
+
+  EXPECT_0(times_chunk_were_enlarged);
+
 }
 
 // Test the MetaspaceArenas' free block list:
 // Allocate, deallocate, then allocate the same block again. The second allocate should
 // reuse the deallocated block.
 TEST_VM(metaspace, MetaspaceArena_deallocate) {
   if (Settings::use_allocation_guard()) {
     return;
   }
   for (size_t s = 2; s <= MAX_CHUNK_WORD_SIZE; s *= 2) {
-    MetaspaceTestContext msthelper;
-    MetaspaceArenaTestHelper helper(msthelper, Metaspace::StandardMetaspaceType, false);
+    MetaspaceGtestContext context;
+    MetaspaceArenaTestHelper helper(context, Metaspace::StandardMetaspaceType, false);
 
     MetaWord* p1 = NULL;
     helper.allocate_from_arena_with_tests_expect_success(&p1, s);
 
     size_t used1 = 0, capacity1 = 0;

@@ -336,26 +472,26 @@
   //
   // This means if the first MetaspaceArena may have to let go of its current chunk and
   // retire it and take a fresh chunk from the freelist.
 
   const size_t commit_limit = Settings::commit_granule_words() * 10;
-  MetaspaceTestContext msthelper(commit_limit);
+  MetaspaceGtestContext context(commit_limit);
 
   // The first MetaspaceArena mimicks a micro loader. This will fill the free
   //  chunk list with very small chunks. We allocate from them in an interleaved
   //  way to cause fragmentation.
-  MetaspaceArenaTestHelper helper1(msthelper, Metaspace::ReflectionMetaspaceType, false);
-  MetaspaceArenaTestHelper helper2(msthelper, Metaspace::ReflectionMetaspaceType, false);
+  MetaspaceArenaTestHelper helper1(context, Metaspace::ReflectionMetaspaceType, false);
+  MetaspaceArenaTestHelper helper2(context, Metaspace::ReflectionMetaspaceType, false);
 
   // This MetaspaceArena should hit the limit. We use BootMetaspaceType here since
   // it gets a large initial chunk which is committed
   // on demand and we are likely to hit a commit limit while trying to expand it.
-  MetaspaceArenaTestHelper helper3(msthelper, Metaspace::BootMetaspaceType, false);
+  MetaspaceArenaTestHelper helper3(context, Metaspace::BootMetaspaceType, false);
 
   // Allocate space until we have below two but above one granule left
   size_t allocated_from_1_and_2 = 0;
-  while (msthelper.commit_limiter().possible_expansion_words() >= Settings::commit_granule_words() * 2 &&
+  while (context.commit_limiter().possible_expansion_words() >= Settings::commit_granule_words() * 2 &&
       allocated_from_1_and_2 < commit_limit) {
     helper1.allocate_from_arena_with_tests_expect_success(1);
     helper2.allocate_from_arena_with_tests_expect_success(1);
     allocated_from_1_and_2 += 2;
   }

@@ -367,29 +503,28 @@
          ++allocated_from_3 < Settings::commit_granule_words() * 2);
 
   EXPECT_LE(allocated_from_3, Settings::commit_granule_words() * 2);
 
   // We expect the freelist to be empty of committed space...
-  EXPECT_0(msthelper.cm().total_committed_word_size());
+  EXPECT_0(context.cm().total_committed_word_size());
 
   //msthelper.cm().print_on(tty);
 
   // Release the first MetaspaceArena.
   helper1.delete_arena_with_tests();
 
   //msthelper.cm().print_on(tty);
 
   // Should have populated the freelist with committed space
   // We expect the freelist to be empty of committed space...
-  EXPECT_GT(msthelper.cm().total_committed_word_size(), (size_t)0);
+  EXPECT_GT(context.cm().total_committed_word_size(), (size_t)0);
 
   // Repeat allocation from helper3, should now work.
   helper3.allocate_from_arena_with_tests_expect_success(1);
 
 }
 
-
 TEST_VM(metaspace, MetaspaceArena_recover_from_limit_hit) {
   test_recover_from_commit_limit_hit();
 }
 
 static void test_controlled_growth(Metaspace::MetaspaceType type, bool is_class,

@@ -404,14 +539,14 @@
   // From a MetaspaceArena in a clean room allocate tiny amounts;
   // watch it grow. Used/committed/capacity should not grow in
   // large jumps. Also, different types of MetaspaceArena should
   // have different initial capacities.
 
-  MetaspaceTestContext msthelper;
-  MetaspaceArenaTestHelper smhelper(msthelper, type, is_class, "Grower");
+  MetaspaceGtestContext context;
+  MetaspaceArenaTestHelper smhelper(context, type, is_class, "Grower");
 
-  MetaspaceArenaTestHelper smhelper_harrasser(msthelper, Metaspace::ReflectionMetaspaceType, true, "Harasser");
+  MetaspaceArenaTestHelper smhelper_harrasser(context, Metaspace::ReflectionMetaspaceType, true, "Harasser");
 
   size_t used = 0, committed = 0, capacity = 0;
   const size_t alloc_words = 16;
 
   smhelper.arena()->usage_numbers(&used, &committed, &capacity);

@@ -431,11 +566,11 @@
 
   ASSERT_EQ(capacity, expected_starting_capacity);
 
   if (!(Settings::new_chunks_are_fully_committed() && type == Metaspace::BootMetaspaceType)) {
     // Initial commit charge for the whole context should be one granule
-    ASSERT_EQ(msthelper.committed_words(), Settings::commit_granule_words());
+    ASSERT_EQ(context.committed_words(), Settings::commit_granule_words());
     // Initial commit number for the arena should be less since - apart from boot loader - no
     //  space type has large initial chunks.
     ASSERT_LE(committed, Settings::commit_granule_words());
   }
 

@@ -459,12 +594,12 @@
     if (!test_in_place_enlargement) {
       smhelper_harrasser.allocate_from_arena_with_tests_expect_success(alloc_words * 2);
     }
 
     smhelper.allocate_from_arena_with_tests_expect_success(alloc_words);
-    words_allocated += alloc_words;
-    num_allocated ++;
+    words_allocated += metaspace::get_raw_word_size_for_requested_word_size(alloc_words);
+    num_allocated++;
 
     size_t used2 = 0, committed2 = 0, capacity2 = 0;
 
     smhelper.arena()->usage_numbers(&used2, &committed2, &capacity2);
 

@@ -499,11 +634,11 @@
         ASSERT_GE(capacity_jump, MIN_CHUNK_WORD_SIZE);
         ASSERT_LE(capacity_jump, MAX_CHUNK_WORD_SIZE);
         */
         highest_capacity_jump = capacity_jump;
       }
-      num_capacity_jumps ++;
+      num_capacity_jumps++;
     }
 
     capacity = capacity2;
 
   }
< prev index next >