| /* | |
| * 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(); | |
| } |