--- /dev/null 2020-09-04 12:37:41.765504620 +0200 +++ new/src/hotspot/share/memory/metaspace/msMetachunk.cpp 2020-09-04 13:57:57.389398574 +0200 @@ -0,0 +1,358 @@ +/* + * Copyright (c) 2012, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2020 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 "precompiled.hpp" + +#include "logging/log.hpp" +#include "memory/metaspace/msCommon.hpp" +#include "memory/metaspace/msMetachunk.hpp" +#include "memory/metaspace/msSettings.hpp" +#include "memory/metaspace/msVirtualSpaceNode.hpp" +#include "runtime/mutexLocker.hpp" + +#include "utilities/align.hpp" +#include "utilities/copy.hpp" +#include "utilities/debug.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(MetaspaceExpand_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(MetaspaceExpand_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 " + SIZE_FORMAT " 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(MetaspaceExpand_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(MetaspaceExpand_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(MetaspaceExpand_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(MetaspaceExpand_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(MetaspaceExpand_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() == NULL || prev_in_vs() == NULL, "Root chunks should have no neighbors"); + + } else { + + // Non-root chunks have neighbors, at least one, possibly two. + + assert(next_in_vs() != NULL || prev_in_vs() != NULL, + "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() != NULL) { + 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() != NULL) { + 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 preceeding us may be our buddy or a splintered part of it. + Metachunk* buddy = is_leader() ? next_in_vs() : prev_in_vs(); + + assert(buddy != NULL, "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. + + // assert(buddy->is_in_use() || is_in_use(), "incomplete merging?"); + + 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 = 0; + +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() != NULL, "No base ptr"); + + assert(committed_words() >= used_words(), + "mismatch: committed: " SIZE_FORMAT ", used: " SIZE_FORMAT ".", + committed_words(), used_words()); + + assert(word_size() >= committed_words(), + "mismatch: word_size: " SIZE_FORMAT ", committed: " SIZE_FORMAT ".", + word_size(), committed_words()); + + // Test base pointer + assert(base() != NULL, "Base pointer NULL"); + assert(vsnode() != NULL, "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. + 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 // 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 " (" SIZE_FORMAT " words), " + "used " SIZE_FORMAT " words, committed " SIZE_FORMAT " words.", + p2i(this), get_state_char(), p2i(base()), level(), + (chunklevel::is_valid_level(level()) ? chunklevel::word_size_for_level(level()) : (size_t)-1), + used_words(), committed_words()); + +} + +} // namespace metaspace +