> ### 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