1 /* 2 * Copyright Amazon.com Inc. or its affiliates. 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 class ShenandoahMmuTask : public PeriodicTask { 36 ShenandoahMmuTracker* _mmu_tracker; 37 public: 38 explicit ShenandoahMmuTask(ShenandoahMmuTracker* mmu_tracker) : 39 PeriodicTask(GCPauseIntervalMillis), _mmu_tracker(mmu_tracker) {} 40 41 void task() override { 42 _mmu_tracker->report(); 43 } 44 }; 45 46 class ThreadTimeAccumulator : public ThreadClosure { 47 public: 48 size_t total_time; 49 ThreadTimeAccumulator() : total_time(0) {} 50 void do_thread(Thread* thread) override { 51 total_time += os::thread_cpu_time(thread); 52 } 53 }; 54 55 ShenandoahMmuTracker::ShenandoahMmuTracker() : 56 _most_recent_timestamp(0.0), 57 _most_recent_gc_time(0.0), 58 _most_recent_gcu(0.0), 59 _most_recent_mutator_time(0.0), 60 _most_recent_mu(0.0), 61 _most_recent_periodic_time_stamp(0.0), 62 _most_recent_periodic_gc_time(0.0), 63 _most_recent_periodic_mutator_time(0.0), 64 _mmu_periodic_task(new ShenandoahMmuTask(this)) { 65 } 66 67 ShenandoahMmuTracker::~ShenandoahMmuTracker() { 68 _mmu_periodic_task->disenroll(); 69 delete _mmu_periodic_task; 70 } 71 72 void ShenandoahMmuTracker::fetch_cpu_times(double &gc_time, double &mutator_time) { 73 ThreadTimeAccumulator cl; 74 // We include only the gc threads because those are the only threads 75 // we are responsible for. 76 ShenandoahHeap::heap()->gc_threads_do(&cl); 77 double most_recent_gc_thread_time = double(cl.total_time) / NANOSECS_PER_SEC; 78 gc_time = most_recent_gc_thread_time; 79 80 double process_real_time(0.0), process_user_time(0.0), process_system_time(0.0); 81 bool valid = os::getTimesSecs(&process_real_time, &process_user_time, &process_system_time); 82 assert(valid, "don't know why this would not be valid"); 83 mutator_time =(process_user_time + process_system_time) - most_recent_gc_thread_time; 84 } 85 86 void ShenandoahMmuTracker::update_utilization(ShenandoahGeneration* generation, size_t gcid, const char *msg) { 87 double current = os::elapsedTime(); 88 _most_recent_gcid = gcid; 89 _most_recent_is_full = false; 90 91 if (gcid == 0) { 92 fetch_cpu_times(_most_recent_gc_time, _most_recent_mutator_time); 93 94 _most_recent_timestamp = current; 95 } else { 96 double gc_cycle_period = current - _most_recent_timestamp; 97 _most_recent_timestamp = current; 98 99 double gc_thread_time, mutator_thread_time; 100 fetch_cpu_times(gc_thread_time, mutator_thread_time); 101 double gc_time = gc_thread_time - _most_recent_gc_time; 102 _most_recent_gc_time = gc_thread_time; 103 _most_recent_gcu = gc_time / (_active_processors * gc_cycle_period); 104 double mutator_time = mutator_thread_time - _most_recent_mutator_time; 105 _most_recent_mutator_time = mutator_thread_time; 106 _most_recent_mu = mutator_time / (_active_processors * gc_cycle_period); 107 log_info(gc, ergo)("At end of %s: GCU: %.1f%%, MU: %.1f%% during period of %.3fs", 108 msg, _most_recent_gcu * 100, _most_recent_mu * 100, gc_cycle_period); 109 } 110 } 111 112 void ShenandoahMmuTracker::record_young(ShenandoahGeneration* generation, size_t gcid) { 113 update_utilization(generation, gcid, "Concurrent Young GC"); 114 } 115 116 void ShenandoahMmuTracker::record_global(ShenandoahGeneration* generation, size_t gcid) { 117 update_utilization(generation, gcid, "Concurrent Global GC"); 118 } 119 120 void ShenandoahMmuTracker::record_bootstrap(ShenandoahGeneration* generation, size_t gcid, bool candidates_for_mixed) { 121 // Not likely that this will represent an "ideal" GCU, but doesn't hurt to try 122 update_utilization(generation, gcid, "Concurrent Bootstrap GC"); 123 } 124 125 void ShenandoahMmuTracker::record_old_marking_increment(ShenandoahGeneration* generation, size_t gcid, bool old_marking_done, 126 bool has_old_candidates) { 127 // No special processing for old marking 128 double now = os::elapsedTime(); 129 double duration = now - _most_recent_timestamp; 130 131 double gc_time, mutator_time; 132 fetch_cpu_times(gc_time, mutator_time); 133 double gcu = (gc_time - _most_recent_gc_time) / duration; 134 double mu = (mutator_time - _most_recent_mutator_time) / duration; 135 log_info(gc, ergo)("At end of %s: GCU: %.1f%%, MU: %.1f%% for duration %.3fs (totals to be subsumed in next gc report)", 136 old_marking_done? "last OLD marking increment": "OLD marking increment", 137 gcu * 100, mu * 100, duration); 138 } 139 140 void ShenandoahMmuTracker::record_mixed(ShenandoahGeneration* generation, size_t gcid, bool is_mixed_done) { 141 update_utilization(generation, gcid, "Mixed Concurrent GC"); 142 } 143 144 void ShenandoahMmuTracker::record_degenerated(ShenandoahGeneration* generation, 145 size_t gcid, bool is_old_bootstrap, bool is_mixed_done) { 146 if ((gcid == _most_recent_gcid) && _most_recent_is_full) { 147 // Do nothing. This is a redundant recording for the full gc that just completed. 148 // TODO: avoid making the call to record_degenerated() in the case that this degenerated upgraded to full gc. 149 } else if (is_old_bootstrap) { 150 update_utilization(generation, gcid, "Degenerated Bootstrap Old GC"); 151 } else { 152 update_utilization(generation, gcid, "Degenerated Young GC"); 153 } 154 } 155 156 void ShenandoahMmuTracker::record_full(ShenandoahGeneration* generation, size_t gcid) { 157 update_utilization(generation, gcid, "Full GC"); 158 _most_recent_is_full = true; 159 } 160 161 void ShenandoahMmuTracker::report() { 162 // This is only called by the periodic thread. 163 double current = os::elapsedTime(); 164 double time_delta = current - _most_recent_periodic_time_stamp; 165 _most_recent_periodic_time_stamp = current; 166 167 double gc_time, mutator_time; 168 fetch_cpu_times(gc_time, mutator_time); 169 170 double gc_delta = gc_time - _most_recent_periodic_gc_time; 171 _most_recent_periodic_gc_time = gc_time; 172 173 double mutator_delta = mutator_time - _most_recent_periodic_mutator_time; 174 _most_recent_periodic_mutator_time = mutator_time; 175 176 double mu = mutator_delta / (_active_processors * time_delta); 177 double gcu = gc_delta / (_active_processors * time_delta); 178 log_info(gc)("Periodic Sample: GCU = %.3f%%, MU = %.3f%% during most recent %.1fs", gcu * 100, mu * 100, time_delta); 179 } 180 181 void ShenandoahMmuTracker::initialize() { 182 // initialize static data 183 _active_processors = os::initial_active_processor_count(); 184 185 double _most_recent_periodic_time_stamp = os::elapsedTime(); 186 fetch_cpu_times(_most_recent_periodic_gc_time, _most_recent_periodic_mutator_time); 187 _mmu_periodic_task->enroll(); 188 } 189 190 ShenandoahGenerationSizer::ShenandoahGenerationSizer(ShenandoahMmuTracker* mmu_tracker) 191 : _sizer_kind(SizerDefaults), 192 _use_adaptive_sizing(true), 193 _min_desired_young_regions(0), 194 _max_desired_young_regions(0), 195 _resize_increment(double(YoungGenerationSizeIncrement) / 100.0), 196 _mmu_tracker(mmu_tracker) { 197 198 if (FLAG_IS_CMDLINE(NewRatio)) { 199 if (FLAG_IS_CMDLINE(NewSize) || FLAG_IS_CMDLINE(MaxNewSize)) { 200 log_warning(gc, ergo)("-XX:NewSize and -XX:MaxNewSize override -XX:NewRatio"); 201 } else { 202 _sizer_kind = SizerNewRatio; 203 _use_adaptive_sizing = false; 204 return; 205 } 206 } 207 208 if (NewSize > MaxNewSize) { 209 if (FLAG_IS_CMDLINE(MaxNewSize)) { 210 log_warning(gc, ergo)("NewSize (" SIZE_FORMAT "k) is greater than the MaxNewSize (" SIZE_FORMAT "k). " 211 "A new max generation size of " SIZE_FORMAT "k will be used.", 212 NewSize/K, MaxNewSize/K, NewSize/K); 213 } 214 FLAG_SET_ERGO(MaxNewSize, NewSize); 215 } 216 217 if (FLAG_IS_CMDLINE(NewSize)) { 218 _min_desired_young_regions = MAX2(uint(NewSize / ShenandoahHeapRegion::region_size_bytes()), 1U); 219 if (FLAG_IS_CMDLINE(MaxNewSize)) { 220 _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U); 221 _sizer_kind = SizerMaxAndNewSize; 222 _use_adaptive_sizing = _min_desired_young_regions != _max_desired_young_regions; 223 } else { 224 _sizer_kind = SizerNewSizeOnly; 225 } 226 } else if (FLAG_IS_CMDLINE(MaxNewSize)) { 227 _max_desired_young_regions = MAX2(uint(MaxNewSize / ShenandoahHeapRegion::region_size_bytes()), 1U); 228 _sizer_kind = SizerMaxNewSizeOnly; 229 } 230 } 231 232 size_t ShenandoahGenerationSizer::calculate_min_young_regions(size_t heap_region_count) { 233 size_t min_young_regions = (heap_region_count * ShenandoahMinYoungPercentage) / 100; 234 return MAX2(min_young_regions, (size_t) 1U); 235 } 236 237 size_t ShenandoahGenerationSizer::calculate_max_young_regions(size_t heap_region_count) { 238 size_t max_young_regions = (heap_region_count * ShenandoahMaxYoungPercentage) / 100; 239 return MAX2(max_young_regions, (size_t) 1U); 240 } 241 242 void ShenandoahGenerationSizer::recalculate_min_max_young_length(size_t heap_region_count) { 243 assert(heap_region_count > 0, "Heap must be initialized"); 244 245 switch (_sizer_kind) { 246 case SizerDefaults: 247 _min_desired_young_regions = calculate_min_young_regions(heap_region_count); 248 _max_desired_young_regions = calculate_max_young_regions(heap_region_count); 249 break; 250 case SizerNewSizeOnly: 251 _max_desired_young_regions = calculate_max_young_regions(heap_region_count); 252 _max_desired_young_regions = MAX2(_min_desired_young_regions, _max_desired_young_regions); 253 break; 254 case SizerMaxNewSizeOnly: 255 _min_desired_young_regions = calculate_min_young_regions(heap_region_count); 256 _min_desired_young_regions = MIN2(_min_desired_young_regions, _max_desired_young_regions); 257 break; 258 case SizerMaxAndNewSize: 259 // Do nothing. Values set on the command line, don't update them at runtime. 260 break; 261 case SizerNewRatio: 262 _min_desired_young_regions = MAX2(uint(heap_region_count / (NewRatio + 1)), 1U); 263 _max_desired_young_regions = _min_desired_young_regions; 264 break; 265 default: 266 ShouldNotReachHere(); 267 } 268 269 assert(_min_desired_young_regions <= _max_desired_young_regions, "Invalid min/max young gen size values"); 270 } 271 272 void ShenandoahGenerationSizer::heap_size_changed(size_t heap_size) { 273 recalculate_min_max_young_length(heap_size / ShenandoahHeapRegion::region_size_bytes()); 274 } 275 276 // Returns true iff transfer is successful 277 bool ShenandoahGenerationSizer::transfer_to_old(size_t regions) const { 278 ShenandoahHeap* heap = ShenandoahHeap::heap(); 279 ShenandoahGeneration* old_gen = heap->old_generation(); 280 ShenandoahGeneration* young_gen = heap->young_generation(); 281 size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); 282 size_t bytes_to_transfer = regions * region_size_bytes; 283 284 if (young_gen->free_unaffiliated_regions() < regions) { 285 return false; 286 } else if (old_gen->max_capacity() + bytes_to_transfer > heap->max_size_for(old_gen)) { 287 return false; 288 } else if (young_gen->max_capacity() - bytes_to_transfer < heap->min_size_for(young_gen)) { 289 return false; 290 } else { 291 young_gen->decrease_capacity(bytes_to_transfer); 292 old_gen->increase_capacity(bytes_to_transfer); 293 size_t new_size = old_gen->max_capacity(); 294 log_info(gc)("Transfer " SIZE_FORMAT " region(s) from %s to %s, yielding increased size: " SIZE_FORMAT "%s", 295 regions, young_gen->name(), old_gen->name(), 296 byte_size_in_proper_unit(new_size), proper_unit_for_byte_size(new_size)); 297 return true; 298 } 299 } 300 301 // This is used when promoting humongous or highly utilized regular regions in place. It is not required in this situation 302 // that the transferred regions be unaffiliated. 303 void ShenandoahGenerationSizer::force_transfer_to_old(size_t regions) const { 304 ShenandoahHeap* heap = ShenandoahHeap::heap(); 305 ShenandoahGeneration* old_gen = heap->old_generation(); 306 ShenandoahGeneration* young_gen = heap->young_generation(); 307 size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); 308 size_t bytes_to_transfer = regions * region_size_bytes; 309 310 young_gen->decrease_capacity(bytes_to_transfer); 311 old_gen->increase_capacity(bytes_to_transfer); 312 size_t new_size = old_gen->max_capacity(); 313 log_info(gc)("Forcing transfer of " SIZE_FORMAT " region(s) from %s to %s, yielding increased size: " SIZE_FORMAT "%s", 314 regions, young_gen->name(), old_gen->name(), 315 byte_size_in_proper_unit(new_size), proper_unit_for_byte_size(new_size)); 316 } 317 318 319 bool ShenandoahGenerationSizer::transfer_to_young(size_t regions) const { 320 ShenandoahHeap* heap = ShenandoahHeap::heap(); 321 ShenandoahGeneration* old_gen = heap->old_generation(); 322 ShenandoahGeneration* young_gen = heap->young_generation(); 323 size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); 324 size_t bytes_to_transfer = regions * region_size_bytes; 325 326 if (old_gen->free_unaffiliated_regions() < regions) { 327 return false; 328 } else if (young_gen->max_capacity() + bytes_to_transfer > heap->max_size_for(young_gen)) { 329 return false; 330 } else if (old_gen->max_capacity() - bytes_to_transfer < heap->min_size_for(old_gen)) { 331 return false; 332 } else { 333 old_gen->decrease_capacity(bytes_to_transfer); 334 young_gen->increase_capacity(bytes_to_transfer); 335 size_t new_size = young_gen->max_capacity(); 336 log_info(gc)("Transfer " SIZE_FORMAT " region(s) from %s to %s, yielding increased size: " SIZE_FORMAT "%s", 337 regions, old_gen->name(), young_gen->name(), 338 byte_size_in_proper_unit(new_size), proper_unit_for_byte_size(new_size)); 339 return true; 340 } 341 } 342 343 size_t ShenandoahGenerationSizer::min_young_size() const { 344 return min_young_regions() * ShenandoahHeapRegion::region_size_bytes(); 345 } 346 347 size_t ShenandoahGenerationSizer::max_young_size() const { 348 return max_young_regions() * ShenandoahHeapRegion::region_size_bytes(); 349 }