| /* |
| * Copyright (c) 2021, 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 "ci/ciUtilities.hpp" |
| #include "gc/g1/g1CardSetMemory.inline.hpp" |
| #include "gc/g1/g1CollectedHeap.hpp" |
| #include "gc/g1/g1HeapRegionRemSet.hpp" |
| #include "gc/g1/g1MonotonicArenaFreeMemoryTask.hpp" |
| #include "gc/g1/g1MonotonicArenaFreePool.hpp" |
| #include "gc/shared/gc_globals.hpp" |
| #include "gc/shared/gcTraceTime.inline.hpp" |
| #include "gc/shared/suspendibleThreadSet.hpp" |
| #include "runtime/os.hpp" |
| |
| constexpr const char* G1MonotonicArenaFreeMemoryTask::_state_names[]; |
| |
| const char* G1MonotonicArenaFreeMemoryTask::get_state_name(State value) const { |
| return _state_names[static_cast<std::underlying_type_t<State>>(value)]; |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::deadline_exceeded(jlong deadline) { |
| return os::elapsed_counter() >= deadline; |
| } |
| |
| static size_t keep_size(size_t free, size_t used, double percent) { |
| size_t to_keep = used * percent; |
| return MIN2(free, to_keep); |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::calculate_return_infos(jlong deadline) { |
| // Ignore the deadline in this step as it is very short. |
| |
| G1MonotonicArenaMemoryStats used = _total_used; |
| G1CollectedHeap* g1h = G1CollectedHeap::heap(); |
| G1MonotonicArenaFreePool* freelist_pool = g1h->card_set_freelist_pool(); |
| G1MonotonicArenaMemoryStats free = freelist_pool->memory_sizes(); |
| |
| _return_info = new G1ReturnMemoryProcessorSet(used.num_pools()); |
| for (uint i = 0; i < used.num_pools(); i++) { |
| size_t return_to_vm_size = keep_size(free._num_mem_sizes[i], |
| used._num_mem_sizes[i], |
| G1RemSetFreeMemoryKeepExcessRatio); |
| log_trace(gc, task)("Monotonic Arena Free Memory: Type %s: Free: %zu (%zu) " |
| "Used: %zu Keep: %zu", |
| G1CardSetConfiguration::mem_object_type_name_str(i), |
| free._num_mem_sizes[i], free._num_segments[i], |
| used._num_mem_sizes[i], return_to_vm_size); |
| |
| _return_info->append(new G1ReturnMemoryProcessor(return_to_vm_size)); |
| } |
| |
| freelist_pool->update_unlink_processors(_return_info); |
| return false; |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::return_memory_to_vm(jlong deadline) { |
| for (int i = 0; i < _return_info->length(); i++) { |
| G1ReturnMemoryProcessor* info = _return_info->at(i); |
| if (!info->finished_return_to_vm()) { |
| if (info->return_to_vm(deadline)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::return_memory_to_os(jlong deadline) { |
| for (int i = 0; i < _return_info->length(); i++) { |
| G1ReturnMemoryProcessor* info = _return_info->at(i); |
| if (!info->finished_return_to_os()) { |
| if (info->return_to_os(deadline)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::cleanup_return_infos() { |
| for (int i = 0; i < _return_info->length(); i++) { |
| G1ReturnMemoryProcessor* info = _return_info->at(i); |
| delete info; |
| } |
| delete _return_info; |
| |
| _return_info = nullptr; |
| return false; |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::free_excess_arena_memory() { |
| jlong start = os::elapsed_counter(); |
| jlong end = start + |
| (os::elapsed_frequency() / 1000) * G1RemSetFreeMemoryStepDurationMillis; |
| |
| log_trace(gc, task)("Monotonic Arena Free Memory: Step start %1.3f end %1.3f", |
| TimeHelper::counter_to_millis(start), TimeHelper::counter_to_millis(end)); |
| |
| State next_state; |
| |
| do { |
| switch (_state) { |
| case State::CalculateUsed: { |
| if (calculate_return_infos(end)) { |
| next_state = _state; |
| return true; |
| } |
| next_state = State::ReturnToVM; |
| break; |
| } |
| case State::ReturnToVM: { |
| if (return_memory_to_vm(end)) { |
| next_state = _state; |
| return true; |
| } |
| next_state = State::ReturnToOS; |
| break; |
| } |
| case State::ReturnToOS: { |
| if (return_memory_to_os(end)) { |
| next_state = _state; |
| return true; |
| } |
| next_state = State::Cleanup; |
| break; |
| } |
| case State::Cleanup: { |
| cleanup_return_infos(); |
| next_state = State::Inactive; |
| break; |
| } |
| default: |
| log_error(gc, task)("Should not try to free excess monotonic area memory in %s state", get_state_name(_state)); |
| ShouldNotReachHere(); |
| break; |
| } |
| |
| set_state(next_state); |
| } while (_state != State::Inactive && !deadline_exceeded(end)); |
| |
| log_trace(gc, task)("Monotonic Arena Free Memory: Step took %1.3fms, done %s", |
| TimeHelper::counter_to_millis(os::elapsed_counter() - start), |
| bool_to_str(_state == State::CalculateUsed)); |
| |
| return is_active(); |
| } |
| |
| void G1MonotonicArenaFreeMemoryTask::set_state(State new_state) { |
| log_trace(gc, task)("Monotonic Arena Free Memory: State change from %s to %s", |
| get_state_name(_state), |
| get_state_name(new_state)); |
| _state = new_state; |
| } |
| |
| bool G1MonotonicArenaFreeMemoryTask::is_active() const { |
| return _state != State::Inactive; |
| } |
| |
| jlong G1MonotonicArenaFreeMemoryTask::reschedule_delay_ms() const { |
| return G1RemSetFreeMemoryRescheduleDelayMillis; |
| } |
| |
| G1MonotonicArenaFreeMemoryTask::G1MonotonicArenaFreeMemoryTask(const char* name) : |
| G1ServiceTask(name), _state(State::CalculateUsed), _return_info(nullptr) { } |
| |
| void G1MonotonicArenaFreeMemoryTask::execute() { |
| SuspendibleThreadSetJoiner sts; |
| |
| if (free_excess_arena_memory()) { |
| schedule(reschedule_delay_ms()); |
| } |
| } |
| |
| void G1MonotonicArenaFreeMemoryTask::notify_new_stats(G1MonotonicArenaMemoryStats* young_gen_stats, |
| G1MonotonicArenaMemoryStats* collection_set_candidate_stats) { |
| assert_at_safepoint_on_vm_thread(); |
| |
| _total_used = *young_gen_stats; |
| _total_used.add(*collection_set_candidate_stats); |
| |
| if (!is_active()) { |
| set_state(State::CalculateUsed); |
| G1CollectedHeap::heap()->service_thread()->schedule_task(this, 0); |
| } |
| } |