/* * Copyright (c) 2017, 2018, 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 "jfr/recorder/storage/jfrVirtualMemory.hpp" #include "memory/virtualspace.hpp" #include "runtime/orderAccess.hpp" #include "runtime/os.hpp" #include "services/memTracker.hpp" #include "utilities/globalDefinitions.hpp" /* * A memory segment represents a virtual memory reservation. * It provides ways to commit and decommit physical storage * onto its virtual memory reservation. */ class JfrVirtualMemorySegment : public JfrCHeapObj { friend class JfrVirtualMemoryManager; private: JfrVirtualMemorySegment* _next; char* _top; ReservedSpace _rs; VirtualSpace _virtual_memory; // Convenience functions to access the underlying virtual space metadata const u1* committed_low() const { return (const u1*)_virtual_memory.low(); } const u1* committed_high() const { return (const u1*)_virtual_memory.high(); } const u1* reserved_low() const { return (const u1*)_virtual_memory.low_boundary(); } const u1* reserved_high() const { return (const u1*)_virtual_memory.high_boundary(); } size_t reserved_words() const { return _virtual_memory.reserved_size() / BytesPerWord; } size_t committed_words() const { return _virtual_memory.actual_committed_size() / BytesPerWord; } bool is_pre_committed() const { return _virtual_memory.special(); } VirtualSpace& virtual_space() { return _virtual_memory; } JfrVirtualMemorySegment(); ~JfrVirtualMemorySegment(); JfrVirtualMemorySegment* next() const { return _next; } void set_next(JfrVirtualMemorySegment* v) { _next = v; } // Returns true if requested size is available in the committed area bool is_available(size_t block_size_request_words) { return block_size_request_words <= pointer_delta(committed_high(), _top, sizeof(char*)); } // allocation pointer committed memory char* top() const { return _top; } void inc_top(size_t size_in_words) { assert(is_available(size_in_words), "invariant"); _top += size_in_words * BytesPerWord; assert(_top <= _virtual_memory.high(), "invariant"); } // initialization is the virtual memory reservation bool initialize(size_t reservation_size_request_bytes); void* take_from_committed(size_t block_size_request_words); // Returns committed memory void* commit(size_t block_size_request_words) { return take_from_committed(block_size_request_words); } // Commit more memory in a reservation bool expand_by(size_t block_size_request_words); // Decommits all committed memory in this reservation segment. void decommit(); }; JfrVirtualMemorySegment::JfrVirtualMemorySegment() : _next(NULL), _top(NULL), _rs(), _virtual_memory() {} JfrVirtualMemorySegment::~JfrVirtualMemorySegment() { decommit(); _rs.release(); } bool JfrVirtualMemorySegment::initialize(size_t reservation_size_request_bytes) { assert(is_aligned(reservation_size_request_bytes, os::vm_allocation_granularity()), "invariant"); _rs = ReservedSpace(reservation_size_request_bytes, os::vm_allocation_granularity(), UseLargePages && os::can_commit_large_page_memory(), false); if (!_rs.is_reserved()) { return false; } assert(_rs.base() != NULL, "invariant"); assert(_rs.size() != 0, "invariant"); assert(is_aligned(_rs.base(), os::vm_allocation_granularity()), "invariant"); assert(is_aligned(_rs.size(), os::vm_allocation_granularity()), "invariant"); os::trace_page_sizes("Jfr", reservation_size_request_bytes, reservation_size_request_bytes, os::vm_page_size(), _rs.base(), _rs.size()); MemTracker::record_virtual_memory_type((address)_rs.base(), mtTracing); assert(is_aligned(_rs.base(), os::vm_page_size()), "invariant"); assert(is_aligned(_rs.size(), os::vm_page_size()), "invariant"); // ReservedSpaces marked as special will have the entire memory // pre-committed. Setting a committed size will make sure that // committed_size and actual_committed_size agrees. const size_t pre_committed_size = _rs.special() ? _rs.size() : 0; const bool result = virtual_space().initialize_with_granularity(_rs, pre_committed_size, os::vm_page_size()); if (result) { assert(virtual_space().committed_size() == virtual_space().actual_committed_size(), "Checking that the pre-committed memory was registered by the VirtualSpace"); _top = virtual_space().low(); } return result; } bool JfrVirtualMemorySegment::expand_by(size_t block_size_request_words) { size_t block_size_request_bytes = block_size_request_words * BytesPerWord; const size_t uncommitted = virtual_space().reserved_size() - virtual_space().actual_committed_size(); if (uncommitted < block_size_request_bytes) { // commit whatever is left in the reservation block_size_request_bytes = uncommitted; } assert(is_aligned(block_size_request_bytes, os::vm_allocation_granularity()), "invariant"); // commit block in reserved memory bool result = virtual_space().expand_by(block_size_request_bytes, false); assert(result, "Failed to commit memory"); return result; } void JfrVirtualMemorySegment::decommit() { assert(_virtual_memory.committed_size() == _virtual_memory.actual_committed_size(), "The committed memory doesn't match the expanded memory."); const size_t committed_size = virtual_space().actual_committed_size(); if (committed_size > 0) { virtual_space().shrink_by(committed_size); } assert(_virtual_memory.actual_committed_size() == 0, "invariant"); } // Attempt to get a committed block void* JfrVirtualMemorySegment::take_from_committed(size_t block_size_request_words) { // The virtual spaces are always expanded by the // commit granularity to enforce the following condition. // Without this the is_available check will not work correctly. assert(_virtual_memory.committed_size() == _virtual_memory.actual_committed_size(), "The committed memory doesn't match the expanded memory."); if (!is_available(block_size_request_words)) { return NULL; } void* const block = top(); assert(block != NULL, "invariant"); inc_top(block_size_request_words); return block; } class JfrVirtualMemoryManager : public JfrCHeapObj { typedef JfrVirtualMemorySegment Segment; private: Segment* _segments; Segment* _current_segment; size_t _reservation_size_request_words; size_t _reservation_size_request_limit_words; // total reservation limit // Sum of reserved and committed memory in the segments size_t _current_reserved_words; size_t _current_committed_words; void link(Segment* segment); Segment* current(); void inc_reserved_words(size_t words); void inc_committed_words(size_t words); bool new_segment(size_t reservation_size_request_words); bool expand_segment_by(Segment* segment, size_t block_size_request_words); bool expand_by(size_t block_size_request_words, size_t reservation_size_request_words); bool can_reserve() const; public: JfrVirtualMemoryManager(); ~JfrVirtualMemoryManager(); bool initialize(size_t reservation_size_request_words, size_t segment_count = 1); void* commit(size_t requested_block_size_words); bool is_full() const { return reserved_high() == committed_high(); } const u1* committed_low() const { return _current_segment->committed_low(); } const u1* committed_high() const { return _current_segment->committed_high(); } const u1* reserved_low() const { return _current_segment->reserved_low(); } const u1* reserved_high() const { return _current_segment->reserved_high(); } }; JfrVirtualMemoryManager::JfrVirtualMemoryManager() : _segments(NULL), _current_segment(NULL), _reservation_size_request_words(0), _reservation_size_request_limit_words(0), _current_reserved_words(0), _current_committed_words(0) {} JfrVirtualMemoryManager::~JfrVirtualMemoryManager() { JfrVirtualMemorySegment* segment = _segments; while (segment != NULL) { JfrVirtualMemorySegment* next_segment = segment->next(); delete segment; segment = next_segment; } } // for now only allow a singleton segment per virtual memory client bool JfrVirtualMemoryManager::initialize(size_t reservation_size_request_words, size_t segment_count /* 1 */) { assert(is_aligned(reservation_size_request_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); _reservation_size_request_words = reservation_size_request_words; assert(segment_count > 0, "invariant"); _reservation_size_request_limit_words = reservation_size_request_words * segment_count; assert(is_aligned(_reservation_size_request_limit_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); return new_segment(_reservation_size_request_words); } bool JfrVirtualMemoryManager::can_reserve() const { return _reservation_size_request_limit_words == 0 ? true : _current_reserved_words < _reservation_size_request_limit_words; } // Allocate another segment and add it to the list. bool JfrVirtualMemoryManager::new_segment(size_t reservation_size_request_words) { assert(reservation_size_request_words > 0, "invariant"); assert(is_aligned(reservation_size_request_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); Segment* segment = new Segment(); if (NULL == segment) { return false; } if (!segment->initialize(reservation_size_request_words * BytesPerWord)) { delete segment; return false; } assert(segment->reserved_words() == reservation_size_request_words, "Actual reserved memory size differs from requested reservation memory size"); link(segment); return true; } bool JfrVirtualMemoryManager::expand_segment_by(JfrVirtualMemorySegment* segment, size_t block_size_request_words) { assert(segment != NULL, "invariant"); const size_t before = segment->committed_words(); const bool result = segment->expand_by(block_size_request_words); const size_t after = segment->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; } void JfrVirtualMemoryManager::inc_reserved_words(size_t words) { _current_reserved_words += words; } JfrVirtualMemorySegment* JfrVirtualMemoryManager::current() { return _current_segment; } void JfrVirtualMemoryManager::inc_committed_words(size_t words) { _current_committed_words += words; } bool JfrVirtualMemoryManager::expand_by(size_t block_size_request_words, size_t reservation_size_request_words) { assert(is_aligned(block_size_request_words * BytesPerWord, os::vm_page_size()), "invariant"); assert(is_aligned(block_size_request_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); assert(is_aligned(reservation_size_request_words * BytesPerWord, os::vm_page_size()), "invariant"); assert(is_aligned(reservation_size_request_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); assert(block_size_request_words <= reservation_size_request_words, "invariant"); // Attempt to commit more memory from the the current virtual space reservation. if (expand_segment_by(current(), block_size_request_words)) { return true; } // reached limit of what is allowed to be reserved? if (!can_reserve()) { return false; } // Get another segment. if (!new_segment(reservation_size_request_words)) { return false; } if (current()->is_pre_committed()) { // The memory was pre-committed, so we are done here. assert(block_size_request_words <= current()->committed_words(), "The new VirtualSpace was pre-committed, so it" "should be large enough to fit the alloc request."); return true; } return expand_segment_by(current(), block_size_request_words); } void JfrVirtualMemoryManager::link(JfrVirtualMemorySegment* segment) { assert(segment != NULL, "invariant"); if (_segments == NULL) { _segments = segment; } else { assert(_current_segment != NULL, "invariant"); assert(_segments == _current_segment, "invariant"); _current_segment->set_next(segment); } _current_segment = segment; inc_reserved_words(segment->reserved_words()); inc_committed_words(segment->committed_words()); } void* JfrVirtualMemoryManager::commit(size_t block_size_request_words) { assert(is_aligned(block_size_request_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); void* block = current()->commit(block_size_request_words); if (block != NULL) { return block; } assert(block == NULL, "invariant"); if (is_full()) { return NULL; } assert(block_size_request_words <= _reservation_size_request_words, "invariant"); if (expand_by(block_size_request_words, _reservation_size_request_words)) { block = current()->commit(block_size_request_words); assert(block != NULL, "The allocation was expected to succeed after the expansion"); } return block; } JfrVirtualMemory::JfrVirtualMemory() : _vmm(NULL), _reserved_low(), _reserved_high(), _top(NULL), _commit_point(NULL), _physical_commit_size_request_words(0), _aligned_datum_size_bytes(0) {} JfrVirtualMemory::~JfrVirtualMemory() { assert(_vmm != NULL, "invariant"); delete _vmm; } size_t JfrVirtualMemory::aligned_datum_size_bytes() const { return _aligned_datum_size_bytes; } static void adjust_allocation_ratio(size_t* const reservation_size_bytes, size_t* const commit_size_bytes) { assert(reservation_size_bytes != NULL, "invariant"); assert(*reservation_size_bytes > 0, "invariant"); assert(commit_size_bytes != NULL, "invariant"); assert(*commit_size_bytes > 0, "invariant"); assert(*reservation_size_bytes >= *commit_size_bytes, "invariant"); assert(is_aligned(*reservation_size_bytes, os::vm_allocation_granularity()), "invariant"); assert(is_aligned(*commit_size_bytes, os::vm_allocation_granularity()), "invariant"); size_t reservation_size_units = *reservation_size_bytes / os::vm_allocation_granularity(); size_t commit_size_units = *commit_size_bytes / os::vm_allocation_granularity(); assert(reservation_size_units > 0, "invariant"); assert(commit_size_units > 0, "invariant"); size_t original_ratio_units = reservation_size_units / commit_size_units; size_t rem = reservation_size_units % commit_size_units; assert(original_ratio_units > 0, "invariant"); if (rem > 0) { reservation_size_units -= rem % original_ratio_units; commit_size_units += rem / original_ratio_units; } assert(commit_size_units > 0, "invariant"); assert(reservation_size_units % original_ratio_units == 0, "invariant"); assert(original_ratio_units * commit_size_units == reservation_size_units , "invariant"); assert(original_ratio_units == reservation_size_units / commit_size_units, "invariant"); *reservation_size_bytes = reservation_size_units * os::vm_allocation_granularity(); *commit_size_bytes = commit_size_units * os::vm_allocation_granularity(); assert((*reservation_size_bytes % *commit_size_bytes) == 0, "invariant"); } void* JfrVirtualMemory::initialize(size_t reservation_size_request_bytes, size_t block_size_request_bytes, size_t datum_size_bytes /* 1 */) { assert(_vmm == NULL, "invariant"); _vmm = new JfrVirtualMemoryManager(); if (_vmm == NULL) { return NULL; } assert(reservation_size_request_bytes > 0, "invariant"); _aligned_datum_size_bytes = align_up(datum_size_bytes, BytesPerWord); assert(is_aligned(_aligned_datum_size_bytes, BytesPerWord), "invariant"); reservation_size_request_bytes = ReservedSpace::allocation_align_size_up(reservation_size_request_bytes); assert(is_aligned(reservation_size_request_bytes, os::vm_allocation_granularity()), "invariant"); assert(is_aligned(reservation_size_request_bytes, _aligned_datum_size_bytes), "invariant"); block_size_request_bytes = MAX2(block_size_request_bytes, (size_t)os::vm_allocation_granularity()); block_size_request_bytes = ReservedSpace::allocation_align_size_up(block_size_request_bytes); assert(is_aligned(block_size_request_bytes, os::vm_allocation_granularity()), "invariant"); assert(is_aligned(block_size_request_bytes, _aligned_datum_size_bytes), "invariant"); // adjustment to valid ratio in units of vm_allocation_granularity adjust_allocation_ratio(&reservation_size_request_bytes, &block_size_request_bytes); assert(is_aligned(reservation_size_request_bytes, os::vm_allocation_granularity()), "invariant"); assert(is_aligned(reservation_size_request_bytes, _aligned_datum_size_bytes), "invariant"); assert(is_aligned(block_size_request_bytes, os::vm_allocation_granularity()), "invariant"); assert(is_aligned(block_size_request_bytes, _aligned_datum_size_bytes), "invariant"); assert((reservation_size_request_bytes % block_size_request_bytes) == 0, "invariant"); const size_t reservation_size_request_words = reservation_size_request_bytes / BytesPerWord; _physical_commit_size_request_words = block_size_request_bytes / BytesPerWord; // virtual memory reservation if (!_vmm->initialize(reservation_size_request_words)) { // is implicitly "full" if reservation fails assert(is_full(), "invariant"); return NULL; } _reserved_low = (const u1*)_vmm->reserved_low(); _reserved_high = (const u1*)_vmm->reserved_high(); // reservation complete _top = (u1*)_vmm->committed_high(); _commit_point = _top; assert(_reserved_low == _top, "invariant"); // initial empty state assert((size_t)(_reserved_high - _reserved_low) == reservation_size_request_bytes, "invariant"); // initial commit commit_memory_block(); return _top; } void* JfrVirtualMemory::commit(size_t block_size_request_words) { assert(_vmm != NULL, "invariant"); assert(is_aligned(block_size_request_words * BytesPerWord, os::vm_allocation_granularity()), "invariant"); return _vmm->commit(block_size_request_words); } bool JfrVirtualMemory::is_full() const { return _top == _reserved_high; } bool JfrVirtualMemory::is_empty() const { return _top == _reserved_low; } bool JfrVirtualMemory::commit_memory_block() { assert(_vmm != NULL, "invariant"); assert(!is_full(), "invariant"); assert(_top == _commit_point, "invariant"); void* const block = _vmm->commit(_physical_commit_size_request_words); if (block != NULL) { _commit_point = _vmm->committed_high(); return true; } // all reserved virtual memory is committed assert(block == NULL, "invariant"); assert(_vmm->reserved_high() == _vmm->committed_high(), "invariant"); return false; } void* JfrVirtualMemory::new_datum() { assert(_vmm != NULL, "invariant"); assert(!is_full(), "invariant"); if (_top == _commit_point) { if (!commit_memory_block()) { assert(is_full(), "invariant"); return NULL; } } assert(_top + _aligned_datum_size_bytes <= _commit_point, "invariant"); u1* allocation = _top; _top += _aligned_datum_size_bytes; assert(is_aligned(allocation, _aligned_datum_size_bytes), "invariant"); return allocation; } void* JfrVirtualMemory::index_ptr(size_t index) { assert((index * _aligned_datum_size_bytes) + _reserved_low < _commit_point, "invariant"); return (void*)((index * _aligned_datum_size_bytes) + _reserved_low); } void* JfrVirtualMemory::get(size_t index) { return index_ptr(index); } size_t JfrVirtualMemory::count() const { return (_top - _reserved_low) / _aligned_datum_size_bytes; } size_t JfrVirtualMemory::live_set() const { return _top - _reserved_low; } size_t JfrVirtualMemory::reserved_size() const { return _reserved_high - _reserved_low; } bool JfrVirtualMemory::compact(size_t index) { assert(index > 0, "invariant"); assert(index <= reserved_size(), "invariant"); const u1* low = static_cast(index_ptr(index)); const size_t block_size = _top - low; memcpy(const_cast(_reserved_low), low, block_size); _top = const_cast(_reserved_low) + block_size; assert(live_set() == block_size, "invariant"); return true; }