/*
* Copyright (c) 2018, 2019, Oracle and/or its affiliates. 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 "logging/logStream.hpp"
#include "memory/metaspace.hpp"
#include "memory/metaspace/chunkManager.hpp"
#include "memory/metaspace/metachunk.hpp"
#include "memory/metaspace/metaspaceCommon.hpp"
#include "memory/metaspace/virtualSpaceList.hpp"
#include "memory/metaspace/virtualSpaceNode.hpp"
#include "runtime/orderAccess.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/safepoint.hpp"
namespace metaspace {
VirtualSpaceList::~VirtualSpaceList() {
VirtualSpaceListIterator iter(virtual_space_list());
while (iter.repeat()) {
VirtualSpaceNode* vsl = iter.get_next();
delete vsl;
}
}
void VirtualSpaceList::inc_reserved_words(size_t v) {
assert_lock_strong(MetaspaceExpand_lock);
_reserved_words = _reserved_words + v;
}
void VirtualSpaceList::dec_reserved_words(size_t v) {
assert_lock_strong(MetaspaceExpand_lock);
_reserved_words = _reserved_words - v;
}
#define assert_committed_below_limit() \
assert(MetaspaceUtils::committed_bytes() <= MaxMetaspaceSize, \
"Too much committed memory. Committed: " SIZE_FORMAT \
" limit (MaxMetaspaceSize): " SIZE_FORMAT, \
MetaspaceUtils::committed_bytes(), MaxMetaspaceSize);
void VirtualSpaceList::inc_committed_words(size_t v) {
assert_lock_strong(MetaspaceExpand_lock);
_committed_words = _committed_words + v;
assert_committed_below_limit();
}
void VirtualSpaceList::dec_committed_words(size_t v) {
assert_lock_strong(MetaspaceExpand_lock);
_committed_words = _committed_words - v;
assert_committed_below_limit();
}
void VirtualSpaceList::inc_virtual_space_count() {
assert_lock_strong(MetaspaceExpand_lock);
_virtual_space_count++;
}
void VirtualSpaceList::dec_virtual_space_count() {
assert_lock_strong(MetaspaceExpand_lock);
_virtual_space_count--;
}
// Walk the list of VirtualSpaceNodes and delete
// nodes with a 0 container_count. Remove Metachunks in
// the node from their respective freelists.
void VirtualSpaceList::purge(ChunkManager* chunk_manager) {
assert_lock_strong(MetaspaceExpand_lock);
// Don't use a VirtualSpaceListIterator because this
// list is being changed and a straightforward use of an iterator is not safe.
VirtualSpaceNode* prev_vsl = virtual_space_list();
VirtualSpaceNode* next_vsl = prev_vsl;
int num_purged_nodes = 0;
while (next_vsl != NULL) {
VirtualSpaceNode* vsl = next_vsl;
DEBUG_ONLY(vsl->verify(false);)
next_vsl = vsl->next();
// Don't free the current virtual space since it will likely
// be needed soon.
if (vsl->container_count() == 0 && vsl != current_virtual_space()) {
log_trace(gc, metaspace, freelist)("Purging VirtualSpaceNode " PTR_FORMAT " (capacity: " SIZE_FORMAT
", used: " SIZE_FORMAT ").", p2i(vsl), vsl->capacity_words_in_vs(), vsl->used_words_in_vs());
DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_vsnodes_purged));
// Unlink it from the list
if (prev_vsl == vsl) {
// This is the case of the current node being the first node.
assert(vsl == virtual_space_list(), "Expected to be the first node");
set_virtual_space_list(vsl->next());
} else {
prev_vsl->set_next(vsl->next());
}
vsl->purge(chunk_manager);
dec_reserved_words(vsl->reserved_words());
dec_committed_words(vsl->committed_words());
dec_virtual_space_count();
delete vsl;
num_purged_nodes ++;
} else {
prev_vsl = vsl;
}
}
// Verify list
#ifdef ASSERT
if (num_purged_nodes > 0) {
verify(false);
}
#endif
}
// This function looks at the mmap regions in the metaspace without locking.
// The chunks are added with store ordering and not deleted except for at
// unloading time during a safepoint.
VirtualSpaceNode* VirtualSpaceList::find_enclosing_space(const void* ptr) {
// List should be stable enough to use an iterator here because removing virtual
// space nodes is only allowed at a safepoint.
if (is_within_envelope((address)ptr)) {
VirtualSpaceListIterator iter(virtual_space_list());
while (iter.repeat()) {
VirtualSpaceNode* vsn = iter.get_next();
if (vsn->contains(ptr)) {
return vsn;
}
}
}
return NULL;
}
void VirtualSpaceList::retire_current_virtual_space() {
assert_lock_strong(MetaspaceExpand_lock);
VirtualSpaceNode* vsn = current_virtual_space();
ChunkManager* cm = is_class() ? Metaspace::chunk_manager_class() :
Metaspace::chunk_manager_metadata();
vsn->retire(cm);
}
VirtualSpaceList::VirtualSpaceList(size_t word_size) :
_virtual_space_list(NULL),
_current_virtual_space(NULL),
_is_class(false),
_reserved_words(0),
_committed_words(0),
_virtual_space_count(0),
_envelope_lo((address)max_uintx),
_envelope_hi(NULL) {
MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
create_new_virtual_space(word_size);
}
VirtualSpaceList::VirtualSpaceList(ReservedSpace rs) :
_virtual_space_list(NULL),
_current_virtual_space(NULL),
_is_class(true),
_reserved_words(0),
_committed_words(0),
_virtual_space_count(0),
_envelope_lo((address)max_uintx),
_envelope_hi(NULL) {
MutexLocker cl(MetaspaceExpand_lock, Mutex::_no_safepoint_check_flag);
VirtualSpaceNode* class_entry = new VirtualSpaceNode(is_class(), rs);
bool succeeded = class_entry->initialize();
if (succeeded) {
expand_envelope_to_include_node(class_entry);
// ensure lock-free iteration sees fully initialized node
OrderAccess::storestore();
link_vs(class_entry);
}
}
size_t VirtualSpaceList::free_bytes() {
return current_virtual_space()->free_words_in_vs() * BytesPerWord;
}
// Allocate another meta virtual space and add it to the list.
bool VirtualSpaceList::create_new_virtual_space(size_t vs_word_size) {
assert_lock_strong(MetaspaceExpand_lock);
if (is_class()) {
assert(false, "We currently don't support more than one VirtualSpace for"
" the compressed class space. The initialization of the"
" CCS uses another code path and should not hit this path.");
return false;
}
if (vs_word_size == 0) {
assert(false, "vs_word_size should always be at least _reserve_alignment large.");
return false;
}
// Reserve the space
size_t vs_byte_size = vs_word_size * BytesPerWord;
assert_is_aligned(vs_byte_size, Metaspace::reserve_alignment());
// Allocate the meta virtual space and initialize it.
VirtualSpaceNode* new_entry = new VirtualSpaceNode(is_class(), vs_byte_size);
if (!new_entry->initialize()) {
delete new_entry;
return false;
} else {
assert(new_entry->reserved_words() == vs_word_size,
"Reserved memory size differs from requested memory size");
expand_envelope_to_include_node(new_entry);
// ensure lock-free iteration sees fully initialized node
OrderAccess::storestore();
link_vs(new_entry);
DEBUG_ONLY(Atomic::inc(&g_internal_statistics.num_vsnodes_created));
return true;
}
DEBUG_ONLY(verify(false);)
}
void VirtualSpaceList::link_vs(VirtualSpaceNode* new_entry) {
if (virtual_space_list() == NULL) {
set_virtual_space_list(new_entry);
} else {
current_virtual_space()->set_next(new_entry);
}
set_current_virtual_space(new_entry);
inc_reserved_words(new_entry->reserved_words());
inc_committed_words(new_entry->committed_words());
inc_virtual_space_count();
#ifdef ASSERT
new_entry->mangle();
#endif
LogTarget(Trace, gc, metaspace) lt;
if (lt.is_enabled()) {
LogStream ls(lt);
VirtualSpaceNode* vsl = current_virtual_space();
ResourceMark rm;
vsl->print_on(&ls);
}
}
bool VirtualSpaceList::expand_node_by(VirtualSpaceNode* node,
size_t min_words,
size_t preferred_words) {
size_t before = node->committed_words();
bool result = node->expand_by(min_words, preferred_words);
size_t after = node->committed_words();
// after and before can be the same if the memory was pre-committed.
assert(after >= before, "Inconsistency");
inc_committed_words(after - before);
return result;
}
bool VirtualSpaceList::expand_by(size_t min_words, size_t preferred_words) {
assert_is_aligned(min_words, Metaspace::commit_alignment_words());
assert_is_aligned(preferred_words, Metaspace::commit_alignment_words());
assert(min_words <= preferred_words, "Invalid arguments");
const char* const class_or_not = (is_class() ? "class" : "non-class");
if (!MetaspaceGC::can_expand(min_words, this->is_class())) {
log_trace(gc, metaspace, freelist)("Cannot expand %s virtual space list.",
class_or_not);
return false;
}
size_t allowed_expansion_words = MetaspaceGC::allowed_expansion();
if (allowed_expansion_words < min_words) {
log_trace(gc, metaspace, freelist)("Cannot expand %s virtual space list (must try gc first).",
class_or_not);
return false;
}
size_t max_expansion_words = MIN2(preferred_words, allowed_expansion_words);
// Commit more memory from the the current virtual space.
bool vs_expanded = expand_node_by(current_virtual_space(),
min_words,
max_expansion_words);
if (vs_expanded) {
log_trace(gc, metaspace, freelist)("Expanded %s virtual space list.",
class_or_not);
return true;
}
log_trace(gc, metaspace, freelist)("%s virtual space list: retire current node.",
class_or_not);
retire_current_virtual_space();
// Get another virtual space.
size_t grow_vs_words = MAX2((size_t)VirtualSpaceSize, preferred_words);
grow_vs_words = align_up(grow_vs_words, Metaspace::reserve_alignment_words());
if (create_new_virtual_space(grow_vs_words)) {
if (current_virtual_space()->is_pre_committed()) {
// The memory was pre-committed, so we are done here.
assert(min_words <= current_virtual_space()->committed_words(),
"The new VirtualSpace was pre-committed, so it"
"should be large enough to fit the alloc request.");
return true;
}
return expand_node_by(current_virtual_space(),
min_words,
max_expansion_words);
}
return false;
}
// Given a chunk, calculate the largest possible padding space which
// could be required when allocating it.
static size_t largest_possible_padding_size_for_chunk(size_t chunk_word_size, bool is_class) {
const ChunkIndex chunk_type = get_chunk_type_by_size(chunk_word_size, is_class);
if (chunk_type != HumongousIndex) {
// Normal, non-humongous chunks are allocated at chunk size
// boundaries, so the largest padding space required would be that
// minus the smallest chunk size.
const size_t smallest_chunk_size = is_class ? ClassSpecializedChunk : SpecializedChunk;
return chunk_word_size - smallest_chunk_size;
} else {
// Humongous chunks are allocated at smallest-chunksize
// boundaries, so there is no padding required.
return 0;
}
}
Metachunk* VirtualSpaceList::get_new_chunk(size_t chunk_word_size, size_t suggested_commit_granularity) {
// Allocate a chunk out of the current virtual space.
Metachunk* next = current_virtual_space()->get_chunk_vs(chunk_word_size);
if (next != NULL) {
return next;
}
// The expand amount is currently only determined by the requested sizes
// and not how much committed memory is left in the current virtual space.
// We must have enough space for the requested size and any
// additional reqired padding chunks.
const size_t size_for_padding = largest_possible_padding_size_for_chunk(chunk_word_size, this->is_class());
size_t min_word_size = align_up(chunk_word_size + size_for_padding, Metaspace::commit_alignment_words());
size_t preferred_word_size = align_up(suggested_commit_granularity, Metaspace::commit_alignment_words());
if (min_word_size >= preferred_word_size) {
// Can happen when humongous chunks are allocated.
preferred_word_size = min_word_size;
}
bool expanded = expand_by(min_word_size, preferred_word_size);
if (expanded) {
next = current_virtual_space()->get_chunk_vs(chunk_word_size);
assert(next != NULL, "The allocation was expected to succeed after the expansion");
}
return next;
}
void VirtualSpaceList::print_on(outputStream* st, size_t scale) const {
st->print_cr(SIZE_FORMAT " nodes, current node: " PTR_FORMAT,
_virtual_space_count, p2i(_current_virtual_space));
VirtualSpaceListIterator iter(virtual_space_list());
while (iter.repeat()) {
st->cr();
VirtualSpaceNode* node = iter.get_next();
node->print_on(st, scale);
}
}
void VirtualSpaceList::print_map(outputStream* st) const {
VirtualSpaceNode* list = virtual_space_list();
VirtualSpaceListIterator iter(list);
unsigned i = 0;
while (iter.repeat()) {
st->print_cr("Node %u:", i);
VirtualSpaceNode* node = iter.get_next();
node->print_map(st, this->is_class());
i ++;
}
}
// Given a node, expand range such that it includes the node.
void VirtualSpaceList::expand_envelope_to_include_node(const VirtualSpaceNode* node) {
_envelope_lo = MIN2(_envelope_lo, (address)node->low_boundary());
_envelope_hi = MAX2(_envelope_hi, (address)node->high_boundary());
}
#ifdef ASSERT
void VirtualSpaceList::verify(bool slow) {
VirtualSpaceNode* list = virtual_space_list();
VirtualSpaceListIterator iter(list);
size_t reserved = 0;
size_t committed = 0;
size_t node_count = 0;
while (iter.repeat()) {
VirtualSpaceNode* node = iter.get_next();
if (slow) {
node->verify(true);
}
// Check that the node resides fully within our envelope.
assert((address)node->low_boundary() >= _envelope_lo && (address)node->high_boundary() <= _envelope_hi,
"Node " SIZE_FORMAT " [" PTR_FORMAT ", " PTR_FORMAT ") outside envelope [" PTR_FORMAT ", " PTR_FORMAT ").",
node_count, p2i(node->low_boundary()), p2i(node->high_boundary()), p2i(_envelope_lo), p2i(_envelope_hi));
reserved += node->reserved_words();
committed += node->committed_words();
node_count ++;
}
assert(reserved == reserved_words() && committed == committed_words() && node_count == _virtual_space_count,
"Mismatch: reserved real: " SIZE_FORMAT " expected: " SIZE_FORMAT
", committed real: " SIZE_FORMAT " expected: " SIZE_FORMAT
", node count real: " SIZE_FORMAT " expected: " SIZE_FORMAT ".",
reserved, reserved_words(), committed, committed_words(),
node_count, _virtual_space_count);
}
#endif // ASSERT
} // namespace metaspace