1 /* 2 * Copyright (c) 2022, Amazon, Inc. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 * 23 */ 24 #include "precompiled.hpp" 25 26 #include "gc/shenandoah/shenandoahAsserts.hpp" 27 #include "gc/shenandoah/shenandoahMmuTracker.hpp" 28 #include "gc/shenandoah/shenandoahHeap.inline.hpp" 29 #include "gc/shenandoah/shenandoahOldGeneration.hpp" 30 #include "gc/shenandoah/shenandoahYoungGeneration.hpp" 31 #include "logging/log.hpp" 32 #include "runtime/os.hpp" 33 #include "runtime/task.hpp" 34 35 36 class ShenandoahMmuTask : public PeriodicTask { 37 ShenandoahMmuTracker* _mmu_tracker; 38 public: 39 explicit ShenandoahMmuTask(ShenandoahMmuTracker* mmu_tracker) : 40 PeriodicTask(GCPauseIntervalMillis), _mmu_tracker(mmu_tracker) {} 41 42 void task() override { 43 _mmu_tracker->report(); 44 } 45 }; 46 47 class ThreadTimeAccumulator : public ThreadClosure { 48 public: 49 size_t total_time; 50 ThreadTimeAccumulator() : total_time(0) {} 51 void do_thread(Thread* thread) override { 52 total_time += os::thread_cpu_time(thread); 53 } 54 }; 55 56 double ShenandoahMmuTracker::gc_thread_time_seconds() { 57 ThreadTimeAccumulator cl; 58 // We include only the gc threads because those are the only threads 59 // we are responsible for. 60 ShenandoahHeap::heap()->gc_threads_do(&cl); 61 return double(cl.total_time) / NANOSECS_PER_SEC; 62 } 63 64 double ShenandoahMmuTracker::process_time_seconds() { 65 double process_real_time(0.0), process_user_time(0.0), process_system_time(0.0); 66 bool valid = os::getTimesSecs(&process_real_time, &process_user_time, &process_system_time); 67 if (valid) { 68 return process_user_time + process_system_time; 69 } 70 return 0.0; 71 } 72 73 ShenandoahMmuTracker::ShenandoahMmuTracker() : 74 _generational_reference_time_s(0.0), 75 _process_reference_time_s(0.0), 76 _collector_reference_time_s(0.0), 77 _mmu_periodic_task(new ShenandoahMmuTask(this)), 78 _mmu_average(10, ShenandoahAdaptiveDecayFactor) { 79 } 80 81 ShenandoahMmuTracker::~ShenandoahMmuTracker() { 82 _mmu_periodic_task->disenroll(); 83 delete _mmu_periodic_task; 84 } 85 86 void ShenandoahMmuTracker::record(ShenandoahGeneration* generation) { 87 shenandoah_assert_control_or_vm_thread(); 88 double collector_time_s = gc_thread_time_seconds(); 89 double elapsed_gc_time_s = collector_time_s - _generational_reference_time_s; 90 generation->add_collection_time(elapsed_gc_time_s); 91 _generational_reference_time_s = collector_time_s; 92 } 93 94 void ShenandoahMmuTracker::report() { 95 // This is only called by the periodic thread. 96 double process_time_s = process_time_seconds(); 97 double elapsed_process_time_s = process_time_s - _process_reference_time_s; 98 if (elapsed_process_time_s <= 0.01) { 99 // No cpu time for this interval? 100 return; 101 } 102 103 _process_reference_time_s = process_time_s; 104 double collector_time_s = gc_thread_time_seconds(); 105 double elapsed_collector_time_s = collector_time_s - _collector_reference_time_s; 106 _collector_reference_time_s = collector_time_s; 107 double minimum_mutator_utilization = ((elapsed_process_time_s - elapsed_collector_time_s) / elapsed_process_time_s) * 100; 108 _mmu_average.add(minimum_mutator_utilization); 109 log_info(gc)("Average MMU = %.3f", _mmu_average.davg()); 110 } 111 112 void ShenandoahMmuTracker::initialize() { 113 _process_reference_time_s = process_time_seconds(); 114 _generational_reference_time_s = gc_thread_time_seconds(); 115 _collector_reference_time_s = _generational_reference_time_s; 116 _mmu_periodic_task->enroll(); 117 } 118 119 ShenandoahGenerationSizer::ShenandoahGenerationSizer(ShenandoahMmuTracker* mmu_tracker) 120 : _sizer_kind(SizerDefaults), 121 _use_adaptive_sizing(true), 122 _min_desired_young_regions(0), 123 _max_desired_young_regions(0), 124 _resize_increment(double(YoungGenerationSizeIncrement) / 100.0), 125 _mmu_tracker(mmu_tracker) { 126 127 if (FLAG_IS_CMDLINE(NewRatio)) { 128 if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) { 129 log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio"); 130 } else { 131 _sizer_kind = SizerNewRatio; 132 _use_adaptive_sizing = false; 133 return; 134 } 135 } 136 137 if (NewSize > MaxNewSize) { 138 if (FLAG_IS_CMDLINE(MaxNewSize)) { 139 log_warning(gc, ergo)("NewSize (" SIZE_FORMAT "k) is greater than the MaxNewSize (" SIZE_FORMAT "k). " 140 "A new max generation size of " SIZE_FORMAT "k will be used.", 141 NewSize/K, MaxNewSize/K, NewSize/K); 142 } 143 FLAG_SET_ERGO(MaxNewSize, NewSize); 144 } 145 146 if (FLAG_IS_CMDLINE(NewSize)) { 147 _min_desired_young_regions = MAX2(uint(NewSize / ShenandoahHeapRegion::region_size_bytes()), 1U); 148 if (FLAG_IS_CMDLINE(MaxNewSize)) { 149 _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U); 150 _sizer_kind = SizerMaxAndNewSize; 151 _use_adaptive_sizing = _min_desired_young_regions != _max_desired_young_regions; 152 } else { 153 _sizer_kind = SizerNewSizeOnly; 154 } 155 } else if (FLAG_IS_CMDLINE(MaxNewSize)) { 156 _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U); 157 _sizer_kind = SizerMaxNewSizeOnly; 158 } 159 } 160 161 size_t ShenandoahGenerationSizer::calculate_min_young_regions(size_t heap_region_count) { 162 size_t min_young_regions = (heap_region_count * ShenandoahMinYoungPercentage) / 100; 163 return MAX2(uint(min_young_regions), 1U); 164 } 165 166 size_t ShenandoahGenerationSizer::calculate_max_young_regions(size_t heap_region_count) { 167 size_t max_young_regions = (heap_region_count * ShenandoahMaxYoungPercentage) / 100; 168 return MAX2(uint(max_young_regions), 1U); 169 } 170 171 void ShenandoahGenerationSizer::recalculate_min_max_young_length(size_t heap_region_count) { 172 assert(heap_region_count > 0, "Heap must be initialized"); 173 174 switch (_sizer_kind) { 175 case SizerDefaults: 176 _min_desired_young_regions = calculate_min_young_regions(heap_region_count); 177 _max_desired_young_regions = calculate_max_young_regions(heap_region_count); 178 break; 179 case SizerNewSizeOnly: 180 _max_desired_young_regions = calculate_max_young_regions(heap_region_count); 181 _max_desired_young_regions = MAX2(_min_desired_young_regions, _max_desired_young_regions); 182 break; 183 case SizerMaxNewSizeOnly: 184 _min_desired_young_regions = calculate_min_young_regions(heap_region_count); 185 _min_desired_young_regions = MIN2(_min_desired_young_regions, _max_desired_young_regions); 186 break; 187 case SizerMaxAndNewSize: 188 // Do nothing. Values set on the command line, don't update them at runtime. 189 break; 190 case SizerNewRatio: 191 _min_desired_young_regions = MAX2(uint(heap_region_count / (NewRatio + 1)), 1U); 192 _max_desired_young_regions = _min_desired_young_regions; 193 break; 194 default: 195 ShouldNotReachHere(); 196 } 197 198 assert(_min_desired_young_regions <= _max_desired_young_regions, "Invalid min/max young gen size values"); 199 } 200 201 void ShenandoahGenerationSizer::heap_size_changed(size_t heap_size) { 202 recalculate_min_max_young_length(heap_size / ShenandoahHeapRegion::region_size_bytes()); 203 } 204 205 bool ShenandoahGenerationSizer::adjust_generation_sizes() const { 206 shenandoah_assert_generational(); 207 if (!use_adaptive_sizing()) { 208 return false; 209 } 210 211 if (_mmu_tracker->average() >= double(GCTimeRatio)) { 212 return false; 213 } 214 215 ShenandoahHeap* heap = ShenandoahHeap::heap(); 216 ShenandoahOldGeneration *old = heap->old_generation(); 217 ShenandoahYoungGeneration *young = heap->young_generation(); 218 ShenandoahGeneration *global = heap->global_generation(); 219 double old_time_s = old->reset_collection_time(); 220 double young_time_s = young->reset_collection_time(); 221 double global_time_s = global->reset_collection_time(); 222 223 const double transfer_threshold = 3.0; 224 double delta = young_time_s - old_time_s; 225 226 log_info(gc)("Thread Usr+Sys YOUNG = %.3f, OLD = %.3f, GLOBAL = %.3f", young_time_s, old_time_s, global_time_s); 227 228 if (abs(delta) <= transfer_threshold) { 229 log_info(gc, ergo)("Difference (%.3f) for thread utilization for each generation is under threshold (%.3f)", abs(delta), transfer_threshold); 230 return false; 231 } 232 233 if (delta > 0) { 234 // young is busier than old, increase size of young to raise MMU 235 return transfer_capacity(old, young); 236 } else { 237 // old is busier than young, increase size of old to raise MMU 238 return transfer_capacity(young, old); 239 } 240 } 241 242 bool ShenandoahGenerationSizer::transfer_capacity(ShenandoahGeneration* target) const { 243 ShenandoahHeapLocker locker(ShenandoahHeap::heap()->lock()); 244 if (target->is_young()) { 245 return transfer_capacity(ShenandoahHeap::heap()->old_generation(), target); 246 } else { 247 assert(target->is_old(), "Expected old generation, if not young."); 248 return transfer_capacity(ShenandoahHeap::heap()->young_generation(), target); 249 } 250 } 251 252 bool ShenandoahGenerationSizer::transfer_capacity(ShenandoahGeneration* from, ShenandoahGeneration* to) const { 253 shenandoah_assert_heaplocked_or_safepoint(); 254 255 size_t available_regions = from->free_unaffiliated_regions(); 256 if (available_regions <= 0) { 257 log_info(gc)("%s has no regions available for transfer to %s", from->name(), to->name()); 258 return false; 259 } 260 261 size_t regions_to_transfer = MAX2(1u, uint(double(available_regions) * _resize_increment)); 262 if (from->generation_mode() == YOUNG) { 263 regions_to_transfer = adjust_transfer_from_young(from, regions_to_transfer); 264 } else { 265 regions_to_transfer = adjust_transfer_to_young(to, regions_to_transfer); 266 } 267 268 if (regions_to_transfer == 0) { 269 log_info(gc)("No capacity available to transfer from: %s (" SIZE_FORMAT "%s) to: %s (" SIZE_FORMAT "%s)", 270 from->name(), byte_size_in_proper_unit(from->max_capacity()), proper_unit_for_byte_size(from->max_capacity()), 271 to->name(), byte_size_in_proper_unit(to->max_capacity()), proper_unit_for_byte_size(to->max_capacity())); 272 return false; 273 } 274 275 log_info(gc)("Transfer " SIZE_FORMAT " region(s) from %s to %s", regions_to_transfer, from->name(), to->name()); 276 from->decrease_capacity(regions_to_transfer * ShenandoahHeapRegion::region_size_bytes()); 277 to->increase_capacity(regions_to_transfer * ShenandoahHeapRegion::region_size_bytes()); 278 return true; 279 } 280 281 size_t ShenandoahGenerationSizer::adjust_transfer_from_young(ShenandoahGeneration* from, size_t regions_to_transfer) const { 282 assert(from->generation_mode() == YOUNG, "Expect to transfer from young"); 283 size_t young_capacity_regions = from->max_capacity() / ShenandoahHeapRegion::region_size_bytes(); 284 size_t new_young_regions = young_capacity_regions - regions_to_transfer; 285 size_t minimum_young_regions = min_young_regions(); 286 // Check that we are not going to violate the minimum size constraint. 287 if (new_young_regions < minimum_young_regions) { 288 assert(minimum_young_regions <= young_capacity_regions, "Young is under minimum capacity."); 289 // If the transfer violates the minimum size and there is still some capacity to transfer, 290 // adjust the transfer to take the size to the minimum. Note that this may be zero. 291 regions_to_transfer = young_capacity_regions - minimum_young_regions; 292 } 293 return regions_to_transfer; 294 } 295 296 size_t ShenandoahGenerationSizer::adjust_transfer_to_young(ShenandoahGeneration* to, size_t regions_to_transfer) const { 297 assert(to->generation_mode() == YOUNG, "Can only transfer between young and old."); 298 size_t young_capacity_regions = to->max_capacity() / ShenandoahHeapRegion::region_size_bytes(); 299 size_t new_young_regions = young_capacity_regions + regions_to_transfer; 300 size_t maximum_young_regions = max_young_regions(); 301 // Check that we are not going to violate the maximum size constraint. 302 if (new_young_regions > maximum_young_regions) { 303 assert(maximum_young_regions >= young_capacity_regions, "Young is over maximum capacity"); 304 // If the transfer violates the maximum size and there is still some capacity to transfer, 305 // adjust the transfer to take the size to the maximum. Note that this may be zero. 306 regions_to_transfer = maximum_young_regions - young_capacity_regions; 307 } 308 return regions_to_transfer; 309 } 310 311 size_t ShenandoahGenerationSizer::min_young_size() const { 312 return min_young_regions() * ShenandoahHeapRegion::region_size_bytes(); 313 } 314 315 size_t ShenandoahGenerationSizer::max_young_size() const { 316 return max_young_regions() * ShenandoahHeapRegion::region_size_bytes(); 317 }