From bb96a10a66d338b799e20215a9a53507aa5b516f Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Mon, 20 Jul 2026 13:03:03 -0400 Subject: [PATCH] Improve heap chunk tracking. --- src/hotspot/share/memory/arena.cpp | 16 +- src/hotspot/share/memory/arena.hpp | 1 + src/hotspot/share/nmt/mallocHeader.hpp | 6 +- src/hotspot/share/nmt/mallocSiteTable.cpp | 2 +- src/hotspot/share/nmt/mallocSiteTable.hpp | 6 +- src/hotspot/share/nmt/mallocTracker.cpp | 75 ++++++-- src/hotspot/share/nmt/mallocTracker.hpp | 10 +- src/hotspot/share/nmt/memReporter.cpp | 3 +- src/hotspot/share/nmt/memTracker.hpp | 16 ++ src/hotspot/share/nmt/nmtUsage.cpp | 19 +- .../gtest/nmt/test_nmt_chunk_accounting.cpp | 177 ++++++++++++++++++ .../jtreg/runtime/NMT/HugeArenaTracking.java | 1 + 12 files changed, 281 insertions(+), 51 deletions(-) create mode 100644 test/hotspot/gtest/nmt/test_nmt_chunk_accounting.cpp diff --git a/src/hotspot/share/memory/arena.cpp b/src/hotspot/share/memory/arena.cpp index 75cd909b02864..046c2fda5900e 100644 --- a/src/hotspot/share/memory/arena.cpp +++ b/src/hotspot/share/memory/arena.cpp @@ -87,6 +87,7 @@ class ChunkPool { // Our four static pools static constexpr int _num_pools = 4; static ChunkPool _pools[_num_pools]; + volatile static bool _suspend_cleaning; Chunk* _first; const size_t _size; // (inner payload) size of the chunks this pool serves @@ -110,7 +111,6 @@ class ChunkPool { // Clear this pool of all contained chunks void prune() { // Free all chunks with ChunkPoolLocker lock - // so NMT adjustment is stable. ChunkPoolLocker lock; Chunk* cur = _first; Chunk* next = nullptr; @@ -136,6 +136,7 @@ class ChunkPool { ChunkPool(size_t size) : _first(nullptr), _size(size) {} static void clean() { + if (_suspend_cleaning) return; NativeHeapTrimmer::SuspendMark sm("chunk pool cleaner"); for (int i = 0; i < _num_pools; i++) { _pools[i].prune(); @@ -145,6 +146,9 @@ class ChunkPool { // Returns an initialized and null-terminated Chunk of requested size static Chunk* allocate_chunk(Arena* arena, size_t length, AllocFailType alloc_failmode); static void deallocate_chunk(Chunk* p); + static void set_suspend_cleaning(bool suspend) { + _suspend_cleaning = suspend; + } }; static bool on_compiler_thread() { @@ -194,6 +198,7 @@ Chunk* ChunkPool::allocate_chunk(Arena* arena, size_t length, AllocFailType allo } chunk = (Chunk*)p; } + MemTracker::chunk_assigned_to_arena(chunk, arena->get_mem_tag()); ::new(chunk) Chunk(length); // We rely on arena alignment <= malloc alignment. assert(is_aligned(chunk, ARENA_AMALLOC_ALIGNMENT), "Chunk start address misaligned."); @@ -218,18 +223,19 @@ void ChunkPool::deallocate_chunk(Chunk* c) { c->set_stamp(0); } + MemTracker::add_chunk_to_pool(c); + // If this is a standard-sized chunk, return it to its pool; otherwise free it. ChunkPool* pool = ChunkPool::get_pool_for_size(c->length()); if (pool != nullptr) { pool->return_to_pool(c); } else { - // Free chunks under a lock so that NMT adjustment is stable. - ChunkPoolLocker lock; os::free(c); } } ChunkPool ChunkPool::_pools[] = { Chunk::size, Chunk::medium_size, Chunk::init_size, Chunk::tiny_size }; +volatile bool ChunkPool::_suspend_cleaning = false; class ChunkPoolCleaner : public PeriodicTask { static const int cleaning_interval = 5000; // cleaning interval in ms @@ -241,6 +247,10 @@ class ChunkPoolCleaner : public PeriodicTask { } }; +void Arena::suspend_chunk_pool_cleaning(bool suspend) { + ChunkPool::set_suspend_cleaning(suspend); +} + void Arena::start_chunk_pool_cleaner_task() { #ifdef ASSERT static bool task_created = false; diff --git a/src/hotspot/share/memory/arena.hpp b/src/hotspot/share/memory/arena.hpp index 252e511f615b4..2de49b59a2cb0 100644 --- a/src/hotspot/share/memory/arena.hpp +++ b/src/hotspot/share/memory/arena.hpp @@ -159,6 +159,7 @@ class Arena : public CHeapObjBase { public: static void initialize_chunk_pool(); + static void suspend_chunk_pool_cleaning(bool suspend); // Start the chunk_pool cleaner task static void start_chunk_pool_cleaner_task(); diff --git a/src/hotspot/share/nmt/mallocHeader.hpp b/src/hotspot/share/nmt/mallocHeader.hpp index 33e288dd8e425..39f4431caf169 100644 --- a/src/hotspot/share/nmt/mallocHeader.hpp +++ b/src/hotspot/share/nmt/mallocHeader.hpp @@ -99,8 +99,8 @@ class MallocHeader { NONCOPYABLE(MallocHeader); NOT_LP64(uint32_t _alt_canary); const size_t _size; - const uint32_t _mst_marker; - const MemTag _mem_tag; + uint32_t _mst_marker; + MemTag _mem_tag; const uint8_t _unused; uint16_t _canary; @@ -140,7 +140,9 @@ class MallocHeader { inline static void revive_block(void* memblock); inline size_t size() const { return _size; } inline MemTag mem_tag() const { return _mem_tag; } + inline void set_mem_tag(MemTag new_tag) { _mem_tag = new_tag; } inline uint32_t mst_marker() const { return _mst_marker; } + inline void set_mst_marker(uint32_t new_marker) { _mst_marker = new_marker; } // Return the necessary data to deaccount the block with NMT. FreeInfo free_info() { diff --git a/src/hotspot/share/nmt/mallocSiteTable.cpp b/src/hotspot/share/nmt/mallocSiteTable.cpp index d0b4bbe379fa4..940c425317196 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.cpp +++ b/src/hotspot/share/nmt/mallocSiteTable.cpp @@ -117,7 +117,7 @@ bool MallocSiteTable::walk(MallocSiteWalker* walker) { */ MallocSite* MallocSiteTable::lookup_or_add(const NativeCallStack& key, uint32_t* marker, MemTag mem_tag) { assert(mem_tag != mtNone, "Should have a real memory tag"); - const unsigned int hash = key.calculate_hash(); + const unsigned int hash = malloc_site_hash(key, mem_tag); const unsigned int index = hash_to_index(hash); *marker = 0; diff --git a/src/hotspot/share/nmt/mallocSiteTable.hpp b/src/hotspot/share/nmt/mallocSiteTable.hpp index 22b944301d396..b37714a131e8f 100644 --- a/src/hotspot/share/nmt/mallocSiteTable.hpp +++ b/src/hotspot/share/nmt/mallocSiteTable.hpp @@ -54,6 +54,10 @@ class MallocSite : public AllocationSite { const MemoryCounter* counter() const { return &_c; } }; +inline unsigned int malloc_site_hash(const NativeCallStack& stack, MemTag tag) { + return stack.calculate_hash() + NMTUtil::tag_to_index(tag) * 73; +} + // Malloc site hashtable entry class MallocSiteHashtableEntry : public CHeapObj { private: @@ -64,7 +68,7 @@ class MallocSiteHashtableEntry : public CHeapObj { public: MallocSiteHashtableEntry(NativeCallStack stack, MemTag mem_tag): - _malloc_site(stack, mem_tag), _hash(stack.calculate_hash()), _next(nullptr) { + _malloc_site(stack, mem_tag), _hash(malloc_site_hash(stack, mem_tag)), _next(nullptr) { assert(mem_tag != mtNone, "Expect a real memory tag"); } diff --git a/src/hotspot/share/nmt/mallocTracker.cpp b/src/hotspot/share/nmt/mallocTracker.cpp index 532b6f41bd0ec..be6d0b71dcacc 100644 --- a/src/hotspot/share/nmt/mallocTracker.cpp +++ b/src/hotspot/share/nmt/mallocTracker.cpp @@ -62,23 +62,16 @@ void MemoryCounter::update_peak(size_t size, size_t cnt) { } void MallocMemorySnapshot::copy_to(MallocMemorySnapshot* s) { - // Use lock to make sure that mtChunks don't get deallocated while the - // copy is going on, because their size is adjusted using this - // buffer in make_adjustment(). - ChunkPoolLocker::LockStrategy ls = ChunkPoolLocker::LockStrategy::Lock; - if (VMError::is_error_reported() && VMError::is_error_reported_in_current_thread()) { - ls = ChunkPoolLocker::LockStrategy::Try; - } - ChunkPoolLocker cpl(ls); s->_all_mallocs = _all_mallocs; size_t total_size = 0; size_t total_count = 0; for (int index = 0; index < mt_number_of_tags; index ++) { s->_malloc[index] = _malloc[index]; - total_size += s->_malloc[index].malloc_size(); + total_size += s->_malloc[index].malloc_size() + s->_malloc[index].arena_size(); total_count += s->_malloc[index].malloc_count(); } // malloc counters may be updated concurrently + // Update total here for consistency with saved values of individual sizes. s->_all_mallocs.set_size_and_count(total_size, total_count); } @@ -91,15 +84,6 @@ size_t MallocMemorySnapshot::total_arena() const { return amount; } -// Make adjustment by subtracting chunks used by arenas -// from total chunks to get total free chunk size -void MallocMemorySnapshot::make_adjustment() { - size_t arena_size = total_arena(); - int chunk_idx = NMTUtil::tag_to_index(mtChunk); - _malloc[chunk_idx].record_free(arena_size); - _all_mallocs.deallocate(arena_size); -} - void MallocMemorySummary::initialize() { _snapshot.initialize(); MallocLimitHandler::initialize(MallocLimit); @@ -203,6 +187,61 @@ void* MallocTracker::record_malloc(void* malloc_base, size_t size, MemTag mem_ta return memblock; } +void MallocTracker::chunk_assigned_to_arena(void* memblock, MemTag new_tag) { + MallocHeader* header = (MallocHeader*)memblock - 1; + + // Only decrement mtChunk, leave the total malloc amounts unchanged. + assert(header->mem_tag() == mtChunk, "Should only be operating on heap chunks"); + MallocMemorySummary::as_snapshot()->by_tag(mtChunk)->record_free(header->size()); + + uint32_t new_mst_marker = 0; + if (MemTracker::tracking_level() == NMT_detail) { + // retrieve the old stack from MST + NativeCallStack old_stack; + if (!MallocSiteTable::access_stack(old_stack, *header)) { + fatal("NMT is now out of sync."); + } + MallocSiteTable::deallocation_at(header->size(), header->mst_marker()); + // update MST with new tag + if (!MallocSiteTable::allocation_at(old_stack, header->size(), &new_mst_marker, new_tag)) { + fatal("NMT is now out of sync."); + } + } + + // Arena only accounts for payload. Account for the header size and count. Attribute it to the arena's tag. + MallocMemorySummary::as_snapshot()->by_tag(new_tag)->record_malloc(Chunk::aligned_overhead_size()); + + header->set_mem_tag(new_tag); + header->set_mst_marker(new_mst_marker); +} + +void MallocTracker::add_chunk_to_pool(void* memblock) { + MallocHeader* header = (MallocHeader*)memblock - 1; + assert(header->mem_tag() != mtChunk, "Should only be operating on arena chunks"); + + // Only increment mtChunk, leave the total malloc amounts unchanged. + MallocMemorySummary::as_snapshot()->by_tag(mtChunk)->record_malloc(header->size()); + + uint32_t new_mst_marker = 0; + if (MemTracker::tracking_level() == NMT_detail) { + NativeCallStack old_stack; + // retrieve the old stack from MST + if (!MallocSiteTable::access_stack(old_stack, *header)) { + fatal("NMT is now out of sync."); + } + MallocSiteTable::deallocation_at(header->size(), header->mst_marker()); + // update MST with new tag + if (!MallocSiteTable::allocation_at(old_stack, header->size(), &new_mst_marker, mtChunk)) { + fatal("NMT is now out of sync."); + } + } + + MallocMemorySummary::as_snapshot()->by_tag(header->mem_tag())->record_free(Chunk::aligned_overhead_size()); + + header->set_mem_tag(mtChunk); + header->set_mst_marker(new_mst_marker); +} + void* MallocTracker::record_free_block(void* memblock) { assert(MemTracker::enabled(), "Sanity"); assert(memblock != nullptr, "precondition"); diff --git a/src/hotspot/share/nmt/mallocTracker.hpp b/src/hotspot/share/nmt/mallocTracker.hpp index e96396ebbcb7e..78b35048b56fe 100644 --- a/src/hotspot/share/nmt/mallocTracker.hpp +++ b/src/hotspot/share/nmt/mallocTracker.hpp @@ -176,7 +176,7 @@ class MallocMemorySnapshot { // Total malloc'd memory amount size_t total() const { - return _all_mallocs.size() + malloc_overhead() + total_arena(); + return _all_mallocs.size() + malloc_overhead(); } // Total peak malloc @@ -193,10 +193,6 @@ class MallocMemorySnapshot { size_t total_arena() const; void copy_to(MallocMemorySnapshot* s); - - // Make adjustment by subtracting chunks used by arenas - // from total chunks to get total free chunk size - void make_adjustment(); }; /* @@ -243,7 +239,6 @@ class MallocMemorySummary : AllStatic { static void snapshot(MallocMemorySnapshot* s) { as_snapshot()->copy_to(s); - s->make_adjustment(); } // The memory used by malloc tracking headers @@ -283,6 +278,9 @@ class MallocTracker : AllStatic { static void* record_malloc(void* malloc_base, size_t size, MemTag mem_tag, const NativeCallStack& stack); + static void chunk_assigned_to_arena(void* memblock, MemTag new_tag); + static void add_chunk_to_pool(void* memblock); + // Given a block returned by os::malloc() or os::realloc(): // deaccount block from NMT, mark its header as dead and return pointer to header. static void* record_free_block(void* memblock); diff --git a/src/hotspot/share/nmt/memReporter.cpp b/src/hotspot/share/nmt/memReporter.cpp index 429db6ad31f0f..5c417489f4ae9 100644 --- a/src/hotspot/share/nmt/memReporter.cpp +++ b/src/hotspot/share/nmt/memReporter.cpp @@ -88,7 +88,6 @@ void MemReporterBase::print_malloc(const MemoryCounter* c, MemTag mem_tag) const amount_in_current_scale(amount), scale); } - // blends out mtChunk count number if (count > 0) { out->print(" #%zu", count); } @@ -705,7 +704,7 @@ void MemSummaryDiffReporter::diff_summary_of_tag(MemTag mem_tag, if (amount_in_current_scale(current_malloc_amount) > 0 || diff_in_current_scale(current_malloc_amount, early_malloc_amount) != 0) { out->print("("); - print_malloc_diff(current_malloc_amount, (mem_tag == mtChunk) ? 0 : current_malloc->malloc_count(), + print_malloc_diff(current_malloc_amount, current_malloc->malloc_count(), early_malloc_amount, early_malloc->malloc_count(), mtNone); out->print_cr(")"); } diff --git a/src/hotspot/share/nmt/memTracker.hpp b/src/hotspot/share/nmt/memTracker.hpp index 6b5b6affa14a3..395c3131dfec3 100644 --- a/src/hotspot/share/nmt/memTracker.hpp +++ b/src/hotspot/share/nmt/memTracker.hpp @@ -90,6 +90,22 @@ class MemTracker : AllStatic { return mem_base; } + static inline void chunk_assigned_to_arena(void* memblock, MemTag new_tag) { + assert(memblock != nullptr, "caller should handle null"); + if (!enabled()) { + return; + } + MallocTracker::chunk_assigned_to_arena(memblock, new_tag); + } + + static inline void add_chunk_to_pool(void* memblock) { + assert(memblock != nullptr, "caller should handle null"); + if (!enabled()) { + return; + } + MallocTracker::add_chunk_to_pool(memblock); + } + // Record malloc free and return malloc base address static inline void* record_free(void* memblock) { // Never turned on diff --git a/src/hotspot/share/nmt/nmtUsage.cpp b/src/hotspot/share/nmt/nmtUsage.cpp index 9e6fc3e183bac..2a39d2d87b8da 100644 --- a/src/hotspot/share/nmt/nmtUsage.cpp +++ b/src/hotspot/share/nmt/nmtUsage.cpp @@ -22,7 +22,6 @@ * */ -#include "memory/arena.hpp" #include "nmt/mallocTracker.hpp" #include "nmt/memoryFileTracker.hpp" #include "nmt/memTracker.hpp" @@ -55,33 +54,17 @@ void NMTUsage::walk_thread_stacks() { } void NMTUsage::update_malloc_usage() { - MallocMemorySnapshot* ms; - // Lock needed to keep values in sync, total area size - // is deducted from mtChunk in the end to give correct values. - { - ChunkPoolLocker::LockStrategy ls = ChunkPoolLocker::LockStrategy::Lock; - if (VMError::is_error_reported() && VMError::is_error_reported_in_current_thread()) { - ls = ChunkPoolLocker::LockStrategy::Try; - } - ChunkPoolLocker cpl(ls); - ms = MallocMemorySummary::as_snapshot(); - } + MallocMemorySnapshot* ms = MallocMemorySummary::as_snapshot(); - size_t total_arena_size = 0; for (int i = 0; i < mt_number_of_tags; i++) { MemTag mem_tag = NMTUtil::index_to_tag(i); const MallocMemory* mm = ms->by_tag(mem_tag); _malloc_by_type[i] = mm->malloc_size() + mm->arena_size(); - total_arena_size += mm->arena_size(); } // Total malloc size. _malloc_total = ms->total(); - // Adjustment due to mtChunk double counting. - _malloc_by_type[NMTUtil::tag_to_index(mtChunk)] -= total_arena_size; - _malloc_total -= total_arena_size; - // Adjust mtNMT to include malloc overhead. _malloc_by_type[NMTUtil::tag_to_index(mtNMT)] += ms->malloc_overhead(); } diff --git a/test/hotspot/gtest/nmt/test_nmt_chunk_accounting.cpp b/test/hotspot/gtest/nmt/test_nmt_chunk_accounting.cpp new file mode 100644 index 0000000000000..c4f25d76058b2 --- /dev/null +++ b/test/hotspot/gtest/nmt/test_nmt_chunk_accounting.cpp @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "memory/allocation.hpp" +#include "memory/arena.hpp" +#include "nmt/mallocHeader.inline.hpp" +#include "nmt/mallocSiteTable.hpp" +#include "nmt/mallocTracker.hpp" +#include "nmt/memTracker.hpp" +#include "runtime/os.hpp" +#include "unittest.hpp" + + + +struct CountersSnapshot { + size_t mtChunk_malloc_size; + size_t mtChunk_malloc_count; + size_t mtTest_arena_size; + size_t all_mallocs_size; + size_t all_mallocs_count; +}; + +static CountersSnapshot take_snapshot() { + MallocMemorySnapshot* mms = MallocMemorySummary::as_snapshot(); + CountersSnapshot cs; + cs.mtChunk_malloc_size = mms->by_tag(mtChunk)->malloc_size(); + cs.mtChunk_malloc_count = mms->by_tag(mtChunk)->malloc_count(); + cs.mtTest_arena_size = mms->by_tag(mtTest)->arena_size(); + cs.all_mallocs_size = mms->total(); + cs.all_mallocs_count = mms->total_count(); + return cs; +} + +// This test checks the non-standard sized chunks path which bypasses the chunk pool. +TEST_VM(NMTChunkAccounting, non_standard_chunks) { + if (!MemTracker::enabled()) { + return; + } + + static const size_t NUM_ARENAS = 200; + static const size_t CHUNK_SIZE = ARENA_ALIGN(5000); + + const CountersSnapshot before = take_snapshot(); + const size_t expected_arena_size = before.mtTest_arena_size + NUM_ARENAS * CHUNK_SIZE; + + // Create a bunch of arenas, resulting in allocating new heap chunks. + Arena* arenas[NUM_ARENAS]; + for (size_t i = 0; i < NUM_ARENAS; i++) { + arenas[i] = new (mtTest) Arena(mtTest, Arena::Tag::tag_other, CHUNK_SIZE); + } + + { + const CountersSnapshot middle = take_snapshot(); + // Concurrent code can malloc and free too, therefore we use a leeway factor of 50% + // Verify that the memory was attributed to the arena's tag, not mtChunk. + EXPECT_LE(middle.mtChunk_malloc_size, before.mtChunk_malloc_size + expected_arena_size / 2); + EXPECT_LE(middle.mtChunk_malloc_count, before.mtChunk_malloc_count + NUM_ARENAS / 2); + EXPECT_EQ(middle.mtTest_arena_size, expected_arena_size); + // Total malloc counters should have grown too. + EXPECT_GE(middle.all_mallocs_size, before.all_mallocs_size + expected_arena_size / 2); + EXPECT_GE(middle.all_mallocs_count, before.all_mallocs_count + NUM_ARENAS / 2); + } + + for (size_t i = 0; i < NUM_ARENAS; i++) { + delete arenas[i]; + } + + const CountersSnapshot after = take_snapshot(); + // Verify mtChunk still hasn't changed and mtTest is back to zero. + EXPECT_LE(after.mtChunk_malloc_size, before.mtChunk_malloc_size + expected_arena_size / 2); + EXPECT_LE(after.mtChunk_malloc_count, before.mtChunk_malloc_count + NUM_ARENAS / 2); + EXPECT_EQ(after.mtTest_arena_size, (size_t)0); +} + +// This test uses standard sized chunks which should exercise the code path using the chunk pool. +TEST_VM(NMTChunkAccounting, standard_chunks) { + if (!MemTracker::enabled()) { + return; + } + static const size_t GROW_SIZE = Chunk::size; + static const size_t NUM_ALLOCS = 512; + CountersSnapshot after_grow; + Arena::suspend_chunk_pool_cleaning(true); + { + Arena arena(mtTest); + const CountersSnapshot baseline = take_snapshot(); + for (size_t i = 0; i < NUM_ALLOCS; i++) { + arena.Amalloc(GROW_SIZE); + } + + after_grow = take_snapshot(); + + const size_t expected_arena_size = baseline.mtTest_arena_size + GROW_SIZE * NUM_ALLOCS; + + // Concurrent code can malloc and free too, therefore we use a leeway factor of 50% + // mtChunk should not have grown. + EXPECT_LE(after_grow.mtChunk_malloc_size, baseline.mtChunk_malloc_size + expected_arena_size / 2); + EXPECT_LE(after_grow.mtChunk_malloc_count, baseline.mtChunk_malloc_count + NUM_ALLOCS / 2); + // mtTest should grow matching arena size. + EXPECT_EQ(arena.size_in_bytes(), expected_arena_size); + EXPECT_EQ(after_grow.mtTest_arena_size, arena.size_in_bytes()); + // Total malloc counters should have grown too, but ensure we account for free chunks pre-existing in the pool. + EXPECT_GE(after_grow.all_mallocs_size, baseline.all_mallocs_size + expected_arena_size / 2 - baseline.mtChunk_malloc_size); + EXPECT_GE(after_grow.all_mallocs_count, baseline.all_mallocs_count + NUM_ALLOCS / 2 - baseline.mtChunk_malloc_count); + } + + const CountersSnapshot after_destroy = take_snapshot(); + + // mtChunk should have grown after chunks are returned to the pool. + EXPECT_GE(after_destroy.mtChunk_malloc_size, after_grow.mtChunk_malloc_size + GROW_SIZE * NUM_ALLOCS / 2); + EXPECT_GE(after_destroy.mtChunk_malloc_count, after_grow.mtChunk_malloc_count + NUM_ALLOCS / 2); + // And mtTest should be completely empty after the arena is destroyed. + EXPECT_EQ(after_destroy.mtTest_arena_size, (size_t)0); + + + Arena arena(mtTest); + const CountersSnapshot baseline2 = take_snapshot(); + for (size_t i = 0; i < NUM_ALLOCS; i++) { + arena.Amalloc(GROW_SIZE); + } + const CountersSnapshot after_grow2 = take_snapshot(); + const size_t expected_arena_size = baseline2.mtTest_arena_size + GROW_SIZE * NUM_ALLOCS; + const size_t expected_min_size_shrunk = GROW_SIZE * NUM_ALLOCS / 2; + const size_t expected_min_count_shrunk = NUM_ALLOCS / 2; + + // mtChunk should have decreased as chunks are pulled from pool. + EXPECT_LE(after_grow2.mtChunk_malloc_size, baseline2.mtChunk_malloc_size - expected_min_size_shrunk); + EXPECT_LE(after_grow2.mtChunk_malloc_count, baseline2.mtChunk_malloc_count - expected_min_count_shrunk); + // mtTest should grow matching arena size. + EXPECT_EQ(arena.size_in_bytes(), expected_arena_size); + EXPECT_EQ(after_grow2.mtTest_arena_size, arena.size_in_bytes()); +} + +// This test verifies malloc header tags, MST markers, and stacks. +TEST_VM(NMTChunkAccounting, mst) { + if (!MemTracker::enabled() || MemTracker::tracking_level() != NMT_detail) { + return; + } + void* allocation = os::malloc(100, mtChunk, CALLER_PC); + MallocHeader* header = (MallocHeader*)allocation - 1; + uint32_t old_marker = header->mst_marker(); + NativeCallStack old_stack; + ASSERT_TRUE(MallocSiteTable::access_stack(old_stack, *header)); + + MemTracker::chunk_assigned_to_arena(allocation, mtTest); + EXPECT_TRUE(header->mem_tag() == mtTest); + EXPECT_NE(header->mst_marker(), old_marker); + NativeCallStack new_stack; + EXPECT_TRUE(MallocSiteTable::access_stack(new_stack, *header)); + EXPECT_TRUE(new_stack.equals(old_stack)); + + MemTracker::add_chunk_to_pool(allocation); + ASSERT_TRUE(header->mem_tag() == mtChunk); + ASSERT_TRUE(MallocSiteTable::access_stack(new_stack, *header)); + EXPECT_TRUE(new_stack.equals(old_stack)); + os::free(allocation); +} diff --git a/test/hotspot/jtreg/runtime/NMT/HugeArenaTracking.java b/test/hotspot/jtreg/runtime/NMT/HugeArenaTracking.java index adccbd011700f..b30605e2dea2f 100644 --- a/test/hotspot/jtreg/runtime/NMT/HugeArenaTracking.java +++ b/test/hotspot/jtreg/runtime/NMT/HugeArenaTracking.java @@ -98,6 +98,7 @@ public static void main(String args[]) throws Exception { NMTTestUtils.runJcmdSummaryReportAndCheckOutput( new String[] { "scale=K" }, new String[] { "Test (reserved=1KB, committed=1KB)", + "(malloc=0KB tag=Test", "(arena=1KB #1) (peak=" }); wb.NMTFreeArena(arena2);