From 77f5b763281ba1a243f630f7a3a44d875dd6a7fa Mon Sep 17 00:00:00 2001 From: gerard ziemski <63425797+gerard-ziemski@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:12:01 -0500 Subject: [PATCH 1/2] experiment: NMT memory tracking using hash table --- src/hotspot/share/nmt/memTag.hpp | 1 + .../share/nmt/memoryPointersHashtable.cpp | 288 ++++++++++++++++++ .../share/nmt/memoryPointersHashtable.hpp | 70 +++++ src/hotspot/share/runtime/os.cpp | 8 + src/hotspot/share/runtime/threads.cpp | 6 + 5 files changed, 373 insertions(+) create mode 100644 src/hotspot/share/nmt/memoryPointersHashtable.cpp create mode 100644 src/hotspot/share/nmt/memoryPointersHashtable.hpp diff --git a/src/hotspot/share/nmt/memTag.hpp b/src/hotspot/share/nmt/memTag.hpp index 9255645638d83..9605a1b7c3fde 100644 --- a/src/hotspot/share/nmt/memTag.hpp +++ b/src/hotspot/share/nmt/memTag.hpp @@ -58,6 +58,7 @@ f(mtMetaspace, "Metaspace") \ f(mtStringDedup, "String Deduplication") \ f(mtObjectMonitor, "Object Monitors") \ + f(mtNMT_MP, "NMT Memory Pointers") /* internal used by NMT */ \ f(mtNone, "Unknown") \ //end diff --git a/src/hotspot/share/nmt/memoryPointersHashtable.cpp b/src/hotspot/share/nmt/memoryPointersHashtable.cpp new file mode 100644 index 0000000000000..3f99bb7a54ba2 --- /dev/null +++ b/src/hotspot/share/nmt/memoryPointersHashtable.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2003, 2025, 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 "cds/cdsConfig.hpp" +#include "logging/log.hpp" +#include "logging/logStream.hpp" +#include "memory/iterator.hpp" +#include "memory/resourceArea.hpp" +#include "nmt/memoryPointersHashtable.hpp" +#include "utilities/concurrentHashTable.inline.hpp" +#include "utilities/ostream.hpp" +#include "utilities/tableStatistics.hpp" +#include "runtime/javaThread.inline.hpp" + + +void print(void* ptr) { + fprintf(stderr, "--> (%p)\n", ptr); +} + +JavaThread* getValidThread(void) { + Thread* raw_thread = Thread::current_or_null_safe(); + if (raw_thread != nullptr && raw_thread->is_Java_thread()) { + JavaThread* jthread = JavaThread::cast(raw_thread); + if (!jthread->is_exiting()) { + return jthread; + } + } + return nullptr; +} + +const int _nmt_pointers_dictionary_size = 19997; + +// 2^24 is max size, like StringTable. +const size_t END_SIZE = 24; +// If a chain gets to 100 something might be wrong +const size_t REHASH_LEN = 100; +const bool ENABLE_STATISTICS = false; +Mutex::Rank RANK = Mutex::service-5; + +MemoryPointersHashtable* _dictionary = nullptr; + +MemoryPointersHashtable::MemoryPointersHashtable(size_t table_size) + : _number_of_entries(0) { + + size_t start_size_log_2 = MAX2(log2i_ceil(table_size), 2); // 2 is minimum size even though some dictionaries only have one entry + size_t current_size = ((size_t)1) << start_size_log_2; + log_info(class, loader, data)("MemoryPointersHashtable start size: %zu (%zu)", + current_size, start_size_log_2); + _local_table = new ConcurrentTable(start_size_log_2, END_SIZE, REHASH_LEN, ENABLE_STATISTICS, RANK); +} + +MemoryPointersHashtable::~MemoryPointersHashtable() { + // This deletes the table and all the nodes, by calling free_node in Config. + delete _local_table; +} + +uintx MemoryPointersHashtable::Config::get_hash(Value const& value, bool* is_dead) { + return (uintx)value; +} + +void* MemoryPointersHashtable::Config::allocate_node(void* context, size_t size, Value const& value) { + return AllocateHeap(size, mtNMT_MP); +} + +void MemoryPointersHashtable::Config::free_node(void* context, void* memory, Value const& value) { + FreeHeap(memory); +} + +const int _resize_load_trigger = 5; // load factor that will trigger the resize + +int MemoryPointersHashtable::table_size() const { + Thread* THREAD = Thread::current_or_null_safe(); + return 1 << _local_table->get_size_log2(THREAD); +} + +bool MemoryPointersHashtable::check_if_needs_resize() { + bool resize = ((_number_of_entries > (_resize_load_trigger * table_size())) && + !_local_table->is_max_size_reached()); + return resize; +} + +void MemoryPointersHashtable::pointers_do(void f(void*)) { + auto doit = [&] (void** value) { + void* k = (*value); + f(k); + return true; + }; + + Thread* THREAD = Thread::current_or_null_safe(); + _local_table->do_scan(THREAD, doit); +} + +class MemoryPointersHashtableLookup : StackObj { +private: + void* _ptr; +public: + MemoryPointersHashtableLookup(void* ptr) : _ptr(ptr) { } + uintx get_hash() const { + return (uintx)_ptr; + } + bool equals(void** value) { + return (*value == _ptr); + } + bool is_dead(void** value) { + return false; + } +}; + +void MemoryPointersHashtable::add_ptr(Thread* current, void* ptr) { + //assert_locked_or_safepoint(SystemDictionary_lock); // doesn't matter now + MemoryPointersHashtableLookup lookup(ptr); + bool needs_rehashing, clean_hint; + bool created = _local_table->insert(current, lookup, ptr, &needs_rehashing, &clean_hint); + //assert(created, "created"); + //assert (!needs_rehashing, "needs_rehashing"); + assert(!clean_hint, "clean_hint"); + _number_of_entries++; // still locked + // This table can be resized while another thread is reading it. + if (check_if_needs_resize()) { + fprintf(stderr, "GROW!\n"); + _local_table->grow(current); + + // It would be nice to have a JFR event here, add some logging. + LogTarget(Info, class, loader, data) lt; + if (lt.is_enabled()) { + ResourceMark rm; + LogStream ls(<); + ls.print("MemoryPointersHashtable resized to %d entries %d for ", table_size(), _number_of_entries); + } + } +} + +void MemoryPointersHashtable::remove_ptr(Thread* current, void* ptr) { + MemoryPointersHashtableLookup lookup(ptr); + bool removed = _dictionary->_local_table->remove(current, lookup); + if (!removed) { + fprintf(stderr, "NOT REMOVED?\n"); + } +} + +// This routine does not lock the dictionary. +// +// Since readers don't hold a lock, we must make sure that system +// dictionary entries are only removed at a safepoint (when only one +// thread is running), and are added to in a safe way (all links must +// be updated in an MT-safe manner). +// +// Callers should be aware that an entry could be added just after +// the table is read here, so the caller will not see the new entry. +// The entry may be accessed by the VM thread in verification. +void* MemoryPointersHashtable::find_pointer(Thread* current, void* ptr) { + MemoryPointersHashtableLookup lookup(ptr); + void* result = nullptr; + auto get = [&] (void** value) { + // function called if value is found so is never null + result = (*value); + }; + bool needs_rehashing = false; + _local_table->get(current, lookup, get, &needs_rehashing); + //assert (!needs_rehashing, "!needs_rehashing"); + return result; +} + +void MemoryPointersHashtable::print_size(outputStream* st) const { + st->print_cr("Java dictionary (table_size=%d, classes=%d)", + table_size(), _number_of_entries); +} + +void MemoryPointersHashtable::print_on(outputStream* st) const { + ResourceMark rm; + Thread* THREAD = getValidThread(); + print_size(st); + st->print_cr("^ indicates that initiating loader is different from defining loader"); + + auto printer = [&] (void** entry) { + void* e = *entry; + st->print_cr("pre: %p", e); + return true; + }; + + if (SafepointSynchronize::is_at_safepoint()) { + _local_table->do_safepoint_scan(printer); + } else { + _local_table->do_scan(THREAD, printer); + } + tty->cr(); +} + +void MemoryPointersHashtable::verify() { + guarantee(_number_of_entries >= 0, "Verify of dictionary failed"); + auto verifier = [&] (void** val) { + return true; + }; + + _local_table->do_safepoint_scan(verifier); +} + +void MemoryPointersHashtable::print_table_statistics(outputStream* st, const char* table_name) { + Thread* THREAD = getValidThread(); + static TableStatistics ts; + auto sz = [&] (void** val) { + return sizeof(*val); + }; + ts = _local_table->statistics_get(THREAD, sz, ts); + ts.print(st, table_name); +} + +void MemoryPointersHashtable::createMemoryPointersHashtable() { + assert(_dictionary == nullptr, "must be"); + Thread* THREAD = getValidThread(); + _dictionary = new MemoryPointersHashtable(_nmt_pointers_dictionary_size); + assert(_dictionary != nullptr, "must be"); + + // example: +// void* ptr = os::malloc(128, mtNone); +// fprintf(stderr, " allocated ptr: %p\n", ptr); +// //pd = new PointerData(ptr, NMT_off); +// //fprintf(stderr, " created pd: %p\n", pd); +// _dictionary->add_ptr(THREAD, ptr, ptr); +// void* found = _dictionary->find_pointer(THREAD, ptr); +// fprintf(stderr, " found: %p\n", found); +// os::exit(0); +} + +bool MemoryPointersHashtable::record_alloc(MemTag mem_tag, void* ptr) { + if (mem_tag != mtNMT_MP) { + Thread* THREAD = getValidThread(); + if (_dictionary != nullptr && THREAD != nullptr) { + void* found = _dictionary->find_pointer(THREAD, ptr); + if (found != nullptr) { + //fprintf(stderr, "IT ALREADY EXISTS? (%p:%p)\n", ptr, found); +// _dictionary->pointers_do(print); + } else { + _dictionary->add_ptr(THREAD, ptr); +// fprintf(stderr, "ADDED %p\n", ptr); + return true; + } + } else { +// fprintf(stderr, "SKIPPED %p\n", ptr); +// if (_dictionary != nullptr) { +// _dictionary->_local_table->unsafe_insert(ptr); +// } + } + } else { +// fprintf(stderr, "SKIPPED (MemTag) %p\n", ptr); + } + + return false; +} + +bool MemoryPointersHashtable::record_free(void* ptr) { + Thread* THREAD = getValidThread(); + if (THREAD != nullptr && _dictionary != nullptr) { + void* found = _dictionary->find_pointer(THREAD, ptr); + if (found != nullptr) { + _dictionary->remove_ptr(THREAD, ptr); + // fprintf(stderr, "REMOVED %p\n", ptr); + return true; + } else { + //fprintf(stderr, "IT DOES NOT EXIST? (%p)\n", ptr); + } + } else { + // fprintf(stderr, "SKIPPED %p\n", ptr); + } + + return false; +} diff --git a/src/hotspot/share/nmt/memoryPointersHashtable.hpp b/src/hotspot/share/nmt/memoryPointersHashtable.hpp new file mode 100644 index 0000000000000..48e4ef4564357 --- /dev/null +++ b/src/hotspot/share/nmt/memoryPointersHashtable.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2003, 2024, 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. + * + */ + +#ifndef SHARE_MEMORY_POINTERS_HASHTABLE_HPP +#define SHARE_MEMORY_POINTERS_HASHTABLE_HPP + +#include "utilities/debug.hpp" +#include "utilities/concurrentHashTable.hpp" +#include "nmt/nmtCommon.hpp" + +class MemoryPointersHashtable : public CHeapObj { + int _number_of_entries; + + class Config { + public: + using Value = void*; + static uintx get_hash(Value const& value, bool* is_dead); + static void* allocate_node(void* context, size_t size, Value const& value); + static void free_node(void* context, void* memory, Value const& value); + }; + + using ConcurrentTable = ConcurrentHashTable; + ConcurrentTable* _local_table; + + bool check_if_needs_resize(); + int table_size() const; + +public: + MemoryPointersHashtable(size_t table_size); + ~MemoryPointersHashtable(); + static void createMemoryPointersHashtable(); + static bool record_alloc(MemTag mem_tag, void* ptr); + static bool record_free(void* ptr); + + void add_ptr(Thread* current, void* ptr); + void remove_ptr(Thread* current, void* ptr); + + void* find_pointer(Thread* current, void* ptr); + + void pointers_do(void f(void*)); + + void print_table_statistics(outputStream* st, const char* table_name); + + void print_on(outputStream* st) const; + void print_size(outputStream* st) const; + void verify(); +}; + +#endif // SHARE_MEMORY_POINTERS_HASHTABLE_HPP diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index 8e85c0a8c021f..6501afb8c193e 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -41,6 +41,7 @@ #include "memory/universe.hpp" #include "nmt/mallocHeader.inline.hpp" #include "nmt/mallocTracker.hpp" +#include "nmt/memoryPointersHashtable.hpp" #include "nmt/memTracker.inline.hpp" #include "nmt/nmtCommon.hpp" #include "nmt/nmtPreInit.hpp" @@ -661,7 +662,9 @@ void* os::malloc(size_t size, MemTag mem_tag, const NativeCallStack& stack) { return nullptr; } + MemoryPointersHashtable::record_alloc(mem_tag, outer_ptr); void* const inner_ptr = MemTracker::record_malloc((address)outer_ptr, size, mem_tag, stack); + static size_t _counter = 0; if (CDSConfig::is_dumping_static_archive()) { // Need to deterministically fill all the alignment gaps in C++ structures. @@ -723,7 +726,9 @@ void* os::realloc(void *memblock, size_t size, MemTag mem_tag, const NativeCallS header->mark_block_as_dead(); // the real realloc + MemoryPointersHashtable::record_free(header); void* const new_outer_ptr = permit_forbidden_function::realloc(header, new_outer_size); + MemoryPointersHashtable::record_alloc(mem_tag, new_outer_ptr); if (new_outer_ptr == nullptr) { // realloc(3) failed and the block still exists. @@ -751,7 +756,9 @@ void* os::realloc(void *memblock, size_t size, MemTag mem_tag, const NativeCallS } else { // NMT disabled. + MemoryPointersHashtable::record_free(memblock); rc = permit_forbidden_function::realloc(memblock, size); + MemoryPointersHashtable::record_alloc(mem_tag, rc); if (rc == nullptr) { return nullptr; } @@ -779,6 +786,7 @@ void os::free(void *memblock) { // When NMT is enabled this checks for heap overwrites, then deaccounts the old block. void* const old_outer_ptr = MemTracker::record_free(memblock); + MemoryPointersHashtable::record_free(old_outer_ptr); permit_forbidden_function::free(old_outer_ptr); } diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 203062582a0e2..e6c951539f0f8 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -57,6 +57,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "nmt/memTracker.hpp" +#include "nmt/memoryPointersHashtable.hpp" #include "oops/instanceKlass.hpp" #include "oops/klass.inline.hpp" #include "oops/oop.inline.hpp" @@ -441,6 +442,8 @@ class ReadReleaseFileTask : public PeriodicTask { jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { extern void JDK_Version_init(); + MemoryPointersHashtable::createMemoryPointersHashtable(); + // Preinitialize version info. VM_Version::early_initialize(); @@ -563,6 +566,9 @@ jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { main_thread->set_active_handles(JNIHandleBlock::allocate_block()); MACOS_AARCH64_ONLY(main_thread->init_wx()); + // the soonest we can start using our pointer hashtable +// MemoryPointersHashtable::createMemoryPointersHashtable(); + // Set the _monitor_owner_id now since we will run Java code before the Thread instance // is even created. The same value will be assigned to the Thread instance on init. main_thread->set_monitor_owner_id(ThreadIdentifier::next()); From 6405392ce34870120c3b83201dfe02efbb7fc14b Mon Sep 17 00:00:00 2001 From: gerard ziemski <63425797+gerard-ziemski@users.noreply.github.com> Date: Mon, 9 Jun 2025 14:27:16 -0500 Subject: [PATCH 2/2] revert --- src/hotspot/share/runtime/threads.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/runtime/threads.cpp b/src/hotspot/share/runtime/threads.cpp index 07c7652f5d396..567c9ea782ff8 100644 --- a/src/hotspot/share/runtime/threads.cpp +++ b/src/hotspot/share/runtime/threads.cpp @@ -442,7 +442,7 @@ class ReadReleaseFileTask : public PeriodicTask { jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) { extern void JDK_Version_init(); - MemoryPointersHashtable::createMemoryPointersHashtable(); + //MemoryPointersHashtable::createMemoryPointersHashtable(); // Preinitialize version info. VM_Version::early_initialize();