Path: blob/master/test/hotspot/gtest/metaspace/test_virtualspacenode.cpp
41144 views
/*1* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.2* Copyright (c) 2020, 2021 SAP SE. All rights reserved.3* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.4*5* This code is free software; you can redistribute it and/or modify it6* under the terms of the GNU General Public License version 2 only, as7* published by the Free Software Foundation.8*9* This code is distributed in the hope that it will be useful, but WITHOUT10* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or11* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License12* version 2 for more details (a copy is included in the LICENSE file that13* accompanied this code).14*15* You should have received a copy of the GNU General Public License version16* 2 along with this work; if not, write to the Free Software Foundation,17* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.18*19* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA20* or visit www.oracle.com if you need additional information or have any21* questions.22*23*/2425#include "precompiled.hpp"26#include "memory/metaspace/chunklevel.hpp"27#include "memory/metaspace/commitLimiter.hpp"28#include "memory/metaspace/counters.hpp"29#include "memory/metaspace/freeChunkList.hpp"30#include "memory/metaspace/metachunk.hpp"31#include "memory/metaspace/metachunkList.hpp"32#include "memory/metaspace/metaspaceSettings.hpp"33#include "memory/metaspace/virtualSpaceNode.hpp"34#include "runtime/mutexLocker.hpp"35#include "utilities/debug.hpp"36//#define LOG_PLEASE37#include "metaspaceGtestCommon.hpp"38#include "metaspaceGtestRangeHelpers.hpp"3940using metaspace::chunklevel_t;41using metaspace::CommitLimiter;42using metaspace::FreeChunkListVector;43using metaspace::Metachunk;44using metaspace::MetachunkList;45using metaspace::VirtualSpaceNode;46using metaspace::Settings;47using metaspace::SizeCounter;4849class VirtualSpaceNodeTest {5051// These counters are updated by the Node.52SizeCounter _counter_reserved_words;53SizeCounter _counter_committed_words;54CommitLimiter _commit_limiter;55VirtualSpaceNode* _node;5657// These are my checks and counters.58const size_t _vs_word_size;59const size_t _commit_limit;6061MetachunkList _root_chunks;6263void verify() const {6465ASSERT_EQ(_root_chunks.count() * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,66_node->used_words());6768ASSERT_GE(_commit_limit, _counter_committed_words.get());69ASSERT_EQ(_commit_limiter.committed_words(), _counter_committed_words.get());7071// Since we know _counter_committed_words serves our single node alone, the counter has to72// match the number of bits in the node internal commit mask.73ASSERT_EQ(_counter_committed_words.get(), _node->committed_words());7475ASSERT_EQ(_counter_reserved_words.get(), _vs_word_size);76ASSERT_EQ(_counter_reserved_words.get(), _node->word_size());7778}7980void lock_and_verify_node() {81#ifdef ASSERT82MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);83_node->verify_locked();84#endif85}8687Metachunk* alloc_root_chunk() {8889verify();9091const bool node_is_full = _node->used_words() == _node->word_size();92Metachunk* c = NULL;93{94MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);95c = _node->allocate_root_chunk();96}9798lock_and_verify_node();99100if (node_is_full) {101102EXPECT_NULL(c);103104} else {105106DEBUG_ONLY(c->verify();)107EXPECT_NOT_NULL(c);108EXPECT_TRUE(c->is_root_chunk());109EXPECT_TRUE(c->is_free());110EXPECT_EQ(c->word_size(), metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);111112EXPECT_TRUE(c->is_fully_uncommitted());113114EXPECT_TRUE(_node->contains(c->base()));115116_root_chunks.add(c);117118}119120verify();121122return c;123124}125126bool commit_root_chunk(Metachunk* c, size_t request_commit_words) {127128verify();129130const size_t committed_words_before = _counter_committed_words.get();131132bool rc = c->ensure_committed(request_commit_words);133134verify();135DEBUG_ONLY(c->verify();)136137lock_and_verify_node();138139if (rc == false) {140141// We must have hit the commit limit.142EXPECT_GE(committed_words_before + request_commit_words, _commit_limit);143144} else {145146// We should not have hit the commit limit.147EXPECT_LE(_counter_committed_words.get(), _commit_limit);148149// We do not know how much we really committed - maybe nothing if the150// chunk had been committed before - but we know the numbers should have151// risen or at least stayed equal.152EXPECT_GE(_counter_committed_words.get(), committed_words_before);153154// The chunk should be as far committed as was requested155EXPECT_GE(c->committed_words(), request_commit_words);156157// Zap committed portion.158DEBUG_ONLY(zap_range(c->base(), c->committed_words());)159160}161162verify();163164return rc;165166} // commit_root_chunk167168void uncommit_chunk(Metachunk* c) {169170verify();171172const size_t committed_words_before = _counter_committed_words.get();173const size_t available_words_before = _commit_limiter.possible_expansion_words();174175c->uncommit();176177DEBUG_ONLY(c->verify();)178179lock_and_verify_node();180181EXPECT_EQ(c->committed_words(), (size_t)0);182183// Commit counter should have gone down (by exactly the size of the chunk) if chunk184// is larger than a commit granule.185// For smaller chunks, we do not know, but at least we know the commit size should not186// have gone up.187if (c->word_size() >= Settings::commit_granule_words()) {188189EXPECT_EQ(_counter_committed_words.get(), committed_words_before - c->word_size());190191// also, commit number in commit limiter should have gone down, so we have more space192EXPECT_EQ(_commit_limiter.possible_expansion_words(),193available_words_before + c->word_size());194195} else {196197EXPECT_LE(_counter_committed_words.get(), committed_words_before);198199}200201verify();202203} // uncommit_chunk204205Metachunk* split_chunk_with_checks(Metachunk* c, chunklevel_t target_level, FreeChunkListVector* freelist) {206207DEBUG_ONLY(c->verify();)208209const chunklevel_t orig_level = c->level();210assert(orig_level < target_level, "Sanity");211DEBUG_ONLY(metaspace::chunklevel::check_valid_level(target_level);)212213const int total_num_chunks_in_freelist_before = freelist->num_chunks();214const size_t total_word_size_in_freelist_before = freelist->word_size();215216// freelist->print_on(tty);217218// Split...219{220MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);221_node->split(target_level, c, freelist);222}223224// freelist->print_on(tty);225226EXPECT_NOT_NULL(c);227EXPECT_EQ(c->level(), target_level);228EXPECT_TRUE(c->is_free());229230// ... check that we get the proper amount of splinters. For every chunk split we expect one231// buddy chunk to appear of level + 1 (aka, half size).232size_t expected_wordsize_increase = 0;233int expected_num_chunks_increase = 0;234for (chunklevel_t l = orig_level + 1; l <= target_level; l++) {235expected_wordsize_increase += metaspace::chunklevel::word_size_for_level(l);236expected_num_chunks_increase++;237}238239const int total_num_chunks_in_freelist_after = freelist->num_chunks();240const size_t total_word_size_in_freelist_after = freelist->word_size();241242EXPECT_EQ(total_num_chunks_in_freelist_after, total_num_chunks_in_freelist_before + expected_num_chunks_increase);243EXPECT_EQ(total_word_size_in_freelist_after, total_word_size_in_freelist_before + expected_wordsize_increase);244245return c;246247} // end: split_chunk_with_checks248249Metachunk* merge_chunk_with_checks(Metachunk* c, chunklevel_t expected_target_level, FreeChunkListVector* freelist) {250251const chunklevel_t orig_level = c->level();252assert(expected_target_level < orig_level, "Sanity");253254const int total_num_chunks_in_freelist_before = freelist->num_chunks();255const size_t total_word_size_in_freelist_before = freelist->word_size();256257//freelist->print_on(tty);258259Metachunk* result = NULL;260{261MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);262result = _node->merge(c, freelist);263}264EXPECT_NOT_NULL(result);265EXPECT_TRUE(result->level() == expected_target_level);266267//freelist->print_on(tty);268269// ... check that we merged in the proper amount of chunks. For every decreased level270// of the original chunk (each size doubling) we should see one buddy chunk swallowed up.271size_t expected_wordsize_decrease = 0;272int expected_num_chunks_decrease = 0;273for (chunklevel_t l = orig_level; l > expected_target_level; l --) {274expected_wordsize_decrease += metaspace::chunklevel::word_size_for_level(l);275expected_num_chunks_decrease++;276}277278const int total_num_chunks_in_freelist_after = freelist->num_chunks();279const size_t total_word_size_in_freelist_after = freelist->word_size();280281EXPECT_EQ(total_num_chunks_in_freelist_after, total_num_chunks_in_freelist_before - expected_num_chunks_decrease);282EXPECT_EQ(total_word_size_in_freelist_after, total_word_size_in_freelist_before - expected_wordsize_decrease);283284return result;285286} // end: merge_chunk_with_checks287288public:289290VirtualSpaceNodeTest(size_t vs_word_size, size_t commit_limit) :291_counter_reserved_words(),292_counter_committed_words(),293_commit_limiter(commit_limit),294_node(NULL),295_vs_word_size(vs_word_size),296_commit_limit(commit_limit)297{298{299MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);300_node = VirtualSpaceNode::create_node(vs_word_size, &_commit_limiter,301&_counter_reserved_words, &_counter_committed_words);302EXPECT_EQ(_node->word_size(), vs_word_size);303}304EXPECT_TRUE(_commit_limiter.possible_expansion_words() == _commit_limit);305verify();306}307308~VirtualSpaceNodeTest() {309{310MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);311delete _node;312}313// After the node is deleted, counters should be back to zero314// (we cannot use ASSERT/EXPECT here in the destructor)315assert(_counter_reserved_words.get() == 0, "Sanity");316assert(_counter_committed_words.get() == 0, "Sanity");317assert(_commit_limiter.committed_words() == 0, "Sanity");318}319320void test_simple() {321Metachunk* c = alloc_root_chunk();322commit_root_chunk(c, Settings::commit_granule_words());323commit_root_chunk(c, c->word_size());324uncommit_chunk(c);325}326327void test_exhaust_node() {328Metachunk* c = NULL;329bool rc = true;330do {331c = alloc_root_chunk();332if (c != NULL) {333rc = commit_root_chunk(c, c->word_size());334}335} while (c != NULL && rc);336}337338void test_arbitrary_commits() {339340assert(_commit_limit >= _vs_word_size, "For this test no commit limit.");341342// Get a root chunk to have a committable region343Metachunk* c = alloc_root_chunk();344ASSERT_NOT_NULL(c);345346if (c->committed_words() > 0) {347c->uncommit();348}349350ASSERT_EQ(_node->committed_words(), (size_t)0);351ASSERT_EQ(_counter_committed_words.get(), (size_t)0);352353TestMap testmap(c->word_size());354assert(testmap.get_num_set() == 0, "Sanity");355356for (int run = 0; run < 1000; run++) {357358const size_t committed_words_before = testmap.get_num_set();359ASSERT_EQ(_commit_limiter.committed_words(), committed_words_before);360ASSERT_EQ(_counter_committed_words.get(), committed_words_before);361362// A random range363SizeRange r = SizeRange(c->word_size()).random_aligned_subrange(Settings::commit_granule_words());364365const size_t committed_words_in_range_before =366testmap.get_num_set(r.start(), r.end());367368const bool do_commit = IntRange(100).random_value() >= 50;369if (do_commit) {370371//LOG("c " SIZE_FORMAT "," SIZE_FORMAT, r.start(), r.end());372373bool rc = false;374{375MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);376rc = _node->ensure_range_is_committed(c->base() + r.start(), r.size());377}378379// Test-zap380zap_range(c->base() + r.start(), r.size());381382// We should never reach commit limit since it is as large as the whole area.383ASSERT_TRUE(rc);384385testmap.set_range(r.start(), r.end());386387} else {388389//LOG("u " SIZE_FORMAT "," SIZE_FORMAT, r.start(), r.end());390391{392MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);393_node->uncommit_range(c->base() + r.start(), r.size());394}395396testmap.clear_range(r.start(), r.end());397398}399400const size_t committed_words_after = testmap.get_num_set();401402ASSERT_EQ(_commit_limiter.committed_words(), committed_words_after);403ASSERT_EQ(_counter_committed_words.get(), committed_words_after);404405verify();406}407}408409// Helper function for test_splitting_chunks_1410static void check_chunk_is_committed_at_least_up_to(const Metachunk* c, size_t& word_size) {411if (word_size >= c->word_size()) {412EXPECT_TRUE(c->is_fully_committed());413word_size -= c->word_size();414} else {415EXPECT_EQ(c->committed_words(), word_size);416word_size = 0; // clear remaining size if there is.417}418}419420void test_split_and_merge_chunks() {421422assert(_commit_limit >= _vs_word_size, "No commit limit here pls");423424// Allocate a root chunk and commit a random part of it. Then repeatedly split425// it and merge it back together; observe the committed regions of the split chunks.426427Metachunk* c = alloc_root_chunk();428429if (c->committed_words() > 0) {430c->uncommit();431}432433// To capture split-off chunks. Note: it is okay to use this here as a temp object.434FreeChunkListVector freelist;435436const int granules_per_root_chunk = (int)(c->word_size() / Settings::commit_granule_words());437438for (int granules_to_commit = 0; granules_to_commit < granules_per_root_chunk; granules_to_commit++) {439440const size_t words_to_commit = Settings::commit_granule_words() * granules_to_commit;441442c->ensure_committed(words_to_commit);443444ASSERT_EQ(c->committed_words(), words_to_commit);445ASSERT_EQ(_counter_committed_words.get(), words_to_commit);446ASSERT_EQ(_commit_limiter.committed_words(), words_to_commit);447448const size_t committed_words_before = c->committed_words();449450verify();451452for (chunklevel_t target_level = LOWEST_CHUNK_LEVEL + 1;453target_level <= HIGHEST_CHUNK_LEVEL; target_level++) {454455// Split:456Metachunk* c2 = split_chunk_with_checks(c, target_level, &freelist);457c2->set_in_use();458459// Split smallest leftover chunk.460if (c2->level() < HIGHEST_CHUNK_LEVEL) {461462Metachunk* c3 = freelist.remove_first(c2->level());463ASSERT_NOT_NULL(c3); // Must exist since c2 must have a splinter buddy now.464465Metachunk* c4 = split_chunk_with_checks(c3, HIGHEST_CHUNK_LEVEL, &freelist);466c4->set_in_use();467468// Merge it back. We expect this to merge up to c2's level, since c2 is in use.469c4->set_free();470Metachunk* c5 = merge_chunk_with_checks(c4, c2->level(), &freelist);471ASSERT_NOT_NULL(c5);472freelist.add(c5);473474}475476// Merge c2 back.477c2->set_free();478merge_chunk_with_checks(c2, LOWEST_CHUNK_LEVEL, &freelist);479480// After all this splitting and combining committed size should not have changed.481ASSERT_EQ(c2->committed_words(), committed_words_before);482483}484485}486487} // end: test_splitting_chunks488489};490491TEST_VM(metaspace, virtual_space_node_test_basics) {492493MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);494495const size_t word_size = metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 10;496497SizeCounter scomm;498SizeCounter sres;499CommitLimiter cl (word_size * 2); // basically, no commit limiter.500501VirtualSpaceNode* node = VirtualSpaceNode::create_node(word_size, &cl, &sres, &scomm);502ASSERT_NOT_NULL(node);503ASSERT_EQ(node->committed_words(), (size_t)0);504ASSERT_EQ(node->committed_words(), scomm.get());505DEBUG_ONLY(node->verify_locked();)506507bool b = node->ensure_range_is_committed(node->base(), node->word_size());508ASSERT_TRUE(b);509ASSERT_EQ(node->committed_words(), word_size);510ASSERT_EQ(node->committed_words(), scomm.get());511DEBUG_ONLY(node->verify_locked();)512zap_range(node->base(), node->word_size());513514node->uncommit_range(node->base(), node->word_size());515ASSERT_EQ(node->committed_words(), (size_t)0);516ASSERT_EQ(node->committed_words(), scomm.get());517DEBUG_ONLY(node->verify_locked();)518519const int num_granules = (int)(word_size / Settings::commit_granule_words());520for (int i = 1; i < num_granules; i += 4) {521b = node->ensure_range_is_committed(node->base(), i * Settings::commit_granule_words());522ASSERT_TRUE(b);523ASSERT_EQ(node->committed_words(), i * Settings::commit_granule_words());524ASSERT_EQ(node->committed_words(), scomm.get());525DEBUG_ONLY(node->verify_locked();)526zap_range(node->base(), i * Settings::commit_granule_words());527}528529node->uncommit_range(node->base(), node->word_size());530ASSERT_EQ(node->committed_words(), (size_t)0);531ASSERT_EQ(node->committed_words(), scomm.get());532DEBUG_ONLY(node->verify_locked();)533534}535536// Note: we unfortunately need TEST_VM even though the system tested537// should be pretty independent since we need things like os::vm_page_size()538// which in turn need OS layer initialization.539TEST_VM(metaspace, virtual_space_node_test_1) {540VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,541metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);542test.test_simple();543}544545TEST_VM(metaspace, virtual_space_node_test_2) {546// Should not hit commit limit547VirtualSpaceNodeTest test(3 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,5483 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);549test.test_simple();550test.test_exhaust_node();551}552553TEST_VM(metaspace, virtual_space_node_test_3) {554double d = os::elapsedTime();555// Test committing uncommitting arbitrary ranges556for (int run = 0; run < 100; run++) {557VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,558metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);559test.test_split_and_merge_chunks();560}561double d2 = os::elapsedTime();562LOG("%f", (d2-d));563}564565TEST_VM(metaspace, virtual_space_node_test_4) {566// Should hit commit limit567VirtualSpaceNodeTest test(10 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,5683 * metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);569test.test_exhaust_node();570}571572TEST_VM(metaspace, virtual_space_node_test_5) {573// Test committing uncommitting arbitrary ranges574VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE,575metaspace::chunklevel::MAX_CHUNK_WORD_SIZE);576test.test_arbitrary_commits();577}578579TEST_VM(metaspace, virtual_space_node_test_7) {580// Test large allocation and freeing.581{582VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100,583metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100);584test.test_exhaust_node();585}586{587VirtualSpaceNodeTest test(metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100,588metaspace::chunklevel::MAX_CHUNK_WORD_SIZE * 100);589test.test_exhaust_node();590}591592}593594595