44ec501a41
Co-authored-by: Sandhya Viswanathan <sviswanathan@openjdk.org> Co-authored-by: Martin Doerr <mdoerr@openjdk.org> Co-authored-by: Hamlin Li <mli@openjdk.org> Co-authored-by: Thomas Stuefe <stuefe@openjdk.org> Co-authored-by: Amit Kumar <amitkumar@openjdk.org> Co-authored-by: Stefan Karlsson <stefank@openjdk.org> Co-authored-by: Coleen Phillimore <coleenp@openjdk.org> Co-authored-by: Axel Boldt-Christmas <aboldtch@openjdk.org> Reviewed-by: coleenp, stefank, stuefe, phh, ihse, lmesnik, tschatzl, matsaave, rcastanedalo, vpaprotski, yzheng, egahlin
410 lines
15 KiB
C++
410 lines
15 KiB
C++
/*
|
|
* Copyright (c) 2024 Red Hat, Inc. All rights reserved.
|
|
* Copyright (c) 2024, 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
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*
|
|
*/
|
|
|
|
#include "precompiled.hpp"
|
|
#include "memory/classLoaderMetaspace.hpp"
|
|
#include "memory/metaspace/freeBlocks.hpp"
|
|
#include "memory/metaspace/metablock.inline.hpp"
|
|
#include "memory/metaspace/metaspaceArena.hpp"
|
|
#include "memory/metaspace/metaspaceSettings.hpp"
|
|
#include "memory/metaspace/metaspaceStatistics.hpp"
|
|
#include "memory/metaspace.hpp"
|
|
#include "oops/klass.hpp"
|
|
#include "runtime/mutex.hpp"
|
|
#include "utilities/debug.hpp"
|
|
#include "utilities/align.hpp"
|
|
#include "utilities/globalDefinitions.hpp"
|
|
|
|
#ifdef _LP64
|
|
|
|
#define LOG_PLEASE
|
|
#include "metaspaceGtestCommon.hpp"
|
|
#include "metaspaceGtestContexts.hpp"
|
|
#include "metaspaceGtestRangeHelpers.hpp"
|
|
#include "metaspaceGtestSparseArray.hpp"
|
|
|
|
#define HANDLE_FAILURE \
|
|
if (testing::Test::HasFailure()) { \
|
|
return; \
|
|
}
|
|
|
|
namespace metaspace {
|
|
|
|
class ClmsTester {
|
|
|
|
Mutex _lock;
|
|
MetaspaceContext* _class_context;
|
|
MetaspaceContext* _nonclass_context;
|
|
ClassLoaderMetaspace* _clms;
|
|
const size_t _klass_arena_alignment_words;
|
|
unsigned _num_allocations;
|
|
|
|
struct Deltas {
|
|
int num_chunks_delta;
|
|
ssize_t used_words_delta;
|
|
int num_freeblocks_delta;
|
|
ssize_t freeblocks_words_delta;
|
|
};
|
|
|
|
Deltas calc_deltas(const ArenaStats& before, const ArenaStats& after) {
|
|
Deltas d;
|
|
d.num_chunks_delta = after.totals()._num - before.totals()._num;
|
|
d.used_words_delta = after.totals()._used_words - before.totals()._used_words;
|
|
d.num_freeblocks_delta = (int)after._free_blocks_num - (int)before._free_blocks_num;
|
|
d.freeblocks_words_delta = after._free_blocks_word_size - before._free_blocks_word_size;
|
|
return d;
|
|
}
|
|
|
|
public:
|
|
|
|
ClmsTester(size_t klass_alignment_words, Metaspace::MetaspaceType space_type,
|
|
MetaspaceContext* class_context, MetaspaceContext* nonclass_context)
|
|
: _lock(Monitor::nosafepoint, "CLMSTest_lock"),
|
|
_class_context(class_context), _nonclass_context(nonclass_context),
|
|
_clms(nullptr), _klass_arena_alignment_words(klass_alignment_words), _num_allocations(0) {
|
|
_clms = new ClassLoaderMetaspace(&_lock, space_type, nonclass_context, class_context, klass_alignment_words);
|
|
}
|
|
|
|
~ClmsTester() {
|
|
delete _clms;
|
|
EXPECT_EQ(_class_context->used_words(), (size_t)0);
|
|
EXPECT_EQ(_nonclass_context->used_words(), (size_t)0);
|
|
}
|
|
|
|
MetaBlock allocate_and_check(size_t word_size, bool is_class) {
|
|
|
|
// take stats before allocation
|
|
ClmsStats stats_before;
|
|
_clms->add_to_statistics(&stats_before);
|
|
|
|
// allocate
|
|
MetaWord* p = _clms->allocate(word_size, is_class ? Metaspace::ClassType : Metaspace::NonClassType);
|
|
_num_allocations ++;
|
|
|
|
// take stats after allocation
|
|
ClmsStats stats_after;
|
|
_clms->add_to_statistics(&stats_after);
|
|
|
|
// for less verbose testing:
|
|
const ArenaStats& ca_before = stats_before._arena_stats_class;
|
|
const ArenaStats& ca_after = stats_after._arena_stats_class;
|
|
const ArenaStats& nca_before = stats_before._arena_stats_nonclass;
|
|
const ArenaStats& nca_after = stats_after._arena_stats_nonclass;
|
|
|
|
// deltas
|
|
const Deltas d_ca = calc_deltas(ca_before, ca_after);
|
|
const Deltas d_nca = calc_deltas(nca_before, nca_after);
|
|
|
|
#define EXPECT_FREEBLOCKS_UNCHANGED(arena_prefix) \
|
|
EXPECT_EQ(d_##arena_prefix.num_freeblocks_delta, 0); \
|
|
EXPECT_EQ(d_##arena_prefix.freeblocks_words_delta, (ssize_t)0);
|
|
|
|
#define EXPECT_ARENA_UNCHANGED(arena_prefix) \
|
|
EXPECT_EQ(d_##arena_prefix.num_chunks_delta, 0); \
|
|
EXPECT_EQ(d_##arena_prefix.used_words_delta, (ssize_t)0);
|
|
|
|
if (p != nullptr) {
|
|
|
|
MetaBlock bl(p, word_size);
|
|
|
|
if (is_class) {
|
|
|
|
EXPECT_TRUE(bl.is_aligned_base(_klass_arena_alignment_words));
|
|
|
|
if (_num_allocations == 1) {
|
|
// first allocation: nonclass arena unchanged, class arena grows by 1 chunk and wordsize,
|
|
// class arena freeblocks unchanged
|
|
EXPECT_ARENA_UNCHANGED(nca);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(nca);
|
|
EXPECT_EQ(d_ca.num_chunks_delta, 1);
|
|
EXPECT_EQ((size_t)d_ca.used_words_delta, word_size);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(ca);
|
|
return bl;
|
|
}
|
|
|
|
// Had this been taken from class arena freeblocks?
|
|
if (d_ca.num_freeblocks_delta == -1) {
|
|
// the class arena freeblocks should have gone down, and the non-class arena freeblocks may have gone
|
|
// up in case the block was larger than required
|
|
const size_t wordsize_block_taken = (size_t)(-d_ca.freeblocks_words_delta);
|
|
EXPECT_GE(wordsize_block_taken, word_size); // the block we took must be at least allocation size
|
|
const size_t expected_freeblock_remainder = wordsize_block_taken - word_size;
|
|
if (expected_freeblock_remainder > 0) {
|
|
// the remainder, if it existed, should have been added to nonclass freeblocks
|
|
EXPECT_EQ(d_nca.num_freeblocks_delta, 1);
|
|
EXPECT_EQ((size_t)d_nca.freeblocks_words_delta, expected_freeblock_remainder);
|
|
}
|
|
// finally, nothing should have happened in the arenas proper.
|
|
EXPECT_ARENA_UNCHANGED(ca);
|
|
EXPECT_ARENA_UNCHANGED(nca);
|
|
return bl;
|
|
}
|
|
|
|
// block was taken from class arena proper
|
|
|
|
// We expect allocation waste due to alignment, should have been added to the freeblocks
|
|
// of nonclass arena. Allocation waste can be 0. If no chunk turnover happened, it must be
|
|
// smaller than klass alignment, otherwise it can get as large as a commit granule.
|
|
const size_t max_expected_allocation_waste =
|
|
d_ca.num_chunks_delta == 0 ? (_klass_arena_alignment_words - 1) : Settings::commit_granule_words();
|
|
EXPECT_GE(d_ca.num_chunks_delta, 0);
|
|
EXPECT_LE(d_ca.num_chunks_delta, 1);
|
|
EXPECT_GE((size_t)d_ca.used_words_delta, word_size);
|
|
EXPECT_LE((size_t)d_ca.used_words_delta, word_size + max_expected_allocation_waste);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(ca);
|
|
EXPECT_ARENA_UNCHANGED(nca);
|
|
if (max_expected_allocation_waste > 0) {
|
|
EXPECT_GE(d_nca.num_freeblocks_delta, 0);
|
|
EXPECT_LE(d_nca.num_freeblocks_delta, 1);
|
|
EXPECT_GE(d_nca.freeblocks_words_delta, 0);
|
|
EXPECT_LE((size_t)d_nca.freeblocks_words_delta, max_expected_allocation_waste);
|
|
} else {
|
|
EXPECT_FREEBLOCKS_UNCHANGED(nca);
|
|
}
|
|
return bl;
|
|
// end: is_class
|
|
} else {
|
|
// Nonclass arena allocation.
|
|
// Allocation waste can happen:
|
|
// - if we allocate from nonclass freeblocks, the block remainder
|
|
// - if we allocate from arena proper, by chunk turnover
|
|
|
|
if (d_nca.freeblocks_words_delta < 0) {
|
|
// We allocated a block from the nonclass arena freeblocks.
|
|
const size_t wordsize_block_taken = (size_t)(-d_nca.freeblocks_words_delta);
|
|
EXPECT_EQ(wordsize_block_taken, word_size);
|
|
// The number of blocks may or may not have decreased (depending on whether there
|
|
// was a wastage block)
|
|
EXPECT_GE(d_nca.num_chunks_delta, -1);
|
|
EXPECT_LE(d_nca.num_chunks_delta, 0);
|
|
EXPECT_ARENA_UNCHANGED(nca);
|
|
EXPECT_ARENA_UNCHANGED(ca);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(ca);
|
|
return bl;
|
|
}
|
|
|
|
// We don't expect alignment waste. Only wastage happens at chunk turnover.
|
|
const size_t max_expected_allocation_waste =
|
|
d_nca.num_chunks_delta == 0 ? 0 : Settings::commit_granule_words();
|
|
EXPECT_ARENA_UNCHANGED(ca);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(ca);
|
|
EXPECT_GE(d_nca.num_chunks_delta, 0);
|
|
EXPECT_LE(d_nca.num_chunks_delta, 1);
|
|
EXPECT_GE((size_t)d_nca.used_words_delta, word_size);
|
|
EXPECT_LE((size_t)d_nca.used_words_delta, word_size + max_expected_allocation_waste);
|
|
if (max_expected_allocation_waste == 0) {
|
|
EXPECT_FREEBLOCKS_UNCHANGED(nca);
|
|
}
|
|
}
|
|
return bl;
|
|
|
|
} // end: allocation successful
|
|
|
|
// allocation failed.
|
|
EXPECT_ARENA_UNCHANGED(ca);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(ca);
|
|
EXPECT_ARENA_UNCHANGED(nca);
|
|
EXPECT_FREEBLOCKS_UNCHANGED(nca);
|
|
|
|
return MetaBlock();
|
|
}
|
|
|
|
MetaBlock allocate_expect_success(size_t word_size, bool is_class) {
|
|
MetaBlock bl = allocate_and_check(word_size, is_class);
|
|
EXPECT_TRUE(bl.is_nonempty());
|
|
return bl;
|
|
}
|
|
|
|
MetaBlock allocate_expect_failure(size_t word_size, bool is_class) {
|
|
MetaBlock bl = allocate_and_check(word_size, is_class);
|
|
EXPECT_TRUE(bl.is_empty());
|
|
return bl;
|
|
}
|
|
|
|
void deallocate_and_check(MetaBlock bl) {
|
|
|
|
// take stats before deallocation
|
|
ClmsStats stats_before;
|
|
_clms->add_to_statistics(&stats_before);
|
|
|
|
// allocate
|
|
_clms->deallocate(bl.base(), bl.word_size());
|
|
|
|
// take stats after deallocation
|
|
ClmsStats stats_after;
|
|
_clms->add_to_statistics(&stats_after);
|
|
|
|
// for less verbose testing:
|
|
const ArenaStats& ca_before = stats_before._arena_stats_class;
|
|
const ArenaStats& ca_after = stats_after._arena_stats_class;
|
|
const ArenaStats& nca_before = stats_before._arena_stats_nonclass;
|
|
const ArenaStats& nca_after = stats_after._arena_stats_nonclass;
|
|
|
|
// deltas
|
|
// deltas
|
|
const Deltas d_ca = calc_deltas(ca_before, ca_after);
|
|
const Deltas d_nca = calc_deltas(nca_before, nca_after);
|
|
|
|
EXPECT_ARENA_UNCHANGED(ca);
|
|
EXPECT_ARENA_UNCHANGED(nca);
|
|
// Depending on whether the returned block was suitable for Klass,
|
|
// it may have gone to either the non-class freelist or the class freelist
|
|
if (d_ca.num_freeblocks_delta == 1) {
|
|
EXPECT_EQ(d_ca.num_freeblocks_delta, 1);
|
|
EXPECT_EQ((size_t)d_ca.freeblocks_words_delta, bl.word_size());
|
|
EXPECT_FREEBLOCKS_UNCHANGED(nca);
|
|
} else {
|
|
EXPECT_EQ(d_nca.num_freeblocks_delta, 1);
|
|
EXPECT_EQ((size_t)d_nca.freeblocks_words_delta, bl.word_size());
|
|
EXPECT_FREEBLOCKS_UNCHANGED(ca);
|
|
}
|
|
}
|
|
};
|
|
|
|
static constexpr size_t klass_size = sizeof(Klass) / BytesPerWord;
|
|
|
|
static void basic_test(size_t klass_arena_alignment) {
|
|
MetaspaceGtestContext class_context, nonclass_context;
|
|
{
|
|
ClmsTester tester(klass_arena_alignment, Metaspace::StandardMetaspaceType, class_context.context(), nonclass_context.context());
|
|
|
|
MetaBlock bl1 = tester.allocate_expect_success(klass_size, true);
|
|
HANDLE_FAILURE;
|
|
|
|
MetaBlock bl2 = tester.allocate_expect_success(klass_size, true);
|
|
HANDLE_FAILURE;
|
|
|
|
tester.deallocate_and_check(bl1);
|
|
HANDLE_FAILURE;
|
|
|
|
MetaBlock bl3 = tester.allocate_expect_success(klass_size, true);
|
|
HANDLE_FAILURE;
|
|
|
|
MetaBlock bl4 = tester.allocate_expect_success(Metaspace::min_allocation_word_size, false);
|
|
HANDLE_FAILURE;
|
|
|
|
MetaBlock bl5 = tester.allocate_expect_success(K, false);
|
|
HANDLE_FAILURE;
|
|
|
|
tester.deallocate_and_check(bl5);
|
|
HANDLE_FAILURE;
|
|
|
|
MetaBlock bl6 = tester.allocate_expect_success(K, false);
|
|
HANDLE_FAILURE;
|
|
|
|
EXPECT_EQ(bl5, bl6); // should have gotten the same block back from freelist
|
|
}
|
|
EXPECT_EQ(class_context.used_words(), (size_t)0);
|
|
EXPECT_EQ(nonclass_context.used_words(), (size_t)0);
|
|
// we should have used exactly one commit granule (64K), not more, for each context
|
|
EXPECT_EQ(class_context.committed_words(), Settings::commit_granule_words());
|
|
EXPECT_EQ(nonclass_context.committed_words(), Settings::commit_granule_words());
|
|
}
|
|
|
|
#define TEST_BASIC_N(n) \
|
|
TEST_VM(metaspace, CLMS_basics_##n) { \
|
|
basic_test(n); \
|
|
}
|
|
|
|
TEST_BASIC_N(1)
|
|
TEST_BASIC_N(4)
|
|
TEST_BASIC_N(16)
|
|
TEST_BASIC_N(32)
|
|
TEST_BASIC_N(128)
|
|
|
|
static void test_random(size_t klass_arena_alignment) {
|
|
MetaspaceGtestContext class_context, nonclass_context;
|
|
constexpr int max_allocations = 1024;
|
|
const SizeRange nonclass_alloc_range(Metaspace::min_allocation_alignment_words, 1024);
|
|
const SizeRange class_alloc_range(klass_size, 1024);
|
|
const IntRange one_out_of_ten(0, 10);
|
|
for (int runs = 9; runs >= 0; runs--) {
|
|
{
|
|
ClmsTester tester(64, Metaspace::StandardMetaspaceType, class_context.context(), nonclass_context.context());
|
|
struct LifeBlock {
|
|
MetaBlock bl;
|
|
bool is_class;
|
|
};
|
|
LifeBlock life_allocations[max_allocations];
|
|
for (int i = 0; i < max_allocations; i++) {
|
|
life_allocations[i].bl.reset();
|
|
}
|
|
|
|
unsigned num_class_allocs = 0, num_nonclass_allocs = 0, num_class_deallocs = 0, num_nonclass_deallocs = 0;
|
|
for (int i = 0; i < 5000; i ++) {
|
|
const int slot = IntRange(0, max_allocations).random_value();
|
|
if (life_allocations[slot].bl.is_empty()) {
|
|
const bool is_class = one_out_of_ten.random_value() == 0;
|
|
const size_t word_size =
|
|
is_class ? class_alloc_range.random_value() : nonclass_alloc_range.random_value();
|
|
MetaBlock bl = tester.allocate_expect_success(word_size, is_class);
|
|
HANDLE_FAILURE;
|
|
life_allocations[slot].bl = bl;
|
|
life_allocations[slot].is_class = is_class;
|
|
if (is_class) {
|
|
num_class_allocs ++;
|
|
} else {
|
|
num_nonclass_allocs ++;
|
|
}
|
|
} else {
|
|
tester.deallocate_and_check(life_allocations[slot].bl);
|
|
HANDLE_FAILURE;
|
|
life_allocations[slot].bl.reset();
|
|
if (life_allocations[slot].is_class) {
|
|
num_class_deallocs ++;
|
|
} else {
|
|
num_nonclass_deallocs ++;
|
|
}
|
|
}
|
|
}
|
|
LOG("num class allocs: %u, num nonclass allocs: %u, num class deallocs: %u, num nonclass deallocs: %u",
|
|
num_class_allocs, num_nonclass_allocs, num_class_deallocs, num_nonclass_deallocs);
|
|
}
|
|
EXPECT_EQ(class_context.used_words(), (size_t)0);
|
|
EXPECT_EQ(nonclass_context.used_words(), (size_t)0);
|
|
constexpr float fragmentation_factor = 3.0f;
|
|
const size_t max_expected_nonclass_committed = max_allocations * nonclass_alloc_range.highest() * fragmentation_factor;
|
|
const size_t max_expected_class_committed = max_allocations * class_alloc_range.highest() * fragmentation_factor;
|
|
// we should have used exactly one commit granule (64K), not more, for each context
|
|
EXPECT_LT(class_context.committed_words(), max_expected_class_committed);
|
|
EXPECT_LT(nonclass_context.committed_words(), max_expected_nonclass_committed);
|
|
}
|
|
}
|
|
|
|
#define TEST_RANDOM_N(n) \
|
|
TEST_VM(metaspace, CLMS_random_##n) { \
|
|
test_random(n); \
|
|
}
|
|
|
|
TEST_RANDOM_N(1)
|
|
TEST_RANDOM_N(4)
|
|
TEST_RANDOM_N(16)
|
|
TEST_RANDOM_N(32)
|
|
TEST_RANDOM_N(128)
|
|
|
|
} // namespace metaspace
|
|
|
|
#endif // _LP64
|