| /* |
| * Copyright (c) 2012, 2025, 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. |
| * |
| */ |
| |
| #ifndef SHARE_MEMORY_METASPACE_METACHUNK_HPP |
| #define SHARE_MEMORY_METASPACE_METACHUNK_HPP |
| |
| #include "memory/metaspace/chunklevel.hpp" |
| #include "memory/metaspace/counters.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| |
| class outputStream; |
| |
| namespace metaspace { |
| |
| class VirtualSpaceNode; |
| |
| // A Metachunk is a contiguous metaspace memory region. It is used by |
| // a MetaspaceArena to allocate from via pointer bump (somewhat similar |
| // to a TLAB in java heap. |
| // |
| // The Metachunk object itself (the "chunk header") is separated from |
| // the memory region (the chunk payload) it describes. It also can have |
| // no payload (a "dead" chunk). In itself it lives in C-heap, managed |
| // as part of a pool of Metachunk headers (ChunkHeaderPool). |
| // |
| // |
| // +---------+ +---------+ +---------+ |
| // |MetaChunk| <--next/prev--> |MetaChunk| <--next/prev--> |MetaChunk| Chunk headers |
| // +---------+ +---------+ +---------+ in C-heap |
| // | | | |
| // base base base |
| // | / | |
| // / --------------- / |
| // / / ---------------------------- |
| // | | / |
| // v v v |
| // +---------+ +---------+ +-------------------+ |
| // | | | | | | |
| // | chunk | | chunk | | chunk | The real chunks ("payload") |
| // | | | | | | live in Metaspace |
| // +---------+ +---------+ +-------------------+ |
| // |
| // |
| // -- Metachunk state -- |
| // |
| // A Metachunk is "in-use" if it is part of a MetaspaceArena. That means |
| // its memory is used - or will be used shortly - to hold VM metadata |
| // on behalf of a class loader. |
| // |
| // A Metachunk is "free" if its payload is currently unused. In that |
| // case it is managed by a chunk freelist (the ChunkManager). |
| // |
| // A Metachunk is "dead" if it does not have a corresponding payload. |
| // In that case it lives as part of a freelist-of-dead-chunk-headers |
| // in the ChunkHeaderPool. |
| // |
| // A Metachunk is always part of a linked list. In-use chunks are part of |
| // the chunk list of a MetaspaceArena. Free chunks are in a freelist in |
| // the ChunkManager. Dead chunk headers are in a linked list as part |
| // of the ChunkHeaderPool. |
| // |
| // |
| // -- Level -- |
| // |
| // Metachunks are managed as part of a buddy style allocation scheme. |
| // Sized always in steps of power-of-2, ranging from the smallest chunk size |
| // (1Kb) to the largest (4Mb) (see chunklevel.hpp). |
| // Its size is encoded as level, with level 0 being the largest chunk |
| // size ("root chunk"). |
| // |
| // |
| // -- Payload commit state -- |
| // |
| // A Metachunk payload (the "real chunk") may be committed, partly committed |
| // or completely uncommitted. Technically, a payload may be committed |
| // "checkered" - i.e. committed and uncommitted parts may interleave - but the |
| // important part is how much contiguous space is committed starting |
| // at the base of the payload (since that's where we allocate). |
| // |
| // The Metachunk keeps track of how much space is committed starting |
| // at the base of the payload - which is a performance optimization - |
| // while underlying layers (VirtualSpaceNode->commitmask) keep track |
| // of the "real" commit state, aka which granules are committed, |
| // independent on what chunks reside above those granules. |
| |
| // +--------------+ <- end -----------+ ----------+ |
| // | | | | |
| // | | | | |
| // | | | | |
| // | | | | |
| // | | | | |
| // | ----------- | <- committed_top -- + | |
| // | | | | |
| // | | | "free" | |
| // | | | | size |
| // | | "free_below_ | | |
| // | | committed" | | |
| // | | | | |
| // | | | | |
| // | ----------- | <- top --------- + -------- | |
| // | | | | |
| // | | "used" | | |
| // | | | | |
| // +--------------+ <- start ----------+ ----------+ |
| // |
| // |
| // -- Relationships -- |
| // |
| // Chunks are managed by a binary buddy style allocator |
| // (see https://en.wikipedia.org/wiki/Buddy_memory_allocation). |
| // Chunks which are not a root chunk always have an adjoining buddy. |
| // The first chunk in a buddy pair is called the leader, the second |
| // one the follower. |
| // |
| // +----------+----------+ |
| // | leader | follower | |
| // +----------+----------+ |
| // |
| // |
| // -- Layout in address space -- |
| // |
| // In order to implement buddy style allocation, we need an easy way to get |
| // from one chunk to the Metachunk representing the neighboring chunks |
| // (preceding resp. following it in memory). |
| // But Metachunk headers and chunks are physically separated, and it is not |
| // possible to get the Metachunk* from the start of the chunk. Therefore |
| // Metachunk headers are part of a second linked list, describing the order |
| // in which their payload appears in memory: |
| // |
| // +---------+ +---------+ +---------+ |
| // |MetaChunk| <--next/prev_in_vs--> |MetaChunk| <--next/prev_in_vs--> |MetaChunk| |
| // +---------+ +---------+ +---------+ |
| // | | | |
| // base base base |
| // | / | |
| // / -------------------------- / |
| // / / -------------------------------------------------- |
| // | | / |
| // v v v |
| // +---------+---------+-------------------+ |
| // | chunk | chunk | chunk | |
| // +---------+---------+-------------------+ |
| // |
| |
| class Metachunk { |
| |
| // start of chunk memory; null if dead. |
| MetaWord* _base; |
| |
| // Used words. |
| size_t _used_words; |
| |
| // Size of the region, starting from base, which is guaranteed to be committed. In words. |
| // The actual size of committed regions may actually be larger. |
| // |
| // (This is a performance optimization. The underlying VirtualSpaceNode knows |
| // which granules are committed; but we want to avoid having to ask.) |
| size_t _committed_words; |
| |
| chunklevel_t _level; // aka size. |
| |
| // state_free: free, owned by a ChunkManager |
| // state_in_use: in-use, owned by a MetaspaceArena |
| // dead: just a hollow chunk header without associated memory, owned |
| // by chunk header pool. |
| enum class State : uint8_t { |
| Free = 0, |
| InUse = 1, |
| Dead = 2 |
| }; |
| State _state; |
| |
| // We need unfortunately a back link to the virtual space node |
| // for splitting and merging nodes. |
| VirtualSpaceNode* _vsnode; |
| |
| // A chunk header is kept in a list: |
| // 1 in the list of used chunks inside a MetaspaceArena, if it is in use |
| // 2 in the list of free chunks inside a ChunkManager, if it is free |
| // 3 in the freelist of unused headers inside the ChunkHeaderPool, |
| // if it is unused (e.g. result of chunk merging) and has no associated |
| // memory area. |
| Metachunk* _prev; |
| Metachunk* _next; |
| |
| // Furthermore, we keep, per chunk, information about the neighboring chunks. |
| // This is needed to split and merge chunks. |
| // |
| // Note: These members can be modified concurrently while a chunk is alive and in use. |
| // This can happen if a neighboring chunk is added or removed. |
| // This means only read or modify these members under expand lock protection. |
| Metachunk* _prev_in_vs; |
| Metachunk* _next_in_vs; |
| |
| // Commit uncommitted section of the chunk. |
| // Fails if we hit a commit limit. |
| bool commit_up_to(size_t new_committed_words); |
| |
| DEBUG_ONLY(static void assert_have_expand_lock();) |
| |
| public: |
| |
| Metachunk() : |
| _base(nullptr), |
| _used_words(0), |
| _committed_words(0), |
| _level(chunklevel::ROOT_CHUNK_LEVEL), |
| _state(State::Free), |
| _vsnode(nullptr), |
| _prev(nullptr), _next(nullptr), |
| _prev_in_vs(nullptr), |
| _next_in_vs(nullptr) |
| {} |
| |
| void clear() { |
| _base = nullptr; |
| _used_words = 0; _committed_words = 0; |
| _level = chunklevel::ROOT_CHUNK_LEVEL; |
| _state = State::Free; |
| _vsnode = nullptr; |
| _prev = nullptr; _next = nullptr; |
| _prev_in_vs = nullptr; _next_in_vs = nullptr; |
| } |
| |
| size_t word_size() const { return chunklevel::word_size_for_level(_level); } |
| |
| MetaWord* base() const { return _base; } |
| MetaWord* top() const { return base() + _used_words; } |
| MetaWord* committed_top() const { return base() + _committed_words; } |
| MetaWord* end() const { return base() + word_size(); } |
| |
| // Chunk list wiring |
| void set_prev(Metachunk* c) { _prev = c; } |
| Metachunk* prev() const { return _prev; } |
| void set_next(Metachunk* c) { _next = c; } |
| Metachunk* next() const { return _next; } |
| |
| DEBUG_ONLY(bool in_list() const { return _prev != nullptr || _next != nullptr; }) |
| |
| // Physical neighbors wiring |
| void set_prev_in_vs(Metachunk* c) { DEBUG_ONLY(assert_have_expand_lock()); _prev_in_vs = c; } |
| Metachunk* prev_in_vs() const { DEBUG_ONLY(assert_have_expand_lock()); return _prev_in_vs; } |
| void set_next_in_vs(Metachunk* c) { DEBUG_ONLY(assert_have_expand_lock()); _next_in_vs = c; } |
| Metachunk* next_in_vs() const { DEBUG_ONLY(assert_have_expand_lock()); return _next_in_vs; } |
| |
| bool is_free() const { return _state == State::Free; } |
| bool is_in_use() const { return _state == State::InUse; } |
| bool is_dead() const { return _state == State::Dead; } |
| void set_free() { _state = State::Free; } |
| void set_in_use() { _state = State::InUse; } |
| void set_dead() { _state = State::Dead; } |
| |
| // Return a single char presentation of the state ('f', 'u', 'd') |
| char get_state_char() const; |
| |
| void inc_level() { _level++; DEBUG_ONLY(chunklevel::is_valid_level(_level);) } |
| void dec_level() { _level --; DEBUG_ONLY(chunklevel::is_valid_level(_level);) } |
| chunklevel_t level() const { return _level; } |
| |
| // Convenience functions for extreme levels. |
| bool is_root_chunk() const { return chunklevel::ROOT_CHUNK_LEVEL == _level; } |
| bool is_leaf_chunk() const { return chunklevel::HIGHEST_CHUNK_LEVEL == _level; } |
| |
| VirtualSpaceNode* vsnode() const { return _vsnode; } |
| |
| size_t used_words() const { return _used_words; } |
| size_t free_words() const { return word_size() - used_words(); } |
| size_t free_below_committed_words() const { return committed_words() - used_words(); } |
| void reset_used_words() { _used_words = 0; } |
| |
| size_t committed_words() const { return _committed_words; } |
| void set_committed_words(size_t v); |
| bool is_fully_committed() const { return committed_words() == word_size(); } |
| bool is_fully_uncommitted() const { return committed_words() == 0; } |
| |
| // Ensure that chunk is committed up to at least new_committed_words words. |
| // Fails if we hit a commit limit. |
| bool ensure_committed(size_t new_committed_words); |
| bool ensure_committed_locked(size_t new_committed_words); |
| |
| // Ensure that the chunk is committed far enough to serve an additional allocation of word_size. |
| bool ensure_committed_additional(size_t additional_word_size) { |
| return ensure_committed(used_words() + additional_word_size); |
| } |
| |
| // 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 uncommit(); |
| void uncommit_locked(); |
| |
| // Allocation from a chunk |
| |
| // 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* allocate(size_t request_word_size); |
| |
| // Initialize structure for reuse. |
| void initialize(VirtualSpaceNode* node, MetaWord* base, chunklevel_t lvl) { |
| clear(); |
| _vsnode = node; _base = base; _level = lvl; |
| } |
| |
| // Returns true if this chunk is the leader in its buddy pair, false if not. |
| // Do not call for root chunks. |
| bool is_leader() const { |
| assert(!is_root_chunk(), "Root chunks have no buddy."); // Bit harsh? |
| return is_aligned(base(), chunklevel::word_size_for_level(level() - 1) * BytesPerWord); |
| } |
| |
| //// Debug stuff //// |
| #ifdef ASSERT |
| void verify() const; |
| // Verifies linking with neighbors in virtual space. Needs expand lock protection. |
| void verify_neighborhood() const; |
| void zap_header(uint8_t c = 0x17); |
| |
| // Returns true if given pointer points into the payload area of this chunk. |
| bool is_valid_pointer(const MetaWord* p) const { |
| return base() <= p && p < top(); |
| } |
| |
| // Returns true if given pointer points into the committed payload area of this chunk. |
| bool is_valid_committed_pointer(const MetaWord* p) const { |
| return base() <= p && p < committed_top(); |
| } |
| |
| #endif // ASSERT |
| |
| void print_on(outputStream* st) const; |
| |
| }; |
| |
| // Little print helpers: since we often print out chunks, here some convenience macros |
| #define METACHUNK_FORMAT "@" PTR_FORMAT ", %c, base " PTR_FORMAT ", level " CHKLVL_FORMAT |
| #define METACHUNK_FORMAT_ARGS(chunk) p2i(chunk), chunk->get_state_char(), p2i(chunk->base()), chunk->level() |
| |
| #define METACHUNK_FULL_FORMAT "@" PTR_FORMAT ", %c, base " PTR_FORMAT ", level " CHKLVL_FORMAT " (%zu), used: %zu, committed: %zu, committed-free: %zu" |
| #define METACHUNK_FULL_FORMAT_ARGS(chunk) p2i(chunk), chunk->get_state_char(), p2i(chunk->base()), chunk->level(), chunk->word_size(), chunk->used_words(), chunk->committed_words(), chunk->free_below_committed_words() |
| |
| } // namespace metaspace |
| |
| #endif // SHARE_MEMORY_METASPACE_METACHUNK_HPP |