< 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 >