1 /* 2 * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2020 SAP SE. All rights reserved. 4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 5 * 6 * This code is free software; you can redistribute it and/or modify it 7 * under the terms of the GNU General Public License version 2 only, as 8 * published by the Free Software Foundation. 9 * 10 * This code is distributed in the hope that it will be useful, but WITHOUT 11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 13 * version 2 for more details (a copy is included in the LICENSE file that 14 * accompanied this code). 15 * 16 * You should have received a copy of the GNU General Public License version 17 * 2 along with this work; if not, write to the Free Software Foundation, 18 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 19 * 20 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 21 * or visit www.oracle.com if you need additional information or have any 22 * questions. 23 * 24 */ 25 26 #include "precompiled.hpp" 27 #include "memory/metaspace/chunkManager.hpp" 28 #include "memory/metaspace/counters.hpp" 29 #include "memory/metaspace/metablock.hpp" 30 #include "memory/metaspace/metaspaceArena.hpp" 31 #include "memory/metaspace/metaspaceArenaGrowthPolicy.hpp" 32 #include "memory/metaspace/metaspaceContext.hpp" 33 #include "memory/metaspace/metaspaceSettings.hpp" 34 #include "memory/metaspace/metaspaceStatistics.hpp" 35 #include "runtime/mutexLocker.hpp" 36 #include "utilities/debug.hpp" 37 #include "utilities/globalDefinitions.hpp" 38 //#define LOG_PLEASE 39 #include "metaspaceGtestCommon.hpp" 40 #include "metaspaceGtestContexts.hpp" 41 #include "metaspaceGtestSparseArray.hpp" 42 43 using metaspace::AllocationAlignmentByteSize; 44 using metaspace::ArenaGrowthPolicy; 45 using metaspace::ChunkManager; 46 using metaspace::IntCounter; 47 using metaspace::MemRangeCounter; 48 using metaspace::MetaBlock; 49 using metaspace::MetaspaceArena; 50 using metaspace::MetaspaceContext; 51 using metaspace::SizeAtomicCounter; 52 using metaspace::ArenaStats; 53 using metaspace::InUseChunkStats; 54 55 // Little randomness helper 56 static bool fifty_fifty() { 57 return IntRange(100).random_value() < 50; 58 } 59 60 // A MetaspaceArenaTestBed contains a single MetaspaceArena and its lock. 61 // It keeps track of allocations done from this MetaspaceArena. 62 class MetaspaceArenaTestBed : public CHeapObj<mtInternal> { 63 64 MetaspaceArena* _arena; 65 66 const SizeRange _allocation_range; 67 size_t _size_of_last_failed_allocation; 68 69 // We keep track of all allocations done thru the MetaspaceArena to 70 // later check for overwriters. 71 struct allocation_t { 72 allocation_t* next; 73 MetaWord* p; // nullptr if deallocated 74 size_t word_size; 75 void mark() { 76 mark_range(p, word_size); 77 } 78 void verify() const { 79 if (p != nullptr) { 80 check_marked_range(p, word_size); 81 } 82 } 83 }; 84 85 allocation_t* _allocations; 86 87 // We count how much we did allocate and deallocate 88 MemRangeCounter _alloc_count; 89 MemRangeCounter _dealloc_count; 90 91 // Check statistics returned by MetaspaceArena::add_to_statistics() against what 92 // we know we allocated. This is a bit flaky since MetaspaceArena has internal 93 // overhead. 94 void verify_arena_statistics() const { 95 96 ArenaStats stats; 97 _arena->add_to_statistics(&stats); 98 InUseChunkStats in_use_stats = stats.totals(); 99 100 assert(_dealloc_count.total_size() <= _alloc_count.total_size() && 101 _dealloc_count.count() <= _alloc_count.count(), "Sanity"); 102 103 // Check consistency of stats 104 ASSERT_GE(in_use_stats._word_size, in_use_stats._committed_words); 105 ASSERT_EQ(in_use_stats._committed_words, 106 in_use_stats._used_words + in_use_stats._free_words + in_use_stats._waste_words); 107 ASSERT_GE(in_use_stats._used_words, stats._free_blocks_word_size); 108 109 // Note: reasons why the outside alloc counter and the inside used counter can differ: 110 // - alignment/padding of allocations 111 // - inside used counter contains blocks in free list 112 // - free block list splinter threshold 113 // - if +MetaspaceGuardAllocations, guard costs 114 115 // Since what we deallocated may have been given back to us in a following allocation, 116 // we only know fore sure we allocated what we did not give back. 117 const size_t at_least_allocated = _alloc_count.total_size() - _dealloc_count.total_size(); 118 119 // At most we allocated this: 120 const size_t max_word_overhead_per_alloc = 121 4 + (metaspace::Settings::use_allocation_guard() ? 4 : 0); 122 const size_t at_most_allocated = _alloc_count.total_size() + max_word_overhead_per_alloc * _alloc_count.count(); 123 124 ASSERT_LE(at_least_allocated, in_use_stats._used_words - stats._free_blocks_word_size); 125 ASSERT_GE(at_most_allocated, in_use_stats._used_words - stats._free_blocks_word_size); 126 127 } 128 129 public: 130 131 MetaspaceArena* arena() { return _arena; } 132 133 MetaspaceArenaTestBed(MetaspaceContext* context, const ArenaGrowthPolicy* growth_policy, 134 size_t allocation_alignment_words, SizeRange allocation_range) 135 : _arena(nullptr) 136 , _allocation_range(allocation_range) 137 , _size_of_last_failed_allocation(0) 138 , _allocations(nullptr) 139 { 140 _arena = new MetaspaceArena(context, growth_policy, Metaspace::min_allocation_alignment_words, "gtest-MetaspaceArenaTestBed-sm"); 141 } 142 143 ~MetaspaceArenaTestBed() { 144 145 verify_arena_statistics(); 146 147 allocation_t* a = _allocations; 148 while (a != nullptr) { 149 allocation_t* b = a->next; 150 a->verify(); 151 FREE_C_HEAP_OBJ(a); 152 a = b; 153 } 154 155 DEBUG_ONLY(_arena->verify();) 156 157 // Delete MetaspaceArena. That should clean up all metaspace. 158 delete _arena; 159 160 } 161 162 size_t words_allocated() const { return _alloc_count.total_size(); } 163 int num_allocations() const { return _alloc_count.count(); } 164 165 size_t size_of_last_failed_allocation() const { return _size_of_last_failed_allocation; } 166 167 // Allocate a random amount. Return false if the allocation failed. 168 bool checked_random_allocate() { 169 size_t word_size = 1 + _allocation_range.random_value(); 170 MetaBlock wastage; 171 MetaBlock bl = _arena->allocate(word_size, wastage); 172 // We only expect wastage if either alignment was not met or the chunk remainder 173 // was not large enough. 174 if (wastage.is_nonempty()) { 175 _arena->deallocate(wastage); 176 wastage.reset(); 177 } 178 if (bl.is_nonempty()) { 179 EXPECT_TRUE(is_aligned(bl.base(), AllocationAlignmentByteSize)); 180 181 allocation_t* a = NEW_C_HEAP_OBJ(allocation_t, mtInternal); 182 a->word_size = word_size; 183 a->p = bl.base(); 184 a->mark(); 185 a->next = _allocations; 186 _allocations = a; 187 _alloc_count.add(word_size); 188 if ((_alloc_count.count() % 20) == 0) { 189 verify_arena_statistics(); 190 DEBUG_ONLY(_arena->verify();) 191 } 192 return true; 193 } else { 194 _size_of_last_failed_allocation = word_size; 195 } 196 return false; 197 } 198 199 // Deallocate a random allocation 200 void checked_random_deallocate() { 201 allocation_t* a = _allocations; 202 while (a && a->p != nullptr && os::random() % 10 != 0) { 203 a = a->next; 204 } 205 if (a != nullptr && a->p != nullptr) { 206 a->verify(); 207 _arena->deallocate(MetaBlock(a->p, a->word_size)); 208 _dealloc_count.add(a->word_size); 209 a->p = nullptr; a->word_size = 0; 210 if ((_dealloc_count.count() % 20) == 0) { 211 verify_arena_statistics(); 212 DEBUG_ONLY(_arena->verify();) 213 } 214 } 215 } 216 217 }; // End: MetaspaceArenaTestBed 218 219 class MetaspaceArenaTest { 220 221 MetaspaceGtestContext _context; 222 223 SizeAtomicCounter _used_words_counter; 224 225 SparseArray<MetaspaceArenaTestBed*> _testbeds; 226 IntCounter _num_beds; 227 228 //////// Bed creation, destruction /////// 229 230 void create_new_test_bed_at(int slotindex, const ArenaGrowthPolicy* growth_policy, SizeRange allocation_range) { 231 DEBUG_ONLY(_testbeds.check_slot_is_null(slotindex)); 232 MetaspaceArenaTestBed* bed = new MetaspaceArenaTestBed(_context.context(), growth_policy, 233 Metaspace::min_allocation_alignment_words, allocation_range); 234 _testbeds.set_at(slotindex, bed); 235 _num_beds.increment(); 236 } 237 238 void create_random_test_bed_at(int slotindex) { 239 SizeRange allocation_range(1, 100); // randomize too? 240 const ArenaGrowthPolicy* growth_policy = ArenaGrowthPolicy::policy_for_space_type( 241 (fifty_fifty() ? Metaspace::StandardMetaspaceType : Metaspace::ReflectionMetaspaceType), 242 fifty_fifty()); 243 create_new_test_bed_at(slotindex, growth_policy, allocation_range); 244 } 245 246 // Randomly create a random test bed at a random slot, and return its slot index 247 // (returns false if we reached max number of test beds) 248 bool create_random_test_bed() { 249 const int slot = _testbeds.random_null_slot_index(); 250 if (slot != -1) { 251 create_random_test_bed_at(slot); 252 } 253 return slot; 254 } 255 256 // Create test beds for all slots 257 void create_all_test_beds() { 258 for (int slot = 0; slot < _testbeds.size(); slot++) { 259 if (_testbeds.slot_is_null(slot)) { 260 create_random_test_bed_at(slot); 261 } 262 } 263 } 264 265 void delete_test_bed_at(int slotindex) { 266 DEBUG_ONLY(_testbeds.check_slot_is_not_null(slotindex)); 267 MetaspaceArenaTestBed* bed = _testbeds.at(slotindex); 268 delete bed; // This will return all its memory to the chunk manager 269 _testbeds.set_at(slotindex, nullptr); 270 _num_beds.decrement(); 271 } 272 273 // Randomly delete a random test bed at a random slot 274 // Return false if there are no test beds to delete. 275 bool delete_random_test_bed() { 276 const int slotindex = _testbeds.random_non_null_slot_index(); 277 if (slotindex != -1) { 278 delete_test_bed_at(slotindex); 279 return true; 280 } 281 return false; 282 } 283 284 // Delete all test beds. 285 void delete_all_test_beds() { 286 for (int slot = _testbeds.first_non_null_slot(); slot != -1; slot = _testbeds.next_non_null_slot(slot)) { 287 delete_test_bed_at(slot); 288 } 289 } 290 291 //////// Allocating metaspace from test beds /////// 292 293 bool random_allocate_from_testbed(int slotindex) { 294 DEBUG_ONLY(_testbeds.check_slot_is_not_null(slotindex);) 295 MetaspaceArenaTestBed* bed = _testbeds.at(slotindex); 296 bool success = bed->checked_random_allocate(); 297 if (success == false) { 298 // We must have hit a limit. 299 EXPECT_LT(_context.commit_limiter().possible_expansion_words(), 300 metaspace::get_raw_word_size_for_requested_word_size(bed->size_of_last_failed_allocation())); 301 } 302 return success; 303 } 304 305 // Allocate multiple times random sizes from a single MetaspaceArena. 306 bool random_allocate_multiple_times_from_testbed(int slotindex, int num_allocations) { 307 bool success = true; 308 int n = 0; 309 while (success && n < num_allocations) { 310 success = random_allocate_from_testbed(slotindex); 311 n++; 312 } 313 return success; 314 } 315 316 // Allocate multiple times random sizes from a single random MetaspaceArena. 317 bool random_allocate_random_times_from_random_testbed() { 318 int slot = _testbeds.random_non_null_slot_index(); 319 bool success = false; 320 if (slot != -1) { 321 const int n = IntRange(5, 20).random_value(); 322 success = random_allocate_multiple_times_from_testbed(slot, n); 323 } 324 return success; 325 } 326 327 /////// Deallocating from testbed /////////////////// 328 329 void deallocate_from_testbed(int slotindex) { 330 DEBUG_ONLY(_testbeds.check_slot_is_not_null(slotindex);) 331 MetaspaceArenaTestBed* bed = _testbeds.at(slotindex); 332 bed->checked_random_deallocate(); 333 } 334 335 void deallocate_from_random_testbed() { 336 int slot = _testbeds.random_non_null_slot_index(); 337 if (slot != -1) { 338 deallocate_from_testbed(slot); 339 } 340 } 341 342 /////// Stats /////////////////////////////////////// 343 344 int get_total_number_of_allocations() const { 345 int sum = 0; 346 for (int i = _testbeds.first_non_null_slot(); i != -1; i = _testbeds.next_non_null_slot(i)) { 347 sum += _testbeds.at(i)->num_allocations(); 348 } 349 return sum; 350 } 351 352 size_t get_total_words_allocated() const { 353 size_t sum = 0; 354 for (int i = _testbeds.first_non_null_slot(); i != -1; i = _testbeds.next_non_null_slot(i)) { 355 sum += _testbeds.at(i)->words_allocated(); 356 } 357 return sum; 358 } 359 360 public: 361 362 MetaspaceArenaTest(size_t commit_limit, int num_testbeds) 363 : _context(commit_limit), 364 _testbeds(num_testbeds), 365 _num_beds() 366 {} 367 368 ~MetaspaceArenaTest () { 369 370 delete_all_test_beds(); 371 372 } 373 374 //////////////// Tests //////////////////////// 375 376 void test() { 377 378 // In a big loop, randomly chose one of these actions 379 // - creating a test bed (simulates a new loader creation) 380 // - allocating from a test bed (simulates allocating metaspace for a loader) 381 // - (rarely) deallocate (simulates metaspace deallocation, e.g. class redefinitions) 382 // - delete a test bed (simulates collection of a loader and subsequent return of metaspace to freelists) 383 384 const int iterations = 2500; 385 386 // Lets have a ceiling on number of words allocated (this is independent from the commit limit) 387 const size_t max_allocation_size = 8 * M; 388 389 bool force_bed_deletion = false; 390 391 for (int niter = 0; niter < iterations; niter++) { 392 393 const int r = IntRange(100).random_value(); 394 395 if (force_bed_deletion || r < 10) { 396 397 force_bed_deletion = false; 398 delete_random_test_bed(); 399 400 } else if (r < 20 || _num_beds.get() < (unsigned)_testbeds.size() / 2) { 401 402 create_random_test_bed(); 403 404 } else if (r < 95) { 405 406 // If allocation fails, we hit the commit limit and should delete some beds first 407 force_bed_deletion = ! random_allocate_random_times_from_random_testbed(); 408 409 } else { 410 411 // Note: does not affect the used words counter. 412 deallocate_from_random_testbed(); 413 414 } 415 416 // If we are close to our quota, start bed deletion 417 if (_used_words_counter.get() >= max_allocation_size) { 418 419 force_bed_deletion = true; 420 421 } 422 423 } 424 425 } 426 427 }; 428 429 // 32 parallel MetaspaceArena objects, random allocating without commit limit 430 TEST_VM(metaspace, MetaspaceArena_random_allocs_32_beds_no_commit_limit) { 431 MetaspaceArenaTest test(max_uintx, 32); 432 test.test(); 433 } 434 435 // 32 parallel Metaspace arena objects, random allocating with commit limit 436 TEST_VM(metaspace, MetaspaceArena_random_allocs_32_beds_with_commit_limit) { 437 MetaspaceArenaTest test(2 * M, 32); 438 test.test(); 439 } 440 441 // A single MetaspaceArena, random allocating without commit limit. This should exercise 442 // chunk enlargement since allocation is undisturbed. 443 TEST_VM(metaspace, MetaspaceArena_random_allocs_1_bed_no_commit_limit) { 444 MetaspaceArenaTest test(max_uintx, 1); 445 test.test(); 446 } 447