Skip to content

8346123: [REDO] NMT should not use ThreadCritical #22745

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 19 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/hotspot/os/posix/perfMemory_posix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1086,7 +1086,7 @@ static char* mmap_create_shared(size_t size) {
static void unmap_shared(char* addr, size_t bytes) {
int res;
if (MemTracker::enabled()) {
ThreadCritical tc;
MemTracker::NmtVirtualMemoryLocker nvml;
res = ::munmap(addr, bytes);
if (res == 0) {
MemTracker::record_virtual_memory_release((address)addr, bytes);
Expand Down
3 changes: 0 additions & 3 deletions src/hotspot/os/windows/os_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3619,10 +3619,7 @@ bool os::pd_release_memory(char* addr, size_t bytes) {
// Handle mapping error. We assert in debug, unconditionally print a warning in release.
if (err != nullptr) {
log_warning(os)("bad release: [" PTR_FORMAT "-" PTR_FORMAT "): %s", p2i(start), p2i(end), err);
#ifdef ASSERT
os::print_memory_mappings((char*)start, bytes, tty);
assert(false, "bad release: [" PTR_FORMAT "-" PTR_FORMAT "): %s", p2i(start), p2i(end), err);
#endif
return false;
}
// Free this range
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/os/windows/perfMemory_windows.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1803,7 +1803,7 @@ void PerfMemory::detach(char* addr, size_t bytes) {

if (MemTracker::enabled()) {
// it does not go through os api, the operation has to record from here
ThreadCritical tc;
MemTracker::NmtVirtualMemoryLocker nvml;
remove_file_mapping(addr);
MemTracker::record_virtual_memory_release((address)addr, bytes);
} else {
Expand Down
2 changes: 1 addition & 1 deletion src/hotspot/share/nmt/memBaseline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ void MemBaseline::baseline_summary() {
MallocMemorySummary::snapshot(&_malloc_memory_snapshot);
VirtualMemorySummary::snapshot(&_virtual_memory_snapshot);
{
MemoryFileTracker::Instance::Locker lock;
MemTracker::NmtVirtualMemoryLocker nvml;
MemoryFileTracker::Instance::summary_snapshot(&_virtual_memory_snapshot);
}

Expand Down
3 changes: 2 additions & 1 deletion src/hotspot/share/nmt/memReporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "nmt/mallocTracker.hpp"
#include "nmt/memTag.hpp"
#include "nmt/memReporter.hpp"
#include "nmt/memTracker.hpp"
#include "nmt/memoryFileTracker.hpp"
#include "nmt/threadStackTracker.hpp"
#include "nmt/virtualMemoryTracker.hpp"
Expand Down Expand Up @@ -465,7 +466,7 @@ void MemDetailReporter::report_virtual_memory_region(const ReservedMemoryRegion*
void MemDetailReporter::report_memory_file_allocations() {
stringStream st;
{
MemoryFileTracker::Instance::Locker lock;
MemTracker::NmtVirtualMemoryLocker nvml;
MemoryFileTracker::Instance::print_all_reports_on(&st, scale());
}
output()->print_raw(st.freeze());
Expand Down
2 changes: 2 additions & 0 deletions src/hotspot/share/nmt/memTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ NMT_TrackingLevel MemTracker::_tracking_level = NMT_unknown;

MemBaseline MemTracker::_baseline;

bool MemTracker::NmtVirtualMemoryLocker::_safe_to_use;

void MemTracker::initialize() {
bool rc = true;
assert(_tracking_level == NMT_unknown, "only call once");
Expand Down
60 changes: 48 additions & 12 deletions src/hotspot/share/nmt/memTracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
#include "nmt/threadStackTracker.hpp"
#include "nmt/virtualMemoryTracker.hpp"
#include "runtime/mutexLocker.hpp"
#include "runtime/threadCritical.hpp"
#include "utilities/debug.hpp"
#include "utilities/nativeCallStack.hpp"

Expand Down Expand Up @@ -62,6 +61,12 @@ class MemTracker : AllStatic {
return _tracking_level != NMT_unknown;
}

// This may be called on a detached thread during VM init, so we should check that first.
static inline void assert_locked() {
assert(!NmtVirtualMemoryLocker::is_safe_to_use() || NmtVirtualMemory_lock->owned_by_self(),
"should have acquired NmtVirtualMemory_lock");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I would prefer "bootstrap_done" rather than "done_bootstrap" throughout. But you should get
opinions from some of the runtime folks like @coleenp and @dholmes-ora .

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree, bootstrap_done reads better.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a few other is_bootstrapping() predicates in the code like Universe::is_bootstrapping. This might read nicely as is_bootstrapping_done() and the instance is _bootstrapping_done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I've updated everything to use "bootstrapping_done" instead of "done_bootstrap"

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arguably, MemTracker is the wrong place anyway since the problem it tries to solve is not tied to NMT initialization.

The thought behind the "bootstrap_done" was to make visible that it is safe to not lock since we still run effectively single-threaded. Hotspot loading and CreateJavaVM may happen on different threads, but not at the same time.

We can delay real locking until other threads are starting up. That is somewhere in the middle of VM initialization, but long after NMT has booted. But we can do it also as soon as Mutex initialization is done.

So we could either move this to the mutex layer and give us a "MutexLocker::all_initialised". In that case, the implicit assumption is that as long as the JVM initializes this before other threads are started. Or, we could move it to the thread subsystem and give us a marker for when we are not single-threaded anymore. I like the former better.

Copy link
Member

@dholmes-ora dholmes-ora Dec 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hotspot loading and CreateJavaVM may happen on different threads

??? What are you referring to as "hotspot loading"? To me that is CreateJavaVM.

Or, we could move it to the thread subsystem

We already have Threads::number_of_threads().

static inline NMT_TrackingLevel tracking_level() {
return _tracking_level;
}
Expand Down Expand Up @@ -125,7 +130,7 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
if (addr != nullptr) {
ThreadCritical tc;
NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::add_reserved_region((address)addr, size, stack, mem_tag);
}
}
Expand All @@ -151,7 +156,7 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
if (addr != nullptr) {
ThreadCritical tc;
NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::add_reserved_region((address)addr, size, stack, mem_tag);
VirtualMemoryTracker::add_committed_region((address)addr, size, stack);
}
Expand All @@ -162,23 +167,23 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
if (addr != nullptr) {
ThreadCritical tc;
NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::add_committed_region((address)addr, size, stack);
}
}

static inline MemoryFileTracker::MemoryFile* register_file(const char* descriptive_name) {
assert_post_init();
if (!enabled()) return nullptr;
MemoryFileTracker::Instance::Locker lock;
NmtVirtualMemoryLocker nvml;
return MemoryFileTracker::Instance::make_file(descriptive_name);
}

static inline void remove_file(MemoryFileTracker::MemoryFile* file) {
assert_post_init();
if (!enabled()) return;
assert(file != nullptr, "must be");
MemoryFileTracker::Instance::Locker lock;
NmtVirtualMemoryLocker nvml;
MemoryFileTracker::Instance::free_file(file);
}

Expand All @@ -187,7 +192,7 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
assert(file != nullptr, "must be");
MemoryFileTracker::Instance::Locker lock;
NmtVirtualMemoryLocker nvml;
MemoryFileTracker::Instance::allocate_memory(file, offset, size, stack, mem_tag);
}

Expand All @@ -196,7 +201,7 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
assert(file != nullptr, "must be");
MemoryFileTracker::Instance::Locker lock;
NmtVirtualMemoryLocker nvml;
MemoryFileTracker::Instance::free_memory(file, offset, size);
}

Expand All @@ -210,7 +215,7 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
if (addr != nullptr) {
ThreadCritical tc;
NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::split_reserved_region((address)addr, size, split, mem_tag, split_tag);
}
}
Expand All @@ -219,7 +224,7 @@ class MemTracker : AllStatic {
assert_post_init();
if (!enabled()) return;
if (addr != nullptr) {
ThreadCritical tc;
NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::set_reserved_region_type((address)addr, mem_tag);
}
}
Expand Down Expand Up @@ -269,6 +274,39 @@ class MemTracker : AllStatic {
// and return true; false if not found.
static bool print_containing_region(const void* p, outputStream* out);

/*
* NmtVirtualMemoryLocker is similar to MutexLocker but can be used during VM init before mutexes are ready or
* current thread has been assigned. Performs no action during VM init.
*
* Unlike malloc, NMT requires locking for virtual memory operations. This is because it must synchronize the usage
* of global data structures used for modelling the effect of virtual memory operations.
* It is important that locking is used such that the actual OS memory operations (mmap) are done atomically with the
* corresponding NMT accounting (updating the internal model). Currently, this is not the case in all situations
* (see JDK-8341491), but this should be changed in the future.
*
* An issue with using Mutex is that NMT is used early during VM initialization before mutexes are initialized
* and current thread is attached. Mutexes do not work under those conditions, so we must use a flag to avoid
* attempting to lock until initialization is finished. Lack of synchronization here should not be a problem since it
* is single threaded at that point in time anyway.
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good comment

class NmtVirtualMemoryLocker: StackObj {
// Returns true if it is safe to start using this locker.
static bool _safe_to_use;
ConditionalMutexLocker _cml;

public:
NmtVirtualMemoryLocker(): _cml(NmtVirtualMemory_lock, _safe_to_use, Mutex::_no_safepoint_check_flag){}

static inline bool is_safe_to_use() {
return _safe_to_use;
}

// Set in Threads::create_vm once threads and mutexes have been initialized.
static inline void set_safe_to_use() {
_safe_to_use = true;
}
};

private:
static void report(bool summary_only, outputStream* output, size_t scale);

Expand All @@ -277,8 +315,6 @@ class MemTracker : AllStatic {
static NMT_TrackingLevel _tracking_level;
// Stored baseline
static MemBaseline _baseline;
// Query lock
static Mutex* _query_lock;
};

#endif // SHARE_NMT_MEMTRACKER_HPP
11 changes: 0 additions & 11 deletions src/hotspot/share/nmt/memoryFileTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,11 @@
#include "nmt/nmtCommon.hpp"
#include "nmt/nmtNativeCallStackStorage.hpp"
#include "nmt/vmatree.hpp"
#include "runtime/mutex.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/nativeCallStack.hpp"
#include "utilities/ostream.hpp"

MemoryFileTracker* MemoryFileTracker::Instance::_tracker = nullptr;
PlatformMutex* MemoryFileTracker::Instance::_mutex = nullptr;

MemoryFileTracker::MemoryFileTracker(bool is_detailed_mode)
: _stack_storage(is_detailed_mode), _files() {}
Expand Down Expand Up @@ -132,7 +130,6 @@ bool MemoryFileTracker::Instance::initialize(NMT_TrackingLevel tracking_level) {
_tracker = static_cast<MemoryFileTracker*>(os::malloc(sizeof(MemoryFileTracker), mtNMT));
if (_tracker == nullptr) return false;
new (_tracker) MemoryFileTracker(tracking_level == NMT_TrackingLevel::NMT_detail);
_mutex = new PlatformMutex();
return true;
}

Expand Down Expand Up @@ -189,11 +186,3 @@ void MemoryFileTracker::summary_snapshot(VirtualMemorySnapshot* snapshot) const
void MemoryFileTracker::Instance::summary_snapshot(VirtualMemorySnapshot* snapshot) {
_tracker->summary_snapshot(snapshot);
}

MemoryFileTracker::Instance::Locker::Locker() {
MemoryFileTracker::Instance::_mutex->lock();
}

MemoryFileTracker::Instance::Locker::~Locker() {
MemoryFileTracker::Instance::_mutex->unlock();
}
7 changes: 0 additions & 7 deletions src/hotspot/share/nmt/memoryFileTracker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include "nmt/nmtNativeCallStackStorage.hpp"
#include "nmt/virtualMemoryTracker.hpp"
#include "nmt/vmatree.hpp"
#include "runtime/mutex.hpp"
#include "runtime/os.inline.hpp"
#include "utilities/growableArray.hpp"
#include "utilities/nativeCallStack.hpp"
Expand Down Expand Up @@ -93,14 +92,8 @@ class MemoryFileTracker {

class Instance : public AllStatic {
static MemoryFileTracker* _tracker;
static PlatformMutex* _mutex;

public:
class Locker : public StackObj {
public:
Locker();
~Locker();
};

static bool initialize(NMT_TrackingLevel tracking_level);

Expand Down
3 changes: 2 additions & 1 deletion src/hotspot/share/nmt/nmtUsage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "precompiled.hpp"
#include "nmt/mallocTracker.hpp"
#include "nmt/memoryFileTracker.hpp"
#include "nmt/memTracker.hpp"
#include "nmt/nmtCommon.hpp"
#include "nmt/nmtUsage.hpp"
#include "nmt/threadStackTracker.hpp"
Expand Down Expand Up @@ -94,7 +95,7 @@ void NMTUsage::update_vm_usage() {

{ // MemoryFileTracker addition
using MFT = MemoryFileTracker::Instance;
MFT::Locker lock;
MemTracker::NmtVirtualMemoryLocker nvml;
MFT::iterate_summary([&](MemTag tag, const VirtualMemory* vm) {
int i = NMTUtil::tag_to_index(tag);
_vm_by_type[i].committed += vm->committed();
Expand Down
5 changes: 2 additions & 3 deletions src/hotspot/share/nmt/threadStackTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
#include "nmt/threadStackTracker.hpp"
#include "nmt/virtualMemoryTracker.hpp"
#include "runtime/os.hpp"
#include "runtime/threadCritical.hpp"
#include "utilities/align.hpp"
#include "utilities/debug.hpp"
#include "utilities/globalDefinitions.hpp"
Expand All @@ -53,7 +52,7 @@ void ThreadStackTracker::new_thread_stack(void* base, size_t size, const NativeC
assert(base != nullptr, "Should have been filtered");
align_thread_stack_boundaries_inward(base, size);

ThreadCritical tc;
MemTracker::NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::add_reserved_region((address)base, size, stack, mtThreadStack);
_thread_count++;
}
Expand All @@ -63,7 +62,7 @@ void ThreadStackTracker::delete_thread_stack(void* base, size_t size) {
assert(base != nullptr, "Should have been filtered");
align_thread_stack_boundaries_inward(base, size);

ThreadCritical tc;
MemTracker::NmtVirtualMemoryLocker nvml;
VirtualMemoryTracker::remove_released_region((address)base, size);
_thread_count--;
}
Expand Down
13 changes: 11 additions & 2 deletions src/hotspot/share/nmt/virtualMemoryTracker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include "nmt/threadStackTracker.hpp"
#include "nmt/virtualMemoryTracker.hpp"
#include "runtime/os.hpp"
#include "runtime/threadCritical.hpp"
#include "utilities/ostream.hpp"

VirtualMemorySnapshot VirtualMemorySummary::_snapshot;
Expand Down Expand Up @@ -338,6 +337,8 @@ bool VirtualMemoryTracker::add_reserved_region(address base_addr, size_t size,
assert(base_addr != nullptr, "Invalid address");
assert(size > 0, "Invalid size");
assert(_reserved_regions != nullptr, "Sanity check");
MemTracker::assert_locked();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is kind of long. And why the space before the comma? And there's a bunch of
these, suggesting there should be a helper to package this up.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I've gotten rid of this in favor of the helper that Coleen suggested below.

ReservedMemoryRegion rgn(base_addr, size, stack, mem_tag);
ReservedMemoryRegion* reserved_rgn = _reserved_regions->find(rgn);

Expand Down Expand Up @@ -416,6 +417,7 @@ bool VirtualMemoryTracker::add_reserved_region(address base_addr, size_t size,
void VirtualMemoryTracker::set_reserved_region_type(address addr, MemTag mem_tag) {
assert(addr != nullptr, "Invalid address");
assert(_reserved_regions != nullptr, "Sanity check");
MemTracker::assert_locked();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could add this to MemTracker class like:

inline static void assert_locked();

Then put the body

void MemTracker::assert_locked() {
  assert(!is_bootstrapping_done() || NmtVirtualMemory_lock->owned_by_self(),
            "should have acquired NmtVirtualMemory_lock");
}

Then all these calls could be MemTracker::assert_locked().

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I've adopted your suggestion. Thank you!

ReservedMemoryRegion rgn(addr, 1);
ReservedMemoryRegion* reserved_rgn = _reserved_regions->find(rgn);
Expand All @@ -434,6 +436,7 @@ bool VirtualMemoryTracker::add_committed_region(address addr, size_t size,
assert(addr != nullptr, "Invalid address");
assert(size > 0, "Invalid size");
assert(_reserved_regions != nullptr, "Sanity check");
MemTracker::assert_locked();

ReservedMemoryRegion rgn(addr, size);
ReservedMemoryRegion* reserved_rgn = _reserved_regions->find(rgn);
Expand All @@ -454,6 +457,7 @@ bool VirtualMemoryTracker::remove_uncommitted_region(address addr, size_t size)
assert(addr != nullptr, "Invalid address");
assert(size > 0, "Invalid size");
assert(_reserved_regions != nullptr, "Sanity check");
MemTracker::assert_locked();

ReservedMemoryRegion rgn(addr, size);
ReservedMemoryRegion* reserved_rgn = _reserved_regions->find(rgn);
Expand All @@ -469,6 +473,7 @@ bool VirtualMemoryTracker::remove_uncommitted_region(address addr, size_t size)
bool VirtualMemoryTracker::remove_released_region(ReservedMemoryRegion* rgn) {
assert(rgn != nullptr, "Sanity check");
assert(_reserved_regions != nullptr, "Sanity check");
MemTracker::assert_locked();

// uncommit regions within the released region
ReservedMemoryRegion backup(*rgn);
Expand All @@ -490,6 +495,7 @@ bool VirtualMemoryTracker::remove_released_region(address addr, size_t size) {
assert(addr != nullptr, "Invalid address");
assert(size > 0, "Invalid size");
assert(_reserved_regions != nullptr, "Sanity check");
MemTracker::assert_locked();

ReservedMemoryRegion rgn(addr, size);
ReservedMemoryRegion* reserved_rgn = _reserved_regions->find(rgn);
Expand Down Expand Up @@ -621,6 +627,9 @@ class SnapshotThreadStackWalker : public VirtualMemoryWalker {
SnapshotThreadStackWalker() {}

bool do_allocation_site(const ReservedMemoryRegion* rgn) {
if (MemTracker::NmtVirtualMemoryLocker::is_safe_to_use()) {
assert_lock_strong(NmtVirtualMemory_lock);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems like this can use MemTracker::assert_locked(), since that is conditioned on bootstrapping state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert_lock_strong skips checking the lock ownership if DebuggingContext::is_enabled() || VMError::is_error_reported(). MemTracker::assert_locked() does not do that. Is that ok?

if (rgn->mem_tag() == mtThreadStack) {
address stack_bottom = rgn->thread_stack_uncommitted_bottom();
address committed_start;
Expand Down Expand Up @@ -661,7 +670,7 @@ void VirtualMemoryTracker::snapshot_thread_stacks() {

bool VirtualMemoryTracker::walk_virtual_memory(VirtualMemoryWalker* walker) {
assert(_reserved_regions != nullptr, "Sanity check");
ThreadCritical tc;
MemTracker::NmtVirtualMemoryLocker nvml;
// Check that the _reserved_regions haven't been deleted.
if (_reserved_regions != nullptr) {
LinkedListNode<ReservedMemoryRegion>* head = _reserved_regions->head();
Expand Down
Loading