| /* |
| * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. |
| * Copyright (c) 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_BLOCKTREE_HPP |
| #define SHARE_MEMORY_METASPACE_BLOCKTREE_HPP |
| |
| #include "memory/allocation.hpp" |
| #include "memory/metaspace/chunklevel.hpp" |
| #include "memory/metaspace/counters.hpp" |
| #include "memory/metaspace/metablock.hpp" |
| #include "utilities/debug.hpp" |
| #include "utilities/globalDefinitions.hpp" |
| |
| namespace metaspace { |
| |
| // BlockTree is a rather simple binary search tree. It is used to |
| // manage medium to large free memory blocks. |
| // |
| // There is no separation between payload (managed blocks) and nodes: the |
| // memory blocks themselves are the nodes, with the block size being the key. |
| // |
| // We store node pointer information in these blocks when storing them. That |
| // imposes a minimum size to the managed memory blocks (1 word) |
| // |
| // We want to manage many memory blocks of the same size, but we want |
| // to prevent the tree from blowing up and degenerating into a list. Therefore |
| // there is only one node for each unique block size; subsequent blocks of the |
| // same size are stacked below that first node: |
| // |
| // +-----+ |
| // | 100 | |
| // +-----+ |
| // / \ |
| // +-----+ |
| // | 80 | |
| // +-----+ |
| // / | \ |
| // / +-----+ \ |
| // +-----+ | 80 | +-----+ |
| // | 70 | +-----+ | 85 | |
| // +-----+ | +-----+ |
| // +-----+ |
| // | 80 | |
| // +-----+ |
| // |
| // |
| // Todo: This tree is unbalanced. It would be a good fit for a red-black tree. |
| // In order to make this a red-black tree, we need an algorithm which can deal |
| // with nodes which are their own payload (most red-black tree implementations |
| // swap payloads of their nodes at some point, see e.g. j.u.TreeSet). |
| // A good example is the Linux kernel rbtree, which is a clean, easy-to-read |
| // implementation. |
| |
| class BlockTree: public CHeapObj<mtMetaspace> { |
| |
| struct Node { |
| |
| static const intptr_t _canary_value = |
| NOT_LP64(0x4e4f4445) LP64_ONLY(0x4e4f44454e4f4445ULL); // "NODE" resp "NODENODE" |
| |
| // Note: we afford us the luxury of an always-there canary value. |
| // The space for that is there (these nodes are only used to manage larger blocks). |
| // It is initialized in debug and release, but only automatically tested |
| // in debug. |
| const intptr_t _canary; |
| |
| // Normal tree node stuff... |
| // (Note: all null if this is a stacked node) |
| Node* _parent; |
| Node* _left; |
| Node* _right; |
| |
| // Blocks with the same size are put in a list with this node as head. |
| Node* _next; |
| |
| // Word size of node. Note that size cannot be larger than max metaspace size, |
| // so this could be very well a 32bit value (in case we ever make this a balancing |
| // tree and need additional space for weighting information). |
| const size_t _word_size; |
| |
| Node(size_t word_size) : |
| _canary(_canary_value), |
| _parent(nullptr), |
| _left(nullptr), |
| _right(nullptr), |
| _next(nullptr), |
| _word_size(word_size) |
| {} |
| |
| #ifdef ASSERT |
| bool valid() const { |
| return _canary == _canary_value && |
| _word_size >= sizeof(Node) && |
| _word_size < chunklevel::MAX_CHUNK_WORD_SIZE; |
| } |
| #endif |
| }; |
| |
| // Needed for verify() and print_tree() |
| struct walkinfo; |
| |
| #ifdef ASSERT |
| // Run a quick check on a node; upon suspicion dive into a full tree check. |
| void check_node(const Node* n) const { if (!n->valid()) verify(); } |
| #endif |
| |
| public: |
| |
| // Minimum word size a block has to be to be added to this structure (note ceil division). |
| const static size_t MinWordSize = |
| (sizeof(Node) + sizeof(MetaWord) - 1) / sizeof(MetaWord); |
| |
| private: |
| |
| Node* _root; |
| |
| MemRangeCounter _counter; |
| |
| // Given a node n, add it to the list starting at head |
| static void add_to_list(Node* n, Node* head) { |
| assert(head->_word_size == n->_word_size, "sanity"); |
| n->_next = head->_next; |
| head->_next = n; |
| DEBUG_ONLY(n->_left = n->_right = n->_parent = nullptr;) |
| } |
| |
| // Given a node list starting at head, remove one of the follow up nodes from |
| // that list and return it. The head node gets not modified and remains in the |
| // tree. |
| // List must contain at least one other node. |
| static Node* remove_from_list(Node* head) { |
| assert(head->_next != nullptr, "sanity"); |
| Node* n = head->_next; |
| head->_next = n->_next; |
| return n; |
| } |
| |
| // Given a node c and a node p, wire up c as left child of p. |
| static void set_left_child(Node* p, Node* c) { |
| p->_left = c; |
| if (c != nullptr) { |
| assert(c->_word_size < p->_word_size, "sanity"); |
| c->_parent = p; |
| } |
| } |
| |
| // Given a node c and a node p, wire up c as right child of p. |
| static void set_right_child(Node* p, Node* c) { |
| p->_right = c; |
| if (c != nullptr) { |
| assert(c->_word_size > p->_word_size, "sanity"); |
| c->_parent = p; |
| } |
| } |
| |
| // Given a node n, return its successor in the tree |
| // (node with the next-larger size). |
| static Node* successor(Node* n) { |
| Node* succ = nullptr; |
| if (n->_right != nullptr) { |
| // If there is a right child, search the left-most |
| // child of that child. |
| succ = n->_right; |
| while (succ->_left != nullptr) { |
| succ = succ->_left; |
| } |
| } else { |
| succ = n->_parent; |
| Node* n2 = n; |
| // As long as I am the right child of my parent, search upward |
| while (succ != nullptr && n2 == succ->_right) { |
| n2 = succ; |
| succ = succ->_parent; |
| } |
| } |
| return succ; |
| } |
| |
| // Given a node, replace it with a replacement node as a child for its parent. |
| // If the node is root and has no parent, sets it as root. |
| void replace_node_in_parent(Node* child, Node* replace) { |
| Node* parent = child->_parent; |
| if (parent != nullptr) { |
| if (parent->_left == child) { // Child is left child |
| set_left_child(parent, replace); |
| } else { |
| set_right_child(parent, replace); |
| } |
| } else { |
| assert(child == _root, "must be root"); |
| _root = replace; |
| if (replace != nullptr) { |
| replace->_parent = nullptr; |
| } |
| } |
| return; |
| } |
| |
| // Given a node n and an insertion point, insert n under insertion point. |
| void insert(Node* insertion_point, Node* n) { |
| assert(n->_parent == nullptr, "Sanity"); |
| for (;;) { |
| DEBUG_ONLY(check_node(insertion_point);) |
| if (n->_word_size == insertion_point->_word_size) { |
| add_to_list(n, insertion_point); // parent stays null in this case. |
| break; |
| } else if (n->_word_size > insertion_point->_word_size) { |
| if (insertion_point->_right == nullptr) { |
| set_right_child(insertion_point, n); |
| break; |
| } else { |
| insertion_point = insertion_point->_right; |
| } |
| } else { |
| if (insertion_point->_left == nullptr) { |
| set_left_child(insertion_point, n); |
| break; |
| } else { |
| insertion_point = insertion_point->_left; |
| } |
| } |
| } |
| } |
| |
| // Given a node and a wish size, search this node and all children for |
| // the node closest (equal or larger sized) to the size s. |
| Node* find_closest_fit(Node* n, size_t s) { |
| Node* best_match = nullptr; |
| while (n != nullptr) { |
| DEBUG_ONLY(check_node(n);) |
| if (n->_word_size >= s) { |
| best_match = n; |
| if (n->_word_size == s) { |
| break; // perfect match or max depth reached |
| } |
| n = n->_left; |
| } else { |
| n = n->_right; |
| } |
| } |
| return best_match; |
| } |
| |
| // Given a wish size, search the whole tree for a |
| // node closest (equal or larger sized) to the size s. |
| Node* find_closest_fit(size_t s) { |
| if (_root != nullptr) { |
| return find_closest_fit(_root, s); |
| } |
| return nullptr; |
| } |
| |
| // Given a node n, remove it from the tree and repair tree. |
| void remove_node_from_tree(Node* n) { |
| assert(n->_next == nullptr, "do not delete a node which has a non-empty list"); |
| |
| if (n->_left == nullptr && n->_right == nullptr) { |
| replace_node_in_parent(n, nullptr); |
| |
| } else if (n->_left == nullptr && n->_right != nullptr) { |
| replace_node_in_parent(n, n->_right); |
| |
| } else if (n->_left != nullptr && n->_right == nullptr) { |
| replace_node_in_parent(n, n->_left); |
| |
| } else { |
| // Node has two children. |
| |
| // 1) Find direct successor (the next larger node). |
| Node* succ = successor(n); |
| |
| // There has to be a successor since n->right was != null... |
| assert(succ != nullptr, "must be"); |
| |
| // ... and it should not have a left child since successor |
| // is supposed to be the next larger node, so it must be the mostleft node |
| // in the sub tree rooted at n->right |
| assert(succ->_left == nullptr, "must be"); |
| assert(succ->_word_size > n->_word_size, "sanity"); |
| |
| Node* successor_parent = succ->_parent; |
| Node* successor_right_child = succ->_right; |
| |
| // Remove successor from its parent. |
| if (successor_parent == n) { |
| |
| // special case: successor is a direct child of n. Has to be the right child then. |
| assert(n->_right == succ, "sanity"); |
| |
| // Just replace n with this successor. |
| replace_node_in_parent(n, succ); |
| |
| // Take over n's old left child, too. |
| // We keep the successor's right child. |
| set_left_child(succ, n->_left); |
| } else { |
| // If the successors parent is not n, we are deeper in the tree, |
| // the successor has to be the left child of its parent. |
| assert(successor_parent->_left == succ, "sanity"); |
| |
| // The right child of the successor (if there was one) replaces |
| // the successor at its parent's left child. |
| set_left_child(successor_parent, succ->_right); |
| |
| // and the successor replaces n at its parent |
| replace_node_in_parent(n, succ); |
| |
| // and takes over n's old children |
| set_left_child(succ, n->_left); |
| set_right_child(succ, n->_right); |
| } |
| } |
| } |
| |
| #ifdef ASSERT |
| void zap_block(MetaBlock block); |
| // Helper for verify() |
| void verify_node_pointer(const Node* n) const; |
| #endif // ASSERT |
| |
| public: |
| |
| BlockTree() : _root(nullptr) {} |
| |
| // Add a memory block to the tree. Its content will be overwritten. |
| void add_block(MetaBlock block) { |
| DEBUG_ONLY(zap_block(block);) |
| const size_t word_size = block.word_size(); |
| assert(word_size >= MinWordSize, "invalid block size %zu", word_size); |
| Node* n = new(block.base()) Node(word_size); |
| if (_root == nullptr) { |
| _root = n; |
| } else { |
| insert(_root, n); |
| } |
| _counter.add(word_size); |
| } |
| |
| // Given a word_size, search and return the smallest block that is equal or |
| // larger than that size. |
| MetaBlock remove_block(size_t word_size) { |
| assert(word_size >= MinWordSize, "invalid block size %zu", word_size); |
| |
| MetaBlock result; |
| Node* n = find_closest_fit(word_size); |
| |
| if (n != nullptr) { |
| DEBUG_ONLY(check_node(n);) |
| assert(n->_word_size >= word_size, "sanity"); |
| |
| if (n->_next != nullptr) { |
| // If the node is head of a chain of same sized nodes, we leave it alone |
| // and instead remove one of the follow up nodes (which is simpler than |
| // removing the chain head node and then having to graft the follow up |
| // node into its place in the tree). |
| n = remove_from_list(n); |
| } else { |
| remove_node_from_tree(n); |
| } |
| |
| result = MetaBlock((MetaWord*)n, n->_word_size); |
| |
| _counter.sub(n->_word_size); |
| |
| DEBUG_ONLY(zap_block(result);) |
| } |
| return result; |
| } |
| |
| // Returns number of blocks in this structure |
| unsigned count() const { return _counter.count(); } |
| |
| // Returns total size, in words, of all elements. |
| size_t total_size() const { return _counter.total_size(); } |
| |
| bool is_empty() const { return _root == nullptr; } |
| |
| DEBUG_ONLY(void print_tree(outputStream* st) const;) |
| DEBUG_ONLY(void verify() const;) |
| }; |
| |
| } // namespace metaspace |
| |
| #endif // SHARE_MEMORY_METASPACE_BLOCKTREE_HPP |