| /* | |
| * Copyright (c) 2017, 2025, 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 "gc/shared/gc_globals.hpp" | |
| #include "gc/z/zArray.inline.hpp" | |
| #include "gc/z/zForwarding.inline.hpp" | |
| #include "gc/z/zPage.inline.hpp" | |
| #include "gc/z/zRelocationSetSelector.inline.hpp" | |
| #include "jfr/jfrEvents.hpp" | |
| #include "logging/log.hpp" | |
| #include "runtime/globals.hpp" | |
| #include "utilities/debug.hpp" | |
| #include "utilities/powerOfTwo.hpp" | |
| ZRelocationSetSelectorGroupStats::ZRelocationSetSelectorGroupStats() | |
| : _npages_candidates(0), | |
| _total(0), | |
| _live(0), | |
| _empty(0), | |
| _npages_selected(0), | |
| _relocate(0) {} | |
| ZRelocationSetSelectorGroup::ZRelocationSetSelectorGroup(const char* name, | |
| ZPageType page_type, | |
| size_t max_page_size, | |
| size_t object_size_limit, | |
| double fragmentation_limit) | |
| : _name(name), | |
| _page_type(page_type), | |
| _max_page_size(max_page_size), | |
| _object_size_limit(object_size_limit), | |
| _fragmentation_limit(fragmentation_limit), | |
| _page_fragmentation_limit((size_t)(_max_page_size * (_fragmentation_limit / 100))), | |
| _live_pages(), | |
| _not_selected_pages(), | |
| _forwarding_entries(0), | |
| _stats() {} | |
| bool ZRelocationSetSelectorGroup::is_disabled() { | |
| // Only medium pages can be disabled | |
| return _page_type == ZPageType::medium && !ZPageSizeMediumEnabled; | |
| } | |
| bool ZRelocationSetSelectorGroup::is_selectable() { | |
| // Large pages are not selectable | |
| return _page_type != ZPageType::large; | |
| } | |
| size_t ZRelocationSetSelectorGroup::partition_index(const ZPage* page) const { | |
| const size_t partition_size = page->size() >> NumPartitionsShift; | |
| const int partition_size_shift = log2i_exact(partition_size); | |
| return page->live_bytes() >> partition_size_shift; | |
| } | |
| void ZRelocationSetSelectorGroup::semi_sort() { | |
| // Semi-sort live pages by number of live bytes in ascending order | |
| // Partition slots/fingers | |
| int partitions[NumPartitions] = { /* zero initialize */ }; | |
| // Calculate partition slots | |
| ZArrayIterator<ZPage*> iter1(&_live_pages); | |
| for (ZPage* page; iter1.next(&page);) { | |
| const size_t index = partition_index(page); | |
| partitions[index]++; | |
| } | |
| // Calculate partition fingers | |
| int finger = 0; | |
| for (size_t i = 0; i < NumPartitions; i++) { | |
| const int slots = partitions[i]; | |
| partitions[i] = finger; | |
| finger += slots; | |
| } | |
| // Allocate destination array | |
| const int npages = _live_pages.length(); | |
| ZArray<ZPage*> sorted_live_pages(npages, npages, nullptr); | |
| // Sort pages into partitions | |
| ZArrayIterator<ZPage*> iter2(&_live_pages); | |
| for (ZPage* page; iter2.next(&page);) { | |
| const size_t index = partition_index(page); | |
| const int finger = partitions[index]++; | |
| assert(sorted_live_pages.at(finger) == nullptr, "Invalid finger"); | |
| sorted_live_pages.at_put(finger, page); | |
| } | |
| _live_pages.swap(&sorted_live_pages); | |
| } | |
| void ZRelocationSetSelectorGroup::select_inner() { | |
| // Calculate the number of pages to relocate by successively including pages in | |
| // a candidate relocation set and calculate the maximum space requirement for | |
| // their live objects. | |
| const int npages = _live_pages.length(); | |
| int selected_from = 0; | |
| int selected_to = 0; | |
| size_t npages_selected[ZPageAgeMax + 1] = { 0 }; | |
| size_t selected_live_bytes[ZPageAgeMax + 1] = { 0 }; | |
| size_t selected_forwarding_entries = 0; | |
| size_t from_live_bytes = 0; | |
| size_t from_forwarding_entries = 0; | |
| semi_sort(); | |
| for (int from = 1; from <= npages; from++) { | |
| // Add page to the candidate relocation set | |
| ZPage* const page = _live_pages.at(from - 1); | |
| const size_t page_live_bytes = page->live_bytes(); | |
| from_live_bytes += page_live_bytes; | |
| from_forwarding_entries += ZForwarding::nentries(page); | |
| // Calculate the maximum number of pages needed by the candidate relocation set. | |
| // By subtracting the object size limit from the pages size we get the maximum | |
| // number of pages that the relocation set is guaranteed to fit in, regardless | |
| // of in which order the objects are relocated. | |
| const int to = (int)ceil(from_live_bytes / (double)(_max_page_size - _object_size_limit)); | |
| // Calculate the relative difference in reclaimable space compared to our | |
| // currently selected final relocation set. If this number is larger than the | |
| // acceptable fragmentation limit, then the current candidate relocation set | |
| // becomes our new final relocation set. | |
| const int diff_from = from - selected_from; | |
| const int diff_to = to - selected_to; | |
| const double diff_reclaimable = 100 - percent_of(diff_to, diff_from); | |
| if (diff_reclaimable > _fragmentation_limit) { | |
| selected_from = from; | |
| selected_to = to; | |
| selected_live_bytes[static_cast<uint>(page->age())] += page_live_bytes; | |
| npages_selected[static_cast<uint>(page->age())] += 1; | |
| selected_forwarding_entries = from_forwarding_entries; | |
| } | |
| log_trace(gc, reloc)("Candidate Relocation Set (%s Pages): %d->%d, " | |
| "%.1f%% relative defragmentation, %zu forwarding entries, %s, live %d", | |
| _name, from, to, diff_reclaimable, from_forwarding_entries, | |
| (selected_from == from) ? "Selected" : "Rejected", | |
| int(page_live_bytes * 100 / page->size())); | |
| } | |
| // Finalize selection | |
| for (int i = selected_from; i < _live_pages.length(); i++) { | |
| ZPage* const page = _live_pages.at(i); | |
| if (page->is_young()) { | |
| _not_selected_pages.append(page); | |
| } | |
| } | |
| _live_pages.trunc_to(selected_from); | |
| _forwarding_entries = selected_forwarding_entries; | |
| // Update statistics | |
| for (uint i = 0; i <= ZPageAgeMax; ++i) { | |
| _stats[i]._relocate = selected_live_bytes[i]; | |
| _stats[i]._npages_selected = npages_selected[i]; | |
| } | |
| log_debug(gc, reloc)("Relocation Set (%s Pages): %d->%d, %d skipped, %zu forwarding entries", | |
| _name, selected_from, selected_to, npages - selected_from, selected_forwarding_entries); | |
| } | |
| void ZRelocationSetSelectorGroup::select() { | |
| if (is_disabled()) { | |
| return; | |
| } | |
| EventZRelocationSetGroup event; | |
| if (is_selectable()) { | |
| select_inner(); | |
| } else { | |
| // Mark pages as not selected | |
| const int npages = _live_pages.length(); | |
| for (int from = 1; from <= npages; from++) { | |
| ZPage* const page = _live_pages.at(from - 1); | |
| _not_selected_pages.append(page); | |
| } | |
| } | |
| ZRelocationSetSelectorGroupStats s{}; | |
| for (uint i = 0; i <= ZPageAgeMax; ++i) { | |
| s._npages_candidates += _stats[i].npages_candidates(); | |
| s._total += _stats[i].total(); | |
| s._empty += _stats[i].empty(); | |
| s._npages_selected += _stats[i].npages_selected(); | |
| s._relocate += _stats[i].relocate(); | |
| } | |
| // Send event | |
| event.commit((u8)_page_type, s._npages_candidates, s._total, s._empty, s._npages_selected, s._relocate); | |
| } | |
| ZRelocationSetSelector::ZRelocationSetSelector(double fragmentation_limit) | |
| : _small("Small", ZPageType::small, ZPageSizeSmall, ZObjectSizeLimitSmall, fragmentation_limit), | |
| _medium("Medium", ZPageType::medium, ZPageSizeMediumMax, ZObjectSizeLimitMedium, fragmentation_limit), | |
| _large("Large", ZPageType::large, 0 /* max_page_size */, 0 /* object_size_limit */, fragmentation_limit), | |
| _empty_pages() {} | |
| void ZRelocationSetSelector::select() { | |
| // Select pages to relocate. The resulting relocation set will be | |
| // sorted such that medium pages comes first, followed by small | |
| // pages. Pages within each page group will be semi-sorted by live | |
| // bytes in ascending order. Relocating pages in this order allows | |
| // us to start reclaiming memory more quickly. | |
| EventZRelocationSet event; | |
| // Select pages from each group | |
| _large.select(); | |
| _medium.select(); | |
| _small.select(); | |
| // Send event | |
| event.commit(total(), empty(), relocate()); | |
| } | |
| ZRelocationSetSelectorStats ZRelocationSetSelector::stats() const { | |
| ZRelocationSetSelectorStats stats; | |
| for (uint i = 0; i <= ZPageAgeMax; ++i) { | |
| const ZPageAge age = static_cast<ZPageAge>(i); | |
| stats._small[i] = _small.stats(age); | |
| stats._medium[i] = _medium.stats(age); | |
| stats._large[i] = _large.stats(age); | |
| } | |
| stats._has_relocatable_pages = total() > 0; | |
| return stats; | |
| } |