1 /* 2 * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. 3 * Copyright (c) 2023 Red Hat, Inc. 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/classLoaderMetaspace.hpp" 28 #include "memory/metaspace/freeBlocks.hpp" 29 #include "memory/metaspace/metablock.inline.hpp" 30 #include "memory/metaspace/metaspaceArena.hpp" 31 #include "memory/metaspace/metaspaceSettings.hpp" 32 #include "memory/metaspace/metaspaceStatistics.hpp" 33 #include "memory/metaspace.hpp" 34 #include "oops/klass.hpp" 35 #include "runtime/mutex.hpp" 36 #include "utilities/debug.hpp" 37 #include "utilities/align.hpp" 38 #include "utilities/globalDefinitions.hpp" 39 40 #ifdef _LP64 41 42 #define LOG_PLEASE 43 #include "metaspaceGtestCommon.hpp" 44 #include "metaspaceGtestContexts.hpp" 45 #include "metaspaceGtestRangeHelpers.hpp" 46 #include "metaspaceGtestSparseArray.hpp" 47 48 #define HANDLE_FAILURE \ 49 if (testing::Test::HasFailure()) { \ 50 return; \ 51 } 52 53 namespace metaspace { 54 55 class ClmsTester { 56 57 Mutex _lock; 58 MetaspaceContext* _class_context; 59 MetaspaceContext* _nonclass_context; 60 ClassLoaderMetaspace* _clms; 61 const size_t _klass_arena_alignment_words; 62 unsigned _num_allocations; 63 64 struct Deltas { 65 int num_chunks_delta; 66 ssize_t used_words_delta; 67 int num_freeblocks_delta; 68 ssize_t freeblocks_words_delta; 69 }; 70 71 Deltas calc_deltas(const ArenaStats& before, const ArenaStats& after) { 72 Deltas d; 73 d.num_chunks_delta = after.totals()._num - before.totals()._num; 74 d.used_words_delta = after.totals()._used_words - before.totals()._used_words; 75 d.num_freeblocks_delta = (int)after._free_blocks_num - (int)before._free_blocks_num; 76 d.freeblocks_words_delta = after._free_blocks_word_size - before._free_blocks_word_size; 77 return d; 78 } 79 80 public: 81 82 ClmsTester(size_t klass_alignment_words, Metaspace::MetaspaceType space_type, 83 MetaspaceContext* class_context, MetaspaceContext* nonclass_context) 84 : _lock(Monitor::nosafepoint, "CLMSTest_lock"), 85 _class_context(class_context), _nonclass_context(nonclass_context), 86 _clms(nullptr), _klass_arena_alignment_words(klass_alignment_words), _num_allocations(0) { 87 _clms = new ClassLoaderMetaspace(&_lock, space_type, nonclass_context, class_context, klass_alignment_words); 88 } 89 90 ~ClmsTester() { 91 delete _clms; 92 EXPECT_EQ(_class_context->used_words(), (size_t)0); 93 EXPECT_EQ(_nonclass_context->used_words(), (size_t)0); 94 } 95 96 MetaBlock allocate_and_check(size_t word_size, bool is_class) { 97 98 // take stats before allocation 99 ClmsStats stats_before; 100 _clms->add_to_statistics(&stats_before); 101 102 // allocate 103 MetaWord* p = _clms->allocate(word_size, is_class ? Metaspace::ClassType : Metaspace::NonClassType); 104 _num_allocations ++; 105 106 // take stats after allocation 107 ClmsStats stats_after; 108 _clms->add_to_statistics(&stats_after); 109 110 // for less verbose testing: 111 const ArenaStats& ca_before = stats_before._arena_stats_class; 112 const ArenaStats& ca_after = stats_after._arena_stats_class; 113 const ArenaStats& nca_before = stats_before._arena_stats_nonclass; 114 const ArenaStats& nca_after = stats_after._arena_stats_nonclass; 115 116 // deltas 117 const Deltas d_ca = calc_deltas(ca_before, ca_after); 118 const Deltas d_nca = calc_deltas(nca_before, nca_after); 119 120 #define EXPECT_FREEBLOCKS_UNCHANGED(arena_prefix) \ 121 EXPECT_EQ(d_##arena_prefix.num_freeblocks_delta, 0); \ 122 EXPECT_EQ(d_##arena_prefix.freeblocks_words_delta, (ssize_t)0); 123 124 #define EXPECT_ARENA_UNCHANGED(arena_prefix) \ 125 EXPECT_EQ(d_##arena_prefix.num_chunks_delta, 0); \ 126 EXPECT_EQ(d_##arena_prefix.used_words_delta, (ssize_t)0); 127 128 if (p != nullptr) { 129 130 MetaBlock bl(p, word_size); 131 132 if (is_class) { 133 134 EXPECT_TRUE(bl.is_aligned_base(_klass_arena_alignment_words)); 135 136 if (_num_allocations == 1) { 137 // first allocation: nonclass arena unchanged, class arena grows by 1 chunk and wordsize, 138 // class arena freeblocks unchanged 139 EXPECT_ARENA_UNCHANGED(nca); 140 EXPECT_FREEBLOCKS_UNCHANGED(nca); 141 EXPECT_EQ(d_ca.num_chunks_delta, 1); 142 EXPECT_EQ((size_t)d_ca.used_words_delta, word_size); 143 EXPECT_FREEBLOCKS_UNCHANGED(ca); 144 return bl; 145 } 146 147 // Had this been taken from class arena freeblocks? 148 if (d_ca.num_freeblocks_delta == -1) { 149 // the class arena freeblocks should have gone down, and the non-class arena freeblocks may have gone 150 // up in case the block was larger than required 151 const size_t wordsize_block_taken = (size_t)(-d_ca.freeblocks_words_delta); 152 EXPECT_GE(wordsize_block_taken, word_size); // the block we took must be at least allocation size 153 const size_t expected_freeblock_remainder = wordsize_block_taken - word_size; 154 if (expected_freeblock_remainder > 0) { 155 // the remainder, if it existed, should have been added to nonclass freeblocks 156 EXPECT_EQ(d_nca.num_freeblocks_delta, 1); 157 EXPECT_EQ((size_t)d_nca.freeblocks_words_delta, expected_freeblock_remainder); 158 } 159 // finally, nothing should have happened in the arenas proper. 160 EXPECT_ARENA_UNCHANGED(ca); 161 EXPECT_ARENA_UNCHANGED(nca); 162 return bl; 163 } 164 165 // block was taken from class arena proper 166 167 // We expect allocation waste due to alignment, should have been added to the freeblocks 168 // of nonclass arena. Allocation waste can be 0. If no chunk turnover happened, it must be 169 // smaller than klass alignment, otherwise it can get as large as a commit granule. 170 const size_t max_expected_allocation_waste = 171 d_ca.num_chunks_delta == 0 ? (_klass_arena_alignment_words - 1) : Settings::commit_granule_words(); 172 EXPECT_GE(d_ca.num_chunks_delta, 0); 173 EXPECT_LE(d_ca.num_chunks_delta, 1); 174 EXPECT_GE((size_t)d_ca.used_words_delta, word_size); 175 EXPECT_LE((size_t)d_ca.used_words_delta, word_size + max_expected_allocation_waste); 176 EXPECT_FREEBLOCKS_UNCHANGED(ca); 177 EXPECT_ARENA_UNCHANGED(nca); 178 if (max_expected_allocation_waste > 0) { 179 EXPECT_GE(d_nca.num_freeblocks_delta, 0); 180 EXPECT_LE(d_nca.num_freeblocks_delta, 1); 181 EXPECT_GE(d_nca.freeblocks_words_delta, 0); 182 EXPECT_LE((size_t)d_nca.freeblocks_words_delta, max_expected_allocation_waste); 183 } else { 184 EXPECT_FREEBLOCKS_UNCHANGED(nca); 185 } 186 return bl; 187 188 } // end: is_class 189 190 else 191 192 { 193 // Nonclass arena allocation. 194 // Allocation waste can happen: 195 // - if we allocate from nonclass freeblocks, the block remainder 196 // - if we allocate from arena proper, by chunk turnover 197 198 if (d_nca.freeblocks_words_delta < 0) { 199 // We allocated a block from the nonclass arena freeblocks. 200 const size_t wordsize_block_taken = (size_t)(-d_nca.freeblocks_words_delta); 201 EXPECT_EQ(wordsize_block_taken, word_size); 202 // The number of blocks may or may not have decreased (depending on whether there 203 // was a wastage block) 204 EXPECT_GE(d_nca.num_chunks_delta, -1); 205 EXPECT_LE(d_nca.num_chunks_delta, 0); 206 EXPECT_ARENA_UNCHANGED(nca); 207 EXPECT_ARENA_UNCHANGED(ca); 208 EXPECT_FREEBLOCKS_UNCHANGED(ca); 209 return bl; 210 } 211 212 // We don't expect alignment waste. Only wastage happens at chunk turnover. 213 const size_t max_expected_allocation_waste = 214 d_nca.num_chunks_delta == 0 ? 0 : Settings::commit_granule_words(); 215 EXPECT_ARENA_UNCHANGED(ca); 216 EXPECT_FREEBLOCKS_UNCHANGED(ca); 217 EXPECT_GE(d_nca.num_chunks_delta, 0); 218 EXPECT_LE(d_nca.num_chunks_delta, 1); 219 EXPECT_GE((size_t)d_nca.used_words_delta, word_size); 220 EXPECT_LE((size_t)d_nca.used_words_delta, word_size + max_expected_allocation_waste); 221 if (max_expected_allocation_waste == 0) { 222 EXPECT_FREEBLOCKS_UNCHANGED(nca); 223 } 224 } 225 return bl; 226 227 } // end: allocation successful 228 229 // allocation failed. 230 EXPECT_ARENA_UNCHANGED(ca); 231 EXPECT_FREEBLOCKS_UNCHANGED(ca); 232 EXPECT_ARENA_UNCHANGED(nca); 233 EXPECT_FREEBLOCKS_UNCHANGED(nca); 234 235 return MetaBlock(); 236 } 237 238 MetaBlock allocate_expect_success(size_t word_size, bool is_class) { 239 MetaBlock bl = allocate_and_check(word_size, is_class); 240 EXPECT_TRUE(bl.is_nonempty()); 241 return bl; 242 } 243 244 MetaBlock allocate_expect_failure(size_t word_size, bool is_class) { 245 MetaBlock bl = allocate_and_check(word_size, is_class); 246 EXPECT_TRUE(bl.is_empty()); 247 return bl; 248 } 249 250 void deallocate_and_check(MetaBlock bl, bool is_class) { 251 252 // take stats before deallocation 253 ClmsStats stats_before; 254 _clms->add_to_statistics(&stats_before); 255 256 // allocate 257 _clms->deallocate(bl.base(), bl.word_size(), is_class); 258 259 // take stats after deallocation 260 ClmsStats stats_after; 261 _clms->add_to_statistics(&stats_after); 262 263 // for less verbose testing: 264 const ArenaStats& ca_before = stats_before._arena_stats_class; 265 const ArenaStats& ca_after = stats_after._arena_stats_class; 266 const ArenaStats& nca_before = stats_before._arena_stats_nonclass; 267 const ArenaStats& nca_after = stats_after._arena_stats_nonclass; 268 269 // deltas 270 // deltas 271 const Deltas d_ca = calc_deltas(ca_before, ca_after); 272 const Deltas d_nca = calc_deltas(nca_before, nca_after); 273 274 EXPECT_ARENA_UNCHANGED(ca); 275 EXPECT_ARENA_UNCHANGED(nca); 276 if (is_class) { 277 EXPECT_EQ(d_ca.num_freeblocks_delta, 1); 278 EXPECT_EQ((size_t)d_ca.freeblocks_words_delta, bl.word_size()); 279 EXPECT_FREEBLOCKS_UNCHANGED(nca); 280 } else { 281 EXPECT_EQ(d_nca.num_freeblocks_delta, 1); 282 EXPECT_EQ((size_t)d_nca.freeblocks_words_delta, bl.word_size()); 283 EXPECT_FREEBLOCKS_UNCHANGED(ca); 284 } 285 } 286 }; 287 288 static constexpr size_t klass_size = sizeof(Klass) / BytesPerWord; 289 290 static void basic_test(size_t klass_arena_alignment) { 291 if (Settings::use_allocation_guard()) { 292 return; 293 } 294 MetaspaceGtestContext class_context, nonclass_context; 295 { 296 ClmsTester tester(klass_arena_alignment, Metaspace::StandardMetaspaceType, class_context.context(), nonclass_context.context()); 297 298 MetaBlock bl1 = tester.allocate_expect_success(klass_size, true); 299 HANDLE_FAILURE; 300 301 MetaBlock bl2 = tester.allocate_expect_success(klass_size, true); 302 HANDLE_FAILURE; 303 304 tester.deallocate_and_check(bl1, true); 305 HANDLE_FAILURE; 306 307 MetaBlock bl3 = tester.allocate_expect_success(klass_size, true); 308 HANDLE_FAILURE; 309 EXPECT_EQ(bl3, bl1); // should have gotten the same block back from freelist 310 311 MetaBlock bl4 = tester.allocate_expect_success(Metaspace::min_allocation_word_size, false); 312 HANDLE_FAILURE; 313 314 MetaBlock bl5 = tester.allocate_expect_success(K, false); 315 HANDLE_FAILURE; 316 317 tester.deallocate_and_check(bl5, false); 318 HANDLE_FAILURE; 319 320 MetaBlock bl6 = tester.allocate_expect_success(K, false); 321 HANDLE_FAILURE; 322 323 EXPECT_EQ(bl5, bl6); // should have gotten the same block back from freelist 324 } 325 EXPECT_EQ(class_context.used_words(), (size_t)0); 326 EXPECT_EQ(nonclass_context.used_words(), (size_t)0); 327 // we should have used exactly one commit granule (64K), not more, for each context 328 EXPECT_EQ(class_context.committed_words(), Settings::commit_granule_words()); 329 EXPECT_EQ(nonclass_context.committed_words(), Settings::commit_granule_words()); 330 } 331 332 #define TEST_BASIC_N(n) \ 333 TEST_VM(metaspace, CLMS_basics_##n) { \ 334 basic_test(n); \ 335 } 336 337 TEST_BASIC_N(1) 338 TEST_BASIC_N(4) 339 TEST_BASIC_N(16) 340 TEST_BASIC_N(32) 341 TEST_BASIC_N(128) 342 343 static void test_random(size_t klass_arena_alignment) { 344 if (Settings::use_allocation_guard()) { 345 return; 346 } 347 348 MetaspaceGtestContext class_context, nonclass_context; 349 constexpr int max_allocations = 1024; 350 const SizeRange nonclass_alloc_range(Metaspace::min_allocation_alignment_words, 1024); 351 const SizeRange class_alloc_range(klass_size, 1024); 352 const IntRange one_out_of_ten(0, 10); 353 for (int runs = 9; runs >= 0; runs--) { 354 { 355 ClmsTester tester(64, Metaspace::StandardMetaspaceType, class_context.context(), nonclass_context.context()); 356 struct LifeBlock { 357 MetaBlock bl; 358 bool is_class; 359 }; 360 LifeBlock life_allocations[max_allocations]; 361 for (int i = 0; i < max_allocations; i++) { 362 life_allocations[i].bl.reset(); 363 } 364 365 unsigned num_class_allocs = 0, num_nonclass_allocs = 0, num_class_deallocs = 0, num_nonclass_deallocs = 0; 366 for (int i = 0; i < 5000; i ++) { 367 const int slot = IntRange(0, max_allocations).random_value(); 368 if (life_allocations[slot].bl.is_empty()) { 369 const bool is_class = one_out_of_ten.random_value() == 0; 370 const size_t word_size = 371 is_class ? class_alloc_range.random_value() : nonclass_alloc_range.random_value(); 372 MetaBlock bl = tester.allocate_expect_success(word_size, is_class); 373 HANDLE_FAILURE; 374 life_allocations[slot].bl = bl; 375 life_allocations[slot].is_class = is_class; 376 if (is_class) { 377 num_class_allocs ++; 378 } else { 379 num_nonclass_allocs ++; 380 } 381 } else { 382 tester.deallocate_and_check(life_allocations[slot].bl, life_allocations[slot].is_class); 383 HANDLE_FAILURE; 384 life_allocations[slot].bl.reset(); 385 if (life_allocations[slot].is_class) { 386 num_class_deallocs ++; 387 } else { 388 num_nonclass_deallocs ++; 389 } 390 } 391 } 392 LOG("num class allocs: %u, num nonclass allocs: %u, num class deallocs: %u, num nonclass deallocs: %u", 393 num_class_allocs, num_nonclass_allocs, num_class_deallocs, num_nonclass_deallocs); 394 } 395 EXPECT_EQ(class_context.used_words(), (size_t)0); 396 EXPECT_EQ(nonclass_context.used_words(), (size_t)0); 397 constexpr float fragmentation_factor = 3.0f; 398 const size_t max_expected_nonclass_committed = max_allocations * nonclass_alloc_range.highest() * fragmentation_factor; 399 const size_t max_expected_class_committed = max_allocations * class_alloc_range.highest() * fragmentation_factor; 400 // we should have used exactly one commit granule (64K), not more, for each context 401 EXPECT_LT(class_context.committed_words(), max_expected_class_committed); 402 EXPECT_LT(nonclass_context.committed_words(), max_expected_nonclass_committed); 403 } 404 } 405 406 #define TEST_RANDOM_N(n) \ 407 TEST_VM(metaspace, CLMS_random_##n) { \ 408 test_random(n); \ 409 } 410 411 TEST_RANDOM_N(1) 412 TEST_RANDOM_N(4) 413 TEST_RANDOM_N(16) 414 TEST_RANDOM_N(32) 415 TEST_RANDOM_N(128) 416 417 } // namespace metaspace 418 419 #endif // _LP64