jdk-24/test/hotspot/gtest/metaspace/test_clms.cpp
Roman Kennke 44ec501a41 8305895: Implement JEP 450: Compact Object Headers (Experimental)
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
2024-11-08 17:21:39 +00:00

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