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