/* * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021 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. * * 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. *
*/
using metaspace::ChunkManager; using metaspace::FreeChunkListVector; using metaspace::Metachunk; using metaspace::Settings; using metaspace::VirtualSpaceNode; usingnamespace metaspace::chunklevel;
// Test ChunkManager::get_chunk
TEST_VM(metaspace, get_chunk) {
ChunkGtestContext context(8 * M);
Metachunk* c = NULL;
for (chunklevel_t pref_lvl = LOWEST_CHUNK_LEVEL; pref_lvl <= HIGHEST_CHUNK_LEVEL; pref_lvl++) {
for (chunklevel_t max_lvl = pref_lvl; max_lvl <= HIGHEST_CHUNK_LEVEL; max_lvl++) {
// Test ChunkManager::get_chunk, but with a reserve limit. // (meaning, the underlying VirtualSpaceList cannot expand, like compressed class space).
TEST_VM(metaspace, get_chunk_with_reserve_limit) {
const size_t reserve_limit_words = word_size_for_level(ROOT_CHUNK_LEVEL); const size_t commit_limit_words = 1024 * M; // just very high
ChunkGtestContext context(commit_limit_words, reserve_limit_words);
// Reserve limit works at root chunk size granularity: if the chunk manager cannot satisfy // a request for a chunk from its freelists, it will acquire a new root chunk from the // underlying virtual space list. If that list is full and cannot be expanded (think ccs) // we should get an error. // Testing this is simply testing a chunk allocation which should cause allocation of a new // root chunk.
// Cause allocation of the firstone root chunk, should still work:
Metachunk* c = NULL;
context.alloc_chunk_expect_success(&c, HIGHEST_CHUNK_LEVEL);
// and this should need a new root chunk and hence fail:
context.alloc_chunk_expect_failure(ROOT_CHUNK_LEVEL);
context.return_chunk(c);
}
// Test MetaChunk::allocate
TEST_VM(metaspace, chunk_allocate_full) {
ChunkGtestContext context;
for (chunklevel_t lvl = LOWEST_CHUNK_LEVEL; lvl <= HIGHEST_CHUNK_LEVEL; lvl++) {
Metachunk* c = NULL;
context.alloc_chunk_expect_success(&c, lvl);
context.allocate_from_chunk(c, c->word_size());
context.return_chunk(c);
}
}
// Test MetaChunk::allocate
TEST_VM(metaspace, chunk_allocate_random) {
ChunkGtestContext context;
for (chunklevel_t lvl = LOWEST_CHUNK_LEVEL; lvl <= HIGHEST_CHUNK_LEVEL; lvl++) {
Metachunk* c = NULL;
context.alloc_chunk_expect_success(&c, lvl);
context.uncommit_chunk_with_test(c); // start out fully uncommitted
// buddies are adjacent in memory // (next/prev_in_vs needs lock)
{
MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
EXPECT_EQ(c1->next_in_vs(), c2);
EXPECT_EQ(c1->end(), c2->base());
EXPECT_NULL(c1->prev_in_vs()); // since we know this is the first in the area
EXPECT_EQ(c2->prev_in_vs(), c1);
}
// A big chunk, but uncommitted.
Metachunk* c = NULL;
context.alloc_chunk_expect_success(&c, ROOT_CHUNK_LEVEL, ROOT_CHUNK_LEVEL, 0);
context.uncommit_chunk_with_test(c); // ... just to make sure.
// first granule...
context.commit_chunk_with_test(c, granule_sz);
context.allocate_from_chunk(c, granule_sz);
// second granule...
context.commit_chunk_with_test(c, granule_sz);
context.allocate_from_chunk(c, granule_sz);
// third granule...
context.commit_chunk_with_test(c, granule_sz);
context.allocate_from_chunk(c, granule_sz);
// This should fail now.
context.commit_chunk_expect_failure(c, granule_sz);
context.return_chunk(c);
}
// Test splitting a chunk
TEST_VM(metaspace, chunk_split_and_merge) {
// Split works like this: // // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // | A | // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // | A' | b | c | d | e | // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // // A original chunk (A) is split to form a target chunk (A') and as a result splinter // chunks form (b..e). A' is the leader of the (A',b) pair, which is the leader of the // ((A',b), c) pair and so on. In other words, A' will be a leader chunk, all splinter // chunks are follower chunks. // // Merging reverses this operation: // // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // | A | b | c | d | e | // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // | A' | // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- // // (A) will be merged with its buddy b, (A+b) with its buddy c and so on. The result // chunk is A'. // Note that merging also works, of course, if we were to start the merge at (b) (so, // with a follower chunk, not a leader). Also, at any point in the merge // process we may arrive at a follower chunk. So, the fact that in this test // we only expect a leader merge is a feature of the test, and of the fact that we // start each split test with a fresh ChunkTestsContext.
// Note: Splitting and merging chunks is usually done from within the ChunkManager and // subject to a lot of assumptions and hence asserts. Here, we have to explicitly use // VirtualSpaceNode::split/::merge and therefore have to observe rules: // - both split and merge expect free chunks, so state has to be "free" // - but that would trigger the "ideally merged" assertion in the RootChunkArea, so the // original chunk has to be a root chunk, we cannot just split any chunk manually. // - Also, after the split we have to completely re-merge to avoid triggering asserts // in ~RootChunkArea() // - finally we have to lock manually
// Split a fully committed chunk. The resulting chunk should be fully // committed as well, and have its content preserved.
Metachunk* c = NULL;
context.alloc_chunk_expect_success(&c, orig_lvl);
// We allocate from this chunk to be able to completely paint the payload.
context.allocate_from_chunk(c, c->word_size());
{ // Splitting/Merging chunks is usually done by the chunkmanager, and no explicit // outside API exists. So we split/merge chunks via the underlying vs node, directly. // This means that we have to go through some extra hoops to not trigger any asserts.
MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
c->reset_used_words();
c->set_free();
c->vsnode()->split(target_lvl, c, &splinters);
}
// I expect splinter chunks (one for each splinter level: // e.g. splitting a 1M chunk to get a 64K chunk should yield splinters: [512K, 256K, 128K, 64K] for (chunklevel_t l = LOWEST_CHUNK_LEVEL; l < HIGHEST_CHUNK_LEVEL; l++) { const Metachunk* c2 = splinters.first_at_level(l); if (l > orig_lvl && l <= target_lvl) {
EXPECT_NOT_NULL(c2);
EXPECT_EQ(c2->level(), l);
EXPECT_TRUE(c2->is_free());
EXPECT_TRUE(!c2->is_leader());
DEBUG_ONLY(c2->verify());
check_range_for_pattern(c2->base(), c2->word_size(), canary);
} else {
EXPECT_NULL(c2);
}
}
// Revert the split by using merge. This should result in all splinters coalescing // to one chunk.
{
MutexLocker fcl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
Metachunk* merged = c->vsnode()->merge(c, &splinters);
// the merged chunk should occupy the same address as the splinter // since it should have been the leader in the split.
EXPECT_EQ(merged, c);
EXPECT_TRUE(merged->is_root_chunk() || merged->is_leader());
// Splitting should have arrived at the original chunk since none of the splinters are in use.
EXPECT_EQ(c->level(), orig_lvl);
// All splinters should have been removed from the list
EXPECT_EQ(splinters.num_chunks(), 0);
}
context.return_chunk(c);
}
}
TEST_VM(metaspace, chunk_enlarge_in_place) {
ChunkGtestContext context;
// Starting with the smallest chunk size, attempt to enlarge the chunk in place until we arrive // at root chunk size. Since the state is clean, this should work.
Metachunk* c = NULL;
context.alloc_chunk_expect_success(&c, HIGHEST_CHUNK_LEVEL);
chunklevel_t l = c->level();
while (l != ROOT_CHUNK_LEVEL) {
// commit and allocate from chunk to pattern it... const size_t original_chunk_size = c->word_size();
context.commit_chunk_with_test(c, c->free_words());
context.allocate_from_chunk(c, c->free_words());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.