RFR: 8341491: Reserve and commit memory operations should be protected by NMT lock [v5]

Robert Toyonaga duke at openjdk.org
Wed Apr 9 13:43:01 UTC 2025


> ### Update:
> After some discussion it was decided it's not necessary to expand the lock scope for reserve/commit. Instead, we are opting to add comments explaining the reasons for locking and the conditions to avoid which could lead to races.  Some of the new tests can be kept because they are general enough to be useful outside of this context.
> 
> ### Summary:
> This PR makes memory operations atomic with NMT accounting.
> 
> ### The problem:
> In memory related functions like `os::commit_memory` and `os::reserve_memory` the OS memory operations are currently done before acquiring the the NMT mutex. And the the virtual memory accounting is done later in `MemTracker`, after the lock has been acquired. Doing the memory operations outside of the lock scope can lead to races. 
> 
> 1.1 Thread_1 releases range_A.
> 1.2 Thread_1 tells NMT "range_A has been released".
> 
> 2.1 Thread_2 reserves (the now free) range_A.
> 2.2 Thread_2 tells NMT "range_A is reserved".
> 
> Since the sequence (1.1)  (1.2) is not atomic, if Thread_2 begins operating after (1.1), we can have (1.1) (2.1) (2.2) (1.2). The OS sees two valid subsequent calls (release range_A, followed by map range_A). But NMT sees "reserve range_A", "release range_A" and is now out of sync with the OS.
> 
> ### Solution:
> Where memory operations such as reserve, commit, or release virtual memory happen,  I've expanded the scope of `NmtVirtualMemoryLocker` to protect both the NMT accounting and the memory operation itself. 
> 
> ### Other notes:
> I also simplified this  pattern found in many places:
> 
> if (MemTracker::enabled()) {
>   MemTracker::NmtVirtualMemoryLocker nvml;
>   result = pd_some_operation(addr, bytes);
>   if (result != nullptr) {
>     MemTracker::record_some_operation(addr, bytes);
>   }
> } else {
>   result = pd_unmap_memory(addr, bytes);
> }
> ``` 
> To:
> 
> MemTracker::NmtVirtualMemoryLocker nvml;
> result = pd_unmap_memory(addr, bytes);
> MemTracker::record_some_operation(addr, bytes);
> ``` 
> This is possible because `NmtVirtualMemoryLocker` now checks `MemTracker::enabled()`.  `MemTracker::record_some_operation` already checks `MemTracker::enabled()` and checks against nullptr.  This refactoring previously wasn't possible because `ThreadCritical` was used before https://github.com/openjdk/jdk/pull/22745 introduced `NmtVirtualMemoryLocker`. 
> 
> I considered moving the locking and NMT accounting down into platform specific code:  Ex. lock around { munmap() + MemTracker::record }. The hope was that this would help reduce the size of the critical section...

Robert Toyonaga has updated the pull request incrementally with one additional commit since the last revision:

  improve tests and comments

-------------

Changes:
  - all: https://git.openjdk.org/jdk/pull/24084/files
  - new: https://git.openjdk.org/jdk/pull/24084/files/813a1e49..7b7263b2

Webrevs:
 - full: https://webrevs.openjdk.org/?repo=jdk&pr=24084&range=04
 - incr: https://webrevs.openjdk.org/?repo=jdk&pr=24084&range=03-04

  Stats: 23 lines in 2 files changed: 1 ins; 8 del; 14 mod
  Patch: https://git.openjdk.org/jdk/pull/24084.diff
  Fetch: git fetch https://git.openjdk.org/jdk.git pull/24084/head:pull/24084

PR: https://git.openjdk.org/jdk/pull/24084


More information about the hotspot-dev mailing list