Path: blob/master/test/hotspot/gtest/metaspace/test_metachunk.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/chunkManager.hpp"27#include "memory/metaspace/freeChunkList.hpp"28#include "memory/metaspace/metachunk.hpp"29#include "memory/metaspace/metaspaceSettings.hpp"30#include "memory/metaspace/virtualSpaceNode.hpp"31#include "metaspaceGtestCommon.hpp"32#include "metaspaceGtestContexts.hpp"33#include "runtime/mutexLocker.hpp"3435using metaspace::ChunkManager;36using metaspace::FreeChunkListVector;37using metaspace::Metachunk;38using metaspace::Settings;39using metaspace::VirtualSpaceNode;40using namespace metaspace::chunklevel;4142// Test ChunkManager::get_chunk43TEST_VM(metaspace, get_chunk) {4445ChunkGtestContext context(8 * M);46Metachunk* c = NULL;4748for (chunklevel_t pref_lvl = LOWEST_CHUNK_LEVEL; pref_lvl <= HIGHEST_CHUNK_LEVEL; pref_lvl++) {4950for (chunklevel_t max_lvl = pref_lvl; max_lvl <= HIGHEST_CHUNK_LEVEL; max_lvl++) {5152for (size_t min_committed_words = Settings::commit_granule_words();53min_committed_words <= word_size_for_level(max_lvl); min_committed_words *= 2) {54context.alloc_chunk_expect_success(&c, pref_lvl, max_lvl, min_committed_words);55context.return_chunk(c);56}57}58}59}6061// Test ChunkManager::get_chunk, but with a commit limit.62TEST_VM(metaspace, get_chunk_with_commit_limit) {6364const size_t commit_limit_words = 1 * M;65ChunkGtestContext context(commit_limit_words);66Metachunk* c = NULL;6768for (chunklevel_t pref_lvl = LOWEST_CHUNK_LEVEL; pref_lvl <= HIGHEST_CHUNK_LEVEL; pref_lvl++) {6970for (chunklevel_t max_lvl = pref_lvl; max_lvl <= HIGHEST_CHUNK_LEVEL; max_lvl++) {7172for (size_t min_committed_words = Settings::commit_granule_words();73min_committed_words <= word_size_for_level(max_lvl); min_committed_words *= 2) {7475if (min_committed_words <= commit_limit_words) {76context.alloc_chunk_expect_success(&c, pref_lvl, max_lvl, min_committed_words);77context.return_chunk(c);78} else {79context.alloc_chunk_expect_failure(pref_lvl, max_lvl, min_committed_words);80}8182}83}84}85}8687// Test that recommitting the used portion of a chunk will preserve the original content.88TEST_VM(metaspace, get_chunk_recommit) {8990ChunkGtestContext context;91Metachunk* c = NULL;92context.alloc_chunk_expect_success(&c, ROOT_CHUNK_LEVEL, ROOT_CHUNK_LEVEL, 0);93context.uncommit_chunk_with_test(c);9495context.commit_chunk_with_test(c, Settings::commit_granule_words());96context.allocate_from_chunk(c, Settings::commit_granule_words());9798c->ensure_committed(Settings::commit_granule_words());99check_range_for_pattern(c->base(), c->used_words(), (uintx)c);100101c->ensure_committed(Settings::commit_granule_words() * 2);102check_range_for_pattern(c->base(), c->used_words(), (uintx)c);103104context.return_chunk(c);105106}107108// Test ChunkManager::get_chunk, but with a reserve limit.109// (meaning, the underlying VirtualSpaceList cannot expand, like compressed class space).110TEST_VM(metaspace, get_chunk_with_reserve_limit) {111112const size_t reserve_limit_words = word_size_for_level(ROOT_CHUNK_LEVEL);113const size_t commit_limit_words = 1024 * M; // just very high114ChunkGtestContext context(commit_limit_words, reserve_limit_words);115116// Reserve limit works at root chunk size granularity: if the chunk manager cannot satisfy117// a request for a chunk from its freelists, it will acquire a new root chunk from the118// underlying virtual space list. If that list is full and cannot be expanded (think ccs)119// we should get an error.120// Testing this is simply testing a chunk allocation which should cause allocation of a new121// root chunk.122123// Cause allocation of the firstone root chunk, should still work:124Metachunk* c = NULL;125context.alloc_chunk_expect_success(&c, HIGHEST_CHUNK_LEVEL);126127// and this should need a new root chunk and hence fail:128context.alloc_chunk_expect_failure(ROOT_CHUNK_LEVEL);129130context.return_chunk(c);131132}133134// Test MetaChunk::allocate135TEST_VM(metaspace, chunk_allocate_full) {136137ChunkGtestContext context;138139for (chunklevel_t lvl = LOWEST_CHUNK_LEVEL; lvl <= HIGHEST_CHUNK_LEVEL; lvl++) {140Metachunk* c = NULL;141context.alloc_chunk_expect_success(&c, lvl);142context.allocate_from_chunk(c, c->word_size());143context.return_chunk(c);144}145146}147148// Test MetaChunk::allocate149TEST_VM(metaspace, chunk_allocate_random) {150151ChunkGtestContext context;152153for (chunklevel_t lvl = LOWEST_CHUNK_LEVEL; lvl <= HIGHEST_CHUNK_LEVEL; lvl++) {154155Metachunk* c = NULL;156context.alloc_chunk_expect_success(&c, lvl);157context.uncommit_chunk_with_test(c); // start out fully uncommitted158159RandSizeGenerator rgen(1, c->word_size() / 30);160bool stop = false;161162while (!stop) {163const size_t s = rgen.get();164if (s <= c->free_words()) {165context.commit_chunk_with_test(c, s);166context.allocate_from_chunk(c, s);167} else {168stop = true;169}170171}172context.return_chunk(c);173174}175176}177178TEST_VM(metaspace, chunk_buddy_stuff) {179180for (chunklevel_t l = ROOT_CHUNK_LEVEL + 1; l <= HIGHEST_CHUNK_LEVEL; l++) {181182ChunkGtestContext context;183184// Allocate two chunks; since we know the first chunk is the first in its area,185// it has to be a leader, and the next one of the same size its buddy.186187// (Note: strictly speaking the ChunkManager does not promise any placement but188// we know how the placement works so these tests make sense).189190Metachunk* c1 = NULL;191context.alloc_chunk(&c1, CHUNK_LEVEL_1K);192EXPECT_TRUE(c1->is_leader());193194Metachunk* c2 = NULL;195context.alloc_chunk(&c2, CHUNK_LEVEL_1K);196EXPECT_FALSE(c2->is_leader());197198// buddies are adjacent in memory199// (next/prev_in_vs needs lock)200{201MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);202EXPECT_EQ(c1->next_in_vs(), c2);203EXPECT_EQ(c1->end(), c2->base());204EXPECT_NULL(c1->prev_in_vs()); // since we know this is the first in the area205EXPECT_EQ(c2->prev_in_vs(), c1);206}207208context.return_chunk(c1);209context.return_chunk(c2);210211}212213}214215TEST_VM(metaspace, chunk_allocate_with_commit_limit) {216217// This test does not make sense if commit-on-demand is off218if (Settings::new_chunks_are_fully_committed()) {219return;220}221222const size_t granule_sz = Settings::commit_granule_words();223const size_t commit_limit = granule_sz * 3;224ChunkGtestContext context(commit_limit);225226// A big chunk, but uncommitted.227Metachunk* c = NULL;228context.alloc_chunk_expect_success(&c, ROOT_CHUNK_LEVEL, ROOT_CHUNK_LEVEL, 0);229context.uncommit_chunk_with_test(c); // ... just to make sure.230231// first granule...232context.commit_chunk_with_test(c, granule_sz);233context.allocate_from_chunk(c, granule_sz);234235// second granule...236context.commit_chunk_with_test(c, granule_sz);237context.allocate_from_chunk(c, granule_sz);238239// third granule...240context.commit_chunk_with_test(c, granule_sz);241context.allocate_from_chunk(c, granule_sz);242243// This should fail now.244context.commit_chunk_expect_failure(c, granule_sz);245246context.return_chunk(c);247248}249250// Test splitting a chunk251TEST_VM(metaspace, chunk_split_and_merge) {252253// Split works like this:254//255// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----256// | A |257// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----258//259// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----260// | A' | b | c | d | e |261// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----262//263// A original chunk (A) is split to form a target chunk (A') and as a result splinter264// chunks form (b..e). A' is the leader of the (A',b) pair, which is the leader of the265// ((A',b), c) pair and so on. In other words, A' will be a leader chunk, all splinter266// chunks are follower chunks.267//268// Merging reverses this operation:269//270// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----271// | A | b | c | d | e |272// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----273//274// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----275// | A' |276// ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----277//278// (A) will be merged with its buddy b, (A+b) with its buddy c and so on. The result279// chunk is A'.280// Note that merging also works, of course, if we were to start the merge at (b) (so,281// with a follower chunk, not a leader). Also, at any point in the merge282// process we may arrive at a follower chunk. So, the fact that in this test283// we only expect a leader merge is a feature of the test, and of the fact that we284// start each split test with a fresh ChunkTestsContext.285286// Note: Splitting and merging chunks is usually done from within the ChunkManager and287// subject to a lot of assumptions and hence asserts. Here, we have to explicitly use288// VirtualSpaceNode::split/::merge and therefore have to observe rules:289// - both split and merge expect free chunks, so state has to be "free"290// - but that would trigger the "ideally merged" assertion in the RootChunkArea, so the291// original chunk has to be a root chunk, we cannot just split any chunk manually.292// - Also, after the split we have to completely re-merge to avoid triggering asserts293// in ~RootChunkArea()294// - finally we have to lock manually295296ChunkGtestContext context;297298const chunklevel_t orig_lvl = ROOT_CHUNK_LEVEL;299for (chunklevel_t target_lvl = orig_lvl + 1; target_lvl <= HIGHEST_CHUNK_LEVEL; target_lvl++) {300301// Split a fully committed chunk. The resulting chunk should be fully302// committed as well, and have its content preserved.303Metachunk* c = NULL;304context.alloc_chunk_expect_success(&c, orig_lvl);305306// We allocate from this chunk to be able to completely paint the payload.307context.allocate_from_chunk(c, c->word_size());308309const uintx canary = os::random();310fill_range_with_pattern(c->base(), c->word_size(), canary);311312FreeChunkListVector splinters;313314{315// Splitting/Merging chunks is usually done by the chunkmanager, and no explicit316// outside API exists. So we split/merge chunks via the underlying vs node, directly.317// This means that we have to go through some extra hoops to not trigger any asserts.318MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);319c->reset_used_words();320c->set_free();321c->vsnode()->split(target_lvl, c, &splinters);322}323324DEBUG_ONLY(context.verify();)325326EXPECT_EQ(c->level(), target_lvl);327EXPECT_TRUE(c->is_fully_committed());328EXPECT_FALSE(c->is_root_chunk());329EXPECT_TRUE(c->is_leader());330331check_range_for_pattern(c->base(), c->word_size(), canary);332333// I expect splinter chunks (one for each splinter level:334// e.g. splitting a 1M chunk to get a 64K chunk should yield splinters: [512K, 256K, 128K, 64K]335for (chunklevel_t l = LOWEST_CHUNK_LEVEL; l < HIGHEST_CHUNK_LEVEL; l++) {336const Metachunk* c2 = splinters.first_at_level(l);337if (l > orig_lvl && l <= target_lvl) {338EXPECT_NOT_NULL(c2);339EXPECT_EQ(c2->level(), l);340EXPECT_TRUE(c2->is_free());341EXPECT_TRUE(!c2->is_leader());342DEBUG_ONLY(c2->verify());343check_range_for_pattern(c2->base(), c2->word_size(), canary);344} else {345EXPECT_NULL(c2);346}347}348349// Revert the split by using merge. This should result in all splinters coalescing350// to one chunk.351{352MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);353Metachunk* merged = c->vsnode()->merge(c, &splinters);354355// the merged chunk should occupy the same address as the splinter356// since it should have been the leader in the split.357EXPECT_EQ(merged, c);358EXPECT_TRUE(merged->is_root_chunk() || merged->is_leader());359360// Splitting should have arrived at the original chunk since none of the splinters are in use.361EXPECT_EQ(c->level(), orig_lvl);362363// All splinters should have been removed from the list364EXPECT_EQ(splinters.num_chunks(), 0);365}366367context.return_chunk(c);368369}370371}372373TEST_VM(metaspace, chunk_enlarge_in_place) {374375ChunkGtestContext context;376377// Starting with the smallest chunk size, attempt to enlarge the chunk in place until we arrive378// at root chunk size. Since the state is clean, this should work.379380Metachunk* c = NULL;381context.alloc_chunk_expect_success(&c, HIGHEST_CHUNK_LEVEL);382383chunklevel_t l = c->level();384385while (l != ROOT_CHUNK_LEVEL) {386387// commit and allocate from chunk to pattern it...388const size_t original_chunk_size = c->word_size();389context.commit_chunk_with_test(c, c->free_words());390context.allocate_from_chunk(c, c->free_words());391392size_t used_before = c->used_words();393size_t free_before = c->free_words();394size_t free_below_committed_before = c->free_below_committed_words();395const MetaWord* top_before = c->top();396397EXPECT_TRUE(context.cm().attempt_enlarge_chunk(c));398EXPECT_EQ(l - 1, c->level());399EXPECT_EQ(c->word_size(), original_chunk_size * 2);400401// Used words should not have changed402EXPECT_EQ(c->used_words(), used_before);403EXPECT_EQ(c->top(), top_before);404405// free words should be expanded by the old size (since old chunk is doubled in size)406EXPECT_EQ(c->free_words(), free_before + original_chunk_size);407408// free below committed can be larger but never smaller409EXPECT_GE(c->free_below_committed_words(), free_below_committed_before);410411// Old content should be preserved412check_range_for_pattern(c->base(), original_chunk_size, (uintx)c);413414l = c->level();415}416417context.return_chunk(c);418419}420421422423