• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 #include <array>
16 #include <atomic>
17 #include <chrono>
18 #include <condition_variable>
19 #include <limits>
20 #include <thread>
21 
22 #include "gtest/gtest.h"
23 #include "iostream"
24 #include "libpandabase/utils/utils.h"
25 #include "runtime/include/coretypes/string.h"
26 #include "runtime/include/runtime.h"
27 #include "runtime/include/panda_vm.h"
28 #include "runtime/handle_scope-inl.h"
29 #include "runtime/mem/gc/g1/g1-allocator.h"
30 #include "runtime/mem/gc/generational-gc-base.h"
31 #include "runtime/mem/malloc-proxy-allocator-inl.h"
32 #include "runtime/mem/mem_stats.h"
33 #include "runtime/mem/mem_stats_default.h"
34 #include "runtime/mem/runslots_allocator-inl.h"
35 
36 namespace ark::mem::test {
37 
38 class G1GCFullGCTest : public testing::Test {
39 public:
40     using ObjVec = PandaVector<ObjectHeader *>;
41     using HanVec = PandaVector<VMHandle<ObjectHeader *> *>;
42     static constexpr size_t ROOT_MAX_SIZE = 100000U;
43 
44     static constexpr GCTaskCause MIXED_G1_GC_CAUSE = GCTaskCause::YOUNG_GC_CAUSE;
45     static constexpr GCTaskCause FULL_GC_CAUSE = GCTaskCause::EXPLICIT_CAUSE;
46 
47     enum class TargetSpace { YOUNG, TENURED, LARGE, HUMONG };
48 
49     class GCCounter : public GCListener {
50     public:
GCStarted(const GCTask & task,size_t heapSize)51         void GCStarted([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSize) override
52         {
53             count_++;
54         }
55 
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)56         void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
57                         [[maybe_unused]] size_t heapSize) override
58         {
59         }
60 
61     private:
62         int count_ = 0;
63     };
64 
65     template <typename F>
66     class GCHook : public GCListener {
67     public:
GCHook(F hookArg)68         explicit GCHook(F hookArg) : hook_(hookArg) {};
69 
GCStarted(const GCTask & task,size_t heapSize)70         void GCStarted([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSize) override {}
71 
GCFinished(const GCTask & task,size_t heapSizeBeforeGc,size_t heapSize)72         void GCFinished([[maybe_unused]] const GCTask &task, [[maybe_unused]] size_t heapSizeBeforeGc,
73                         [[maybe_unused]] size_t heapSize) override
74         {
75             hook_();
76         }
77 
78     private:
79         F hook_;
80     };
81 
SetupRuntime(const std::string & gcTypeParam)82     void SetupRuntime(const std::string &gcTypeParam)
83     {
84         RuntimeOptions options;
85         options.SetShouldLoadBootPandaFiles(false);
86         options.SetShouldInitializeIntrinsics(false);
87         options.SetUseTlabForAllocations(false);
88         options.SetGcType(gcTypeParam);
89         options.SetGcTriggerType("debug");
90         options.SetGcDebugTriggerStart(std::numeric_limits<int>::max());
91         options.SetCompilerEnableJit(false);
92         constexpr size_t HEAP_SIZE = 33554432U;
93         options.SetHeapSizeLimit(HEAP_SIZE);
94         constexpr size_t YOUNG_SIZE = 4194304U;
95         options.SetYoungSpaceSize(YOUNG_SIZE);
96         options.SetExplicitConcurrentGcEnabled(false);
97         [[maybe_unused]] bool success = Runtime::Create(options);
98         ASSERT(success);
99 
100         thread = ark::MTManagedThread::GetCurrent();
101         gcType = Runtime::GetGCType(options, plugins::RuntimeTypeToLang(Runtime::GetRuntimeType()));
102         [[maybe_unused]] auto gcLocal = thread->GetVM()->GetGC();
103         ASSERT(gcLocal->GetType() == ark::mem::GCTypeFromString(gcTypeParam));
104         ASSERT(gcLocal->IsGenerational());
105         thread->ManagedCodeBegin();
106     }
107 
ResetRuntime()108     void ResetRuntime()
109     {
110         DeleteHandles();
111         internalAllocator->Delete(gccnt);
112         thread->ManagedCodeEnd();
113         bool success = Runtime::Destroy();
114         ASSERT_TRUE(success) << "Cannot destroy Runtime";
115     }
116 
117     template <typename F, size_t MULTI>
118     ObjVec MakeAllocations(size_t minSize, size_t maxSize, size_t count, size_t *allocated, size_t *requested,
119                            F spaceChecker, bool checkOomInTenured = false);
120 
121     void InitRoot();
122     void MakeObjectsAlive(const ObjVec &objects, int every = 1);
123     void MakeObjectsPermAlive(const ObjVec &objects, int every = 1);
124     void MakeObjectsGarbage(size_t startIdx, size_t afterEndIdx, int every = 1);
125     void DumpHandles();
126     void DumpAliveObjects();
127     void DeleteHandles();
128     bool IsInYoung(uintptr_t addr);
129 
130     template <class LanguageConfig>
131     void PrepareTest();
132 
TearDown()133     void TearDown() override {}
134 
135     // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
136     ark::MTManagedThread *thread {};
137     GCType gcType {};
138 
139     LanguageContext ctx {nullptr};
140     ObjectAllocatorBase *objectAllocator {};
141     mem::InternalAllocatorPtr internalAllocator;
142     PandaVM *vm {};
143     GC *gc {};
144     std::vector<HanVec> handles;
145     MemStatsType *ms {};
146     GCStats *gcMs {};
147     coretypes::Array *root = nullptr;
148     size_t rootSize = 0;
149     GCCounter *gccnt {};
150     // NOLINTEND(misc-non-private-member-variables-in-classes)
151 };
152 
153 template <typename F, size_t MULTI>
MakeAllocations(size_t minSize,size_t maxSize,size_t count,size_t * allocated,size_t * requested,F spaceChecker,bool checkOomInTenured)154 G1GCFullGCTest::ObjVec G1GCFullGCTest::MakeAllocations(size_t minSize, size_t maxSize, size_t count, size_t *allocated,
155                                                        size_t *requested, [[maybe_unused]] F spaceChecker,
156                                                        bool checkOomInTenured)
157 {
158     ASSERT(minSize <= maxSize);
159     *allocated = 0;
160     *requested = 0;
161     // Create array of object templates based on count and max size
162     PandaVector<PandaString> objTemplates(count);
163     size_t objSize = sizeof(coretypes::String) + minSize;
164     for (size_t i = 0; i < count; ++i) {
165         PandaString simpleString;
166         simpleString.resize(objSize - sizeof(coretypes::String));
167         objTemplates[i] = std::move(simpleString);
168         objSize += (maxSize / count + i);  // +i to mess with the alignment
169         if (objSize > maxSize) {
170             objSize = maxSize;
171         }
172     }
173     ObjVec result;
174     result.reserve(count * MULTI);
175     for (size_t j = 0; j < count; ++j) {
176         size_t size = objTemplates[j].length() + sizeof(coretypes::String);
177         if (checkOomInTenured) {
178             // Leaving 5MB in tenured seems OK
179             auto free =
180                 reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
181             constexpr size_t BIG_SIZE = 5000000U;
182             if (size + BIG_SIZE > free) {
183                 return result;
184             }
185         }
186         for (size_t i = 0; i < MULTI; ++i) {
187             // create string of '\0's
188             coretypes::String *stringObj =
189                 coretypes::String::CreateFromMUtf8(reinterpret_cast<const uint8_t *>(&objTemplates[j][0]),
190                                                    objTemplates[j].length(), objTemplates[j].length(), true, ctx, vm);
191             ASSERT(stringObj != nullptr);
192             ASSERT(stringObj->GetLength() == objTemplates[j].length());
193             ASSERT(spaceChecker(ToUintPtr(stringObj)));
194             *allocated += GetAlignedObjectSize(size);
195             *requested += size;
196             result.push_back(stringObj);
197         }
198     }
199     return result;
200 }
201 
InitRoot()202 void G1GCFullGCTest::InitRoot()
203 {
204     ClassLinker *classLinker = Runtime::GetCurrent()->GetClassLinker();
205     Class *klass = classLinker->GetExtension(panda_file::SourceLang::PANDA_ASSEMBLY)
206                        ->GetClass(ctx.GetStringArrayClassDescriptor());
207     ASSERT_NE(klass, nullptr);
208     root = coretypes::Array::Create(klass, ROOT_MAX_SIZE);
209     rootSize = 0;
210     MakeObjectsPermAlive({root});
211 }
212 
MakeObjectsAlive(const ObjVec & objects,int every)213 void G1GCFullGCTest::MakeObjectsAlive(const ObjVec &objects, int every)
214 {
215     int cnt = every;
216     for (auto *obj : objects) {
217         cnt--;
218         if (cnt != 0) {
219             continue;
220         }
221         root->Set(rootSize, obj);
222         rootSize++;
223         ASSERT(rootSize < ROOT_MAX_SIZE);
224         cnt = every;
225     }
226 }
227 
MakeObjectsGarbage(size_t startIdx,size_t afterEndIdx,int every)228 void G1GCFullGCTest::MakeObjectsGarbage(size_t startIdx, size_t afterEndIdx, int every)
229 {
230     int cnt = every;
231     for (size_t i = startIdx; i < afterEndIdx; ++i) {
232         cnt--;
233         if (cnt != 0) {
234             continue;
235         }
236         root->Set(i, static_cast<ObjectHeader *>(nullptr));
237         rootSize++;
238         cnt = every;
239     }
240 }
241 
MakeObjectsPermAlive(const ObjVec & objects,int every)242 void G1GCFullGCTest::MakeObjectsPermAlive(const ObjVec &objects, int every)
243 {
244     HanVec result;
245     result.reserve(objects.size() / every);
246     int cnt = every;
247     for (auto *obj : objects) {
248         cnt--;
249         if (cnt != 0) {
250             continue;
251         }
252         result.push_back(internalAllocator->New<VMHandle<ObjectHeader *>>(thread, obj));
253         cnt = every;
254     }
255     handles.push_back(result);
256 }
257 
DumpHandles()258 void G1GCFullGCTest::DumpHandles()
259 {
260     for (auto &hv : handles) {
261         for (auto *handle : hv) {
262             std::cout << "vector " << (void *)&hv << " handle " << (void *)handle << " obj " << handle->GetPtr()
263                       << std::endl;
264         }
265     }
266 }
267 
DumpAliveObjects()268 void G1GCFullGCTest::DumpAliveObjects()
269 {
270     std::cout << "Alive root array : " << handles[0][0]->GetPtr() << std::endl;
271     for (size_t i = 0; i < rootSize; ++i) {
272         if (root->Get<ObjectHeader *>(i) != nullptr) {
273             std::cout << "Alive idx " << i << " : " << root->Get<ObjectHeader *>(i) << std::endl;
274         }
275     }
276 }
277 
DeleteHandles()278 void G1GCFullGCTest::DeleteHandles()
279 {
280     for (auto &hv : handles) {
281         for (auto *handle : hv) {
282             internalAllocator->Delete(handle);
283         }
284     }
285     handles.clear();
286 }
287 
288 template <class LanguageConfig>
PrepareTest()289 void G1GCFullGCTest::PrepareTest()
290 {
291     if constexpr (std::is_same<LanguageConfig, ark::PandaAssemblyLanguageConfig>::value) {
292         DeleteHandles();
293         ctx = Runtime::GetCurrent()->GetLanguageContext(panda_file::SourceLang::PANDA_ASSEMBLY);
294         objectAllocator = thread->GetVM()->GetGC()->GetObjectAllocator();
295         vm = Runtime::GetCurrent()->GetPandaVM();
296         internalAllocator = Runtime::GetCurrent()->GetClassLinker()->GetAllocator();
297         gc = vm->GetGC();
298         ms = vm->GetMemStats();
299         gcMs = vm->GetGCStats();
300         gccnt = internalAllocator->New<GCCounter>();
301         gc->AddListener(gccnt);
302         InitRoot();
303     } else {
304         UNREACHABLE();  // NYI
305     }
306 }
307 
IsInYoung(uintptr_t addr)308 bool G1GCFullGCTest::IsInYoung(uintptr_t addr)
309 {
310     switch (gcType) {
311         case GCType::GEN_GC: {
312             return objectAllocator->IsObjectInYoungSpace(reinterpret_cast<ObjectHeader *>(addr));
313         }
314         case GCType::G1_GC: {
315             auto memPool = PoolManager::GetMmapMemPool();
316             if (memPool->GetSpaceTypeForAddr(reinterpret_cast<ObjectHeader *>(addr)) != SpaceType::SPACE_TYPE_OBJECT) {
317                 return false;
318             }
319             return AddrToRegion(ToVoidPtr(addr))->HasFlag(RegionFlag::IS_EDEN);
320         }
321         default:
322             UNREACHABLE();  // NYI
323     }
324     return false;
325 }
326 
TEST_F(G1GCFullGCTest,TestIntensiveAlloc)327 TEST_F(G1GCFullGCTest, TestIntensiveAlloc)
328 {
329     std::string gctype = static_cast<std::string>(GCStringFromType(GCType::G1_GC));
330     SetupRuntime(gctype);
331     {
332         HandleScope<ObjectHeader *> scope(thread);
333         PrepareTest<ark::PandaAssemblyLanguageConfig>();
334         [[maybe_unused]] size_t bytes {};
335         [[maybe_unused]] size_t rawObjectsSize {};
336 
337         [[maybe_unused]] size_t youngSize =
338             reinterpret_cast<GenerationalSpaces *>(
339                 reinterpret_cast<ObjectAllocatorGenBase *>(objectAllocator)->GetHeapSpace())
340                 ->GetCurrentYoungSize();
341         [[maybe_unused]] size_t heapSize = mem::MemConfig::GetHeapSizeLimit();
342         [[maybe_unused]] auto g1Alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(objectAllocator);
343         [[maybe_unused]] size_t maxYSize = g1Alloc->GetYoungAllocMaxSize();
344 
345         [[maybe_unused]] auto ySpaceCheck = [this](uintptr_t addr) -> bool { return IsInYoung(addr); };
346         [[maybe_unused]] auto hSpaceCheck = [this](uintptr_t addr) -> bool { return !IsInYoung(addr); };
347         [[maybe_unused]] auto tFree =
348             reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
349         const size_t yObjSize = maxYSize / 10;
350         gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
351         size_t initialHeap = ms->GetFootprintHeap();
352 
353         {
354             // Ordinary young shall not be broken when intermixed with explicits
355             int i = 0;
356             size_t allocated = 0;
357             while (allocated < 2U * heapSize) {
358                 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
359                                                                        ySpaceCheck);
360                 allocated += bytes;
361                 // NOLINTNEXTLINE(readability-magic-numbers)
362                 if (i++ % 100_I == 0) {
363                     gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
364                 }
365             }
366             gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
367             ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
368         }
369 
370         {
371             // Intensive allocations surviving 1 young
372             auto oldRootSize = rootSize;
373             size_t allocated = 0;
374             bool gcHappened = false;
375             GCHook gchook([&oldRootSize, this, &gcHappened]() {
376                 MakeObjectsGarbage(oldRootSize, this->rootSize);
377                 oldRootSize = this->rootSize;
378                 gcHappened = true;
379             });
380             gc->AddListener(&gchook);
381             while (allocated < 4U * heapSize) {
382                 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
383                                                                        ySpaceCheck);
384                 MakeObjectsAlive(ov1, 1);
385                 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
386                             ->GetCurrentFreeTenuredSize();
387                 allocated += bytes;
388             }
389             MakeObjectsGarbage(oldRootSize, rootSize);
390             gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
391             ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
392             gc->RemoveListener(&gchook);
393         }
394 
395         MakeObjectsGarbage(0, rootSize);
396         gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
397         ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
398     }
399     ResetRuntime();
400 }
401 
TEST_F(G1GCFullGCTest,TestExplicitFullNearLimit)402 TEST_F(G1GCFullGCTest, TestExplicitFullNearLimit)
403 {
404     SetupRuntime(static_cast<std::string>(GCStringFromType(GCType::G1_GC)));
405     {
406         HandleScope<ObjectHeader *> scope(thread);
407         PrepareTest<ark::PandaAssemblyLanguageConfig>();
408         [[maybe_unused]] size_t bytes;
409         [[maybe_unused]] size_t rawObjectsSize;
410 
411         [[maybe_unused]] size_t youngSize =
412             reinterpret_cast<GenerationalSpaces *>(
413                 reinterpret_cast<ObjectAllocatorGenBase *>(objectAllocator)->GetHeapSpace())
414                 ->GetCurrentYoungSize();
415         [[maybe_unused]] size_t heapSize = mem::MemConfig::GetHeapSizeLimit();
416         [[maybe_unused]] auto g1Alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(objectAllocator);
417 
418         [[maybe_unused]] auto ySpaceCheck = [this](uintptr_t addr) -> bool { return IsInYoung(addr); };
419         [[maybe_unused]] auto hSpaceCheck = [this](uintptr_t addr) -> bool { return !IsInYoung(addr); };
420         [[maybe_unused]] auto tFree =
421             reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
422         const size_t yObjSize = g1Alloc->GetYoungAllocMaxSize() / 10U;
423         gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
424         size_t initialHeap = ms->GetFootprintHeap();
425 
426         {
427             // Allocating until we are close to OOM, then do release this mem,
428             // do explicit full and allocate the same size once again
429             auto oldRootSize = rootSize;
430             int i = 0;
431             // NOLINTNEXTLINE(readability-magic-numbers)
432             while (tFree > 2.2F * youngSize) {
433                 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
434                                                                        ySpaceCheck);
435                 MakeObjectsAlive(ov1, 1);
436                 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
437                             ->GetCurrentFreeTenuredSize();
438                 i++;
439             }
440             gc->WaitForGCInManaged(GCTask(GCTaskCause::EXPLICIT_CAUSE));
441             MakeObjectsGarbage(oldRootSize, rootSize);
442 
443             oldRootSize = rootSize;
444             while (--i > 0) {
445                 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
446                                                                        ySpaceCheck);
447                 MakeObjectsAlive(ov1, 1);
448                 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
449                             ->GetCurrentFreeTenuredSize();
450             }
451             MakeObjectsGarbage(oldRootSize, rootSize);
452             gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
453         }
454 
455         MakeObjectsGarbage(0, rootSize);
456         gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
457         ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
458     }
459     ResetRuntime();
460 }
461 
TEST_F(G1GCFullGCTest,TestOOMFullNearLimit)462 TEST_F(G1GCFullGCTest, TestOOMFullNearLimit)
463 {
464     SetupRuntime(static_cast<std::string>(GCStringFromType(GCType::G1_GC)));
465     {
466         HandleScope<ObjectHeader *> scope(thread);
467         PrepareTest<ark::PandaAssemblyLanguageConfig>();
468         [[maybe_unused]] size_t bytes;
469         [[maybe_unused]] size_t rawObjectsSize;
470 
471         [[maybe_unused]] size_t youngSize =
472             reinterpret_cast<GenerationalSpaces *>(
473                 reinterpret_cast<ObjectAllocatorGenBase *>(objectAllocator)->GetHeapSpace())
474                 ->GetCurrentYoungSize();
475         [[maybe_unused]] size_t heapSize = mem::MemConfig::GetHeapSizeLimit();
476         [[maybe_unused]] auto g1Alloc = reinterpret_cast<ObjectAllocatorG1<MT_MODE_MULTI> *>(objectAllocator);
477 
478         [[maybe_unused]] auto ySpaceCheck = [this](uintptr_t addr) -> bool { return IsInYoung(addr); };
479         [[maybe_unused]] auto hSpaceCheck = [this](uintptr_t addr) -> bool { return !IsInYoung(addr); };
480         [[maybe_unused]] auto tFree =
481             reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())->GetCurrentFreeTenuredSize();
482         const size_t yObjSize = g1Alloc->GetYoungAllocMaxSize() / 10U;
483         gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
484         size_t initialHeap = ms->GetFootprintHeap();
485 
486         {
487             // Allocating until we are close to OOM, then do release this mem,
488             // then allocate the same size once again checking if we can handle it w/o OOM
489             auto oldRootSize = rootSize;
490             int i = 0;
491             // NOLINTNEXTLINE(readability-magic-numbers)
492             while (tFree > 2.2F * youngSize) {
493                 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
494                                                                        ySpaceCheck);
495                 MakeObjectsAlive(ov1, 1);
496                 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
497                             ->GetCurrentFreeTenuredSize();
498                 i++;
499             }
500             MakeObjectsGarbage(oldRootSize, rootSize);
501 
502             oldRootSize = rootSize;
503             while (--i > 0) {
504                 ObjVec ov1 = MakeAllocations<decltype(ySpaceCheck), 1>(yObjSize, yObjSize, 1, &bytes, &rawObjectsSize,
505                                                                        ySpaceCheck);
506                 MakeObjectsAlive(ov1, 1);
507                 tFree = reinterpret_cast<GenerationalSpaces *>(objectAllocator->GetHeapSpace())
508                             ->GetCurrentFreeTenuredSize();
509             }
510             MakeObjectsGarbage(oldRootSize, rootSize);
511             gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
512         }
513 
514         MakeObjectsGarbage(0, rootSize);
515         gc->WaitForGCInManaged(GCTask(FULL_GC_CAUSE));
516         ASSERT_EQ(initialHeap, ms->GetFootprintHeap());
517     }
518     ResetRuntime();
519 }
520 }  // namespace ark::mem::test
521