blob: df3718d7bfa546c5b33ca6be4b78fb56dd0e6129 [file] [edit]
/*
* Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2017, 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.
*
*/
#include "logging/log.hpp"
#include "memory/metaspace/metachunk.hpp"
#include "memory/metaspace/metaspaceCommon.hpp"
#include "memory/metaspace/metaspaceSettings.hpp"
#include "memory/metaspace/virtualSpaceNode.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/os.hpp"
#include "utilities/align.hpp"
#include "utilities/copy.hpp"
#include "utilities/debug.hpp"
#include "utilities/macros.hpp"
#include "utilities/ostream.hpp"
namespace metaspace {
// Return a single char presentation of the state ('f', 'u', 'd')
char Metachunk::get_state_char() const {
switch (_state) {
case State::Free: return 'f';
case State::InUse: return 'u';
case State::Dead: return 'd';
}
return '?';
}
#ifdef ASSERT
void Metachunk::assert_have_expand_lock() {
assert_lock_strong(Metaspace_lock);
}
#endif
// Commit uncommitted section of the chunk.
// Fails if we hit a commit limit.
bool Metachunk::commit_up_to(size_t new_committed_words) {
// Please note:
//
// VirtualSpaceNode::ensure_range_is_committed(), when called over a range containing both committed and uncommitted parts,
// will replace the whole range with a new mapping, thus erasing the existing content in the committed parts. Therefore
// we must make sure never to call VirtualSpaceNode::ensure_range_is_committed() over a range containing live data.
//
// Luckily, this cannot happen by design. We have two cases:
//
// 1) chunks equal or larger than a commit granule.
// In this case, due to chunk geometry, the chunk should cover whole commit granules (in other words, a chunk equal or larger than
// a commit granule will never share a granule with a neighbor). That means whatever we commit or uncommit here does not affect
// neighboring chunks. We only have to take care not to re-commit used parts of ourself. We do this by moving the committed_words
// limit in multiple of commit granules.
//
// 2) chunks smaller than a commit granule.
// In this case, a chunk shares a single commit granule with its neighbors. But this never can be a problem:
// - Either the commit granule is already committed (and maybe the neighbors contain live data). In that case calling
// ensure_range_is_committed() will do nothing.
// - Or the commit granule is not committed, but in this case, the neighbors are uncommitted too and cannot contain live data.
#ifdef ASSERT
if (word_size() >= Settings::commit_granule_words()) {
// case (1)
assert(is_aligned(base(), Settings::commit_granule_bytes()) &&
is_aligned(end(), Settings::commit_granule_bytes()),
"Chunks larger than a commit granule must cover whole granules.");
assert(is_aligned(_committed_words, Settings::commit_granule_words()),
"The commit boundary must be aligned to commit granule size");
assert(_used_words <= _committed_words, "Sanity");
} else {
// case (2)
assert(_committed_words == 0 || _committed_words == word_size(), "Sanity");
}
#endif
// We should hold the expand lock at this point.
assert_lock_strong(Metaspace_lock);
const size_t commit_from = _committed_words;
const size_t commit_to = MIN2(align_up(new_committed_words, Settings::commit_granule_words()), word_size());
assert(commit_from >= used_words(), "Sanity");
assert(commit_to <= word_size(), "Sanity");
if (commit_to > commit_from) {
log_debug(metaspace)("Chunk " METACHUNK_FORMAT ": attempting to move commit line to "
"%zu words.", METACHUNK_FORMAT_ARGS(this), commit_to);
if (!_vsnode->ensure_range_is_committed(base() + commit_from, commit_to - commit_from)) {
DEBUG_ONLY(verify();)
return false;
}
}
// Remember how far we have committed.
_committed_words = commit_to;
DEBUG_ONLY(verify();)
return true;
}
// Ensure that chunk is committed up to at least new_committed_words words.
// Fails if we hit a commit limit.
bool Metachunk::ensure_committed(size_t new_committed_words) {
bool rc = true;
if (new_committed_words > committed_words()) {
MutexLocker cl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
rc = commit_up_to(new_committed_words);
}
return rc;
}
bool Metachunk::ensure_committed_locked(size_t new_committed_words) {
// the .._locked() variant should be called if we own the lock already.
assert_lock_strong(Metaspace_lock);
bool rc = true;
if (new_committed_words > committed_words()) {
rc = commit_up_to(new_committed_words);
}
return rc;
}
// Uncommit chunk area. The area must be a common multiple of the
// commit granule size (in other words, we cannot uncommit chunks smaller than
// a commit granule size).
void Metachunk::uncommit() {
MutexLocker cl(Metaspace_lock, Mutex::_no_safepoint_check_flag);
uncommit_locked();
}
void Metachunk::uncommit_locked() {
// Only uncommit chunks which are free, have no used words set (extra precaution) and are equal or larger in size than a single commit granule.
assert_lock_strong(Metaspace_lock);
assert(_state == State::Free && _used_words == 0 && word_size() >= Settings::commit_granule_words(),
"Only free chunks equal or larger than commit granule size can be uncommitted "
"(chunk " METACHUNK_FULL_FORMAT ").", METACHUNK_FULL_FORMAT_ARGS(this));
if (word_size() >= Settings::commit_granule_words()) {
_vsnode->uncommit_range(base(), word_size());
_committed_words = 0;
}
}
void Metachunk::set_committed_words(size_t v) {
// Set committed words. Since we know that we only commit whole commit granules, we can round up v here.
v = MIN2(align_up(v, Settings::commit_granule_words()), word_size());
_committed_words = v;
}
// Allocate word_size words from this chunk (word_size must be aligned to
// allocation_alignment_words).
//
// Caller must make sure the chunk is both large enough and committed far enough
// to hold the allocation. Will always work.
//
MetaWord* Metachunk::allocate(size_t request_word_size) {
// Caller must have made sure this works
assert(free_words() >= request_word_size, "Chunk too small.");
assert(free_below_committed_words() >= request_word_size, "Chunk not committed.");
MetaWord* const p = top();
_used_words += request_word_size;
SOMETIMES(verify();)
return p;
}
#ifdef ASSERT
// Zap this structure.
void Metachunk::zap_header(uint8_t c) {
memset(this, c, sizeof(Metachunk));
}
// Verifies linking with neighbors in virtual space.
// Can only be done under expand lock protection.
void Metachunk::verify_neighborhood() const {
assert_lock_strong(Metaspace_lock);
assert(!is_dead(), "Do not call on dead chunks.");
if (is_root_chunk()) {
// Root chunks are all alone in the world.
assert(next_in_vs() == nullptr || prev_in_vs() == nullptr, "Root chunks should have no neighbors");
} else {
// Non-root chunks have neighbors, at least one, possibly two.
assert(next_in_vs() != nullptr || prev_in_vs() != nullptr,
"A non-root chunk should have neighbors (chunk @" PTR_FORMAT
", base " PTR_FORMAT ", level " CHKLVL_FORMAT ".",
p2i(this), p2i(base()), level());
if (prev_in_vs() != nullptr) {
assert(prev_in_vs()->end() == base(),
"Chunk " METACHUNK_FULL_FORMAT ": should be adjacent to predecessor: " METACHUNK_FULL_FORMAT ".",
METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(prev_in_vs()));
assert(prev_in_vs()->next_in_vs() == this,
"Chunk " METACHUNK_FULL_FORMAT ": broken link to left neighbor: " METACHUNK_FULL_FORMAT " (" PTR_FORMAT ").",
METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(prev_in_vs()), p2i(prev_in_vs()->next_in_vs()));
}
if (next_in_vs() != nullptr) {
assert(end() == next_in_vs()->base(),
"Chunk " METACHUNK_FULL_FORMAT ": should be adjacent to successor: " METACHUNK_FULL_FORMAT ".",
METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(next_in_vs()));
assert(next_in_vs()->prev_in_vs() == this,
"Chunk " METACHUNK_FULL_FORMAT ": broken link to right neighbor: " METACHUNK_FULL_FORMAT " (" PTR_FORMAT ").",
METACHUNK_FULL_FORMAT_ARGS(this), METACHUNK_FULL_FORMAT_ARGS(next_in_vs()), p2i(next_in_vs()->prev_in_vs()));
}
// One of the neighbors must be the buddy. It can be whole or splintered.
// The chunk following us or preceding us may be our buddy or a splintered part of it.
Metachunk* buddy = is_leader() ? next_in_vs() : prev_in_vs();
assert(buddy != nullptr, "Missing neighbor.");
assert(!buddy->is_dead(), "Invalid buddy state.");
// This neighbor is either or buddy (same level) or a splinter of our buddy - hence
// the level can never be smaller (aka the chunk size cannot be larger).
assert(buddy->level() >= level(), "Wrong level.");
if (buddy->level() == level()) {
// If the buddy is of the same size as us, it is unsplintered.
assert(buddy->is_leader() == !is_leader(),
"Only one chunk can be leader in a pair");
// When direct buddies are neighbors, one or both should be in use, otherwise they should
// have been merged.
// But since we call this verification function from internal functions where we are about to merge or just did split,
// do not test this. We have RootChunkArea::verify_area_is_ideally_merged() for testing that.
if (is_leader()) {
assert(buddy->base() == end(), "Sanity");
assert(is_aligned(base(), word_size() * 2 * BytesPerWord), "Sanity");
} else {
assert(buddy->end() == base(), "Sanity");
assert(is_aligned(buddy->base(), word_size() * 2 * BytesPerWord), "Sanity");
}
} else {
// Buddy, but splintered, and this is a part of it.
if (is_leader()) {
assert(buddy->base() == end(), "Sanity");
} else {
assert(buddy->end() > (base() - word_size()), "Sanity");
}
}
}
}
volatile MetaWord dummy = nullptr;
void Metachunk::verify() const {
// Note. This should be called under CLD lock protection.
// We can verify everything except the _prev_in_vs/_next_in_vs pair.
// This is because neighbor chunks may be added concurrently, so we cannot rely
// on the content of _next_in_vs/_prev_in_vs unless we have the expand lock.
assert(!is_dead(), "Do not call on dead chunks.");
if (is_free()) {
assert(used_words() == 0, "free chunks are not used.");
}
// Note: only call this on a life Metachunk.
chunklevel::check_valid_level(level());
assert(base() != nullptr, "No base ptr");
assert(committed_words() >= used_words(),
"mismatch: committed: %zu, used: %zu.",
committed_words(), used_words());
assert(word_size() >= committed_words(),
"mismatch: word_size: %zu, committed: %zu.",
word_size(), committed_words());
// Test base pointer
assert(base() != nullptr, "Base pointer null");
assert(vsnode() != nullptr, "No space");
vsnode()->check_pointer(base());
// Starting address shall be aligned to chunk size.
const size_t required_alignment = word_size() * sizeof(MetaWord);
assert_is_aligned(base(), required_alignment);
// Test accessing the committed area. But not for ASAN. We don't know which portions
// of the chunk are still poisoned.
#if !INCLUDE_ASAN
SOMETIMES(
if (_committed_words > 0) {
for (const MetaWord* p = _base; p < _base + _committed_words; p += os::vm_page_size()) {
dummy = *p;
}
dummy = *(_base + _committed_words - 1);
}
)
#endif // !INCLUDE_ASAN
}
#endif // ASSERT
void Metachunk::print_on(outputStream* st) const {
// Note: must also work with invalid/random data. (e.g. do not call word_size())
st->print("Chunk @" PTR_FORMAT ", state %c, base " PTR_FORMAT ", "
"level " CHKLVL_FORMAT " (%zu words), "
"used %zu words, committed %zu words.",
p2i(this), get_state_char(), p2i(base()), level(),
(chunklevel::is_valid_level(level()) ? chunklevel::word_size_for_level(level()) : SIZE_MAX),
used_words(), committed_words());
}
} // namespace metaspace