| /* |
| * Copyright Amazon.com Inc. 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/shenandoah/shenandoahHeap.inline.hpp" |
| #include "gc/shenandoah/shenandoahHeapRegion.hpp" |
| #include "gc/shenandoah/shenandoahUncommitThread.hpp" |
| #include "logging/log.hpp" |
| #include "runtime/mutexLocker.hpp" |
| #include "utilities/events.hpp" |
| |
| ShenandoahUncommitThread::ShenandoahUncommitThread(ShenandoahHeap* heap) |
| : _heap(heap), |
| _uncommit_lock(Mutex::safepoint - 2, "ShenandoahUncommit_lock", true) { |
| set_name("Shenandoah Uncommit Thread"); |
| create_and_start(); |
| |
| // Allow uncommits. This is managed by the control thread during a GC. |
| _uncommit_allowed.set(); |
| } |
| |
| void ShenandoahUncommitThread::run_service() { |
| assert(ShenandoahUncommit, "Thread should only run when uncommit is enabled"); |
| |
| // poll_interval avoids constantly polling regions for shrinking. |
| // Having an interval 10x lower than the delay would mean we hit the |
| // shrinking with lag of less than 1/10-th of true delay. |
| // ShenandoahUncommitDelay is in millis, but shrink_period is in seconds. |
| const int64_t poll_interval = int64_t(ShenandoahUncommitDelay) / 10; |
| const double shrink_period = double(ShenandoahUncommitDelay) / 1000; |
| bool timed_out = false; |
| while (!should_terminate()) { |
| bool soft_max_changed = _soft_max_changed.try_unset(); |
| bool explicit_gc_requested = _explicit_gc_requested.try_unset(); |
| |
| if (soft_max_changed || explicit_gc_requested || timed_out) { |
| double current = os::elapsedTime(); |
| size_t shrink_until = soft_max_changed ? _heap->soft_max_capacity() : _heap->min_capacity(); |
| double shrink_before = (soft_max_changed || explicit_gc_requested) ? |
| current : |
| current - shrink_period; |
| |
| // Explicit GC tries to uncommit everything down to min capacity. |
| // Soft max change tries to uncommit everything down to target capacity. |
| // Periodic uncommit tries to uncommit suitable regions down to min capacity. |
| if (should_uncommit(shrink_before, shrink_until)) { |
| uncommit(shrink_before, shrink_until); |
| } |
| } |
| |
| if (!should_terminate()) { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| timed_out = locker.wait(poll_interval); |
| } |
| } |
| } |
| |
| bool ShenandoahUncommitThread::should_uncommit(double shrink_before, size_t shrink_until) const { |
| // Only start uncommit if the GC is idle, is not trying to run and there is work to do. |
| return _heap->is_idle() && is_uncommit_allowed() && has_work(shrink_before, shrink_until); |
| } |
| |
| bool ShenandoahUncommitThread::has_work(double shrink_before, size_t shrink_until) const { |
| // Determine if there is work to do. This avoids locking the heap if there is |
| // no work available, avoids spamming logs with superfluous logging messages, |
| // and minimises the amount of work while locks are held. |
| |
| if (_heap->committed() <= shrink_until) { |
| return false; |
| } |
| |
| for (size_t i = 0; i < _heap->num_regions(); i++) { |
| ShenandoahHeapRegion *r = _heap->get_region(i); |
| if (r->is_empty_committed() && (r->empty_time() < shrink_before)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void ShenandoahUncommitThread::notify_soft_max_changed() { |
| assert(is_uncommit_allowed(), "Only notify if uncommit is allowed"); |
| if (_soft_max_changed.try_set()) { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| locker.notify_all(); |
| } |
| } |
| |
| void ShenandoahUncommitThread::notify_explicit_gc_requested() { |
| assert(is_uncommit_allowed(), "Only notify if uncommit is allowed"); |
| if (_explicit_gc_requested.try_set()) { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| locker.notify_all(); |
| } |
| } |
| |
| bool ShenandoahUncommitThread::is_uncommit_allowed() const { |
| return _uncommit_allowed.is_set(); |
| } |
| |
| void ShenandoahUncommitThread::uncommit(double shrink_before, size_t shrink_until) { |
| assert(ShenandoahUncommit, "should be enabled"); |
| assert(_uncommit_in_progress.is_unset(), "Uncommit should not be in progress"); |
| |
| { |
| // Final check, under the lock, if uncommit is allowed. |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| if (is_uncommit_allowed()) { |
| _uncommit_in_progress.set(); |
| } |
| } |
| |
| // If not allowed to start, do nothing. |
| if (!_uncommit_in_progress.is_set()) { |
| return; |
| } |
| |
| // From here on, uncommit is in progress. Attempts to stop the uncommit must wait |
| // until the cancellation request is acknowledged and uncommit is no longer in progress. |
| const char* msg = "Concurrent uncommit"; |
| const double start = os::elapsedTime(); |
| EventMark em("%s", msg); |
| log_info(gc, start)("%s", msg); |
| |
| // This is the number of regions uncommitted during this increment of uncommit work. |
| const size_t uncommitted_region_count = do_uncommit_work(shrink_before, shrink_until); |
| |
| { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| _uncommit_in_progress.unset(); |
| locker.notify_all(); |
| } |
| |
| if (uncommitted_region_count > 0) { |
| _heap->notify_heap_changed(); |
| } |
| |
| const double elapsed = os::elapsedTime() - start; |
| log_info(gc)("%s " PROPERFMT " (" PROPERFMT ") %.3fms", |
| msg, PROPERFMTARGS(uncommitted_region_count * ShenandoahHeapRegion::region_size_bytes()), PROPERFMTARGS(_heap->capacity()), |
| elapsed * MILLIUNITS); |
| } |
| |
| size_t ShenandoahUncommitThread::do_uncommit_work(double shrink_before, size_t shrink_until) const { |
| size_t count = 0; |
| // Application allocates from the beginning of the heap, and GC allocates at |
| // the end of it. It is more efficient to uncommit from the end, so that applications |
| // could enjoy the near committed regions. GC allocations are much less frequent, |
| // and therefore can accept the committing costs. |
| for (size_t i = _heap->num_regions(); i > 0; i--) { |
| if (!is_uncommit_allowed()) { |
| // GC wants to start, so the uncommit operation must stop |
| break; |
| } |
| |
| ShenandoahHeapRegion* r = _heap->get_region(i - 1); |
| if (r->is_empty_committed() && (r->empty_time() < shrink_before)) { |
| SuspendibleThreadSetJoiner sts_joiner; |
| ShenandoahHeapLocker heap_locker(_heap->lock()); |
| if (r->is_empty_committed()) { |
| if (_heap->committed() < shrink_until + ShenandoahHeapRegion::region_size_bytes()) { |
| // We have uncommitted enough regions to hit the target heap committed size |
| break; |
| } |
| |
| r->make_uncommitted(); |
| count++; |
| } |
| } |
| SpinPause(); // allow allocators to take the lock |
| } |
| return count; |
| } |
| |
| |
| void ShenandoahUncommitThread::stop_service() { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_safepoint_check_flag); |
| _uncommit_allowed.unset(); |
| locker.notify_all(); |
| } |
| |
| void ShenandoahUncommitThread::forbid_uncommit() { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| _uncommit_allowed.unset(); |
| while (_uncommit_in_progress.is_set()) { |
| locker.wait(); |
| } |
| } |
| |
| void ShenandoahUncommitThread::allow_uncommit() { |
| MonitorLocker locker(&_uncommit_lock, Mutex::_no_safepoint_check_flag); |
| _uncommit_allowed.set(); |
| } |