### The Problem Historically, NMT needed special handling for `mtChunk`. Free heap chunks in the chunk pool were tagged with `mtChunk`, and they remained tagged `mtChunk` even after being pulled from the pool and assigned to arenas bearing other tags. For example, a malloc'd heap chunk block assigned to a mtCompiler arena continues to contribute “size” and “count” toward the `mtChunk` tag. This is problematic because that chunk memory is also accounted for in the arena’s “size” – effectively double counting the chunk’s memory.
>From a user’s perspective, `mtChunk` means “free” heap chunks (not assigned to >arenas). The internal NTM accounting had a different meaning: `mtChunk` = free >chunks and arena chunks. This mismatch resulted in several problems: 1. Before reporting `mtChunk` values to the user, NMT had to subtract the total size of all arenas from `mtChunk` size. 2. This patching needed to be done at all locations reporting NMT data. This adds unnecessary complexity because multiple locations need to be aware of this special treatment for `mtChunk`. 3. `mtChunk` malloc count was wrong. This is because arena chunk count was not tracked anywhere, so could not be deducted from `mtChunk` count. So `mtChunk` "size" is the free chunk size, but `mtChunk` "count" is the **total** chunk count. That's a conflicting mismatch. 4. This special report-time patching needed to be protected by a chunk pool lock to prevent inconsistencies. 5. `mtChunk` peak size and peak count are wrong. These values are inflated by chunks assigned to arenas. 6. Awkward delayed timing. Adjustments are made at report time instead of when ownership actually changes. 7. NMT detail reports contain conflicting allocation info. When a chunk is assigned to an arena, the MST still reports the allocation under `mtChunk`. This conflicts with the NMT summary data which has removed the chunk size from the `mtChunk` category. ### Solution This PR makes `mtChunk` internally only represent free chunks. This aligns the internal meaning with what is presented externally to the user. This naturally fixes all the problems listed above. This is done by removing the chunk memory from `mtChunk` when the chunk is assigned to an arena. The assigned arena then assumes responsibility for tracking that chunk’s memory (under it's own tag). This change meshes nicely with the existing way that NMT treats arena memory explained more in the next section. ### Invariants maintained The existing way NMT presents memory externally to the user is as follows: - Per-tag malloc memory: Individual mallocs and **free** heap chunks completely separate from arena memory. - Per-tag arena memory: Memory used by arenas which is backed by heap chunks. - Total malloc memory: The sum of malloc memory and arena memory The approach in this PR does not change any of the above pre-existing semantics. Free chunks are counted in per-tag malloc memory, but when they are assigned to arenas they switch to being counted in per-tag arena memory. A chunk’s contribution to **_total_** malloc memory remains the same for its entire lifetime. ### Breakdown of operations - When a chunk is **created** (regardless of whether it is for the pool or immediately for an arena) - “total malloc” size and # are incremented. This is done as part of the normal `os::malloc` operation. - When chunks are **assigned to arenas** (regardless of whether they are newly created or pulled from pool) - Increment arena size and # - Account chunk header size to arena’s tag - Decrement `mtChunk` size and # - malloc header and MST is updated - “total malloc” size and # remain unchanged - When chunks are **removed from arenas** (regardless of whether they came from the pool originally) - Decrement arena size and # - Deaccount chunk header size from arena’s tag - Increment `mtChunk` size and # - malloc header and MST is updated - Total malloc size and # remains unchanged - When chunks are **destroyed** (either pruned or non-standard size chunk removed from arena) - “total malloc” size and # are decremented. This is done as part of the normal `os::free` operation. <img width="988" height="564" alt="image" src="https://github.com/user-attachments/assets/c850544a-0a48-44f6-ba89-f2444a135e0d" /> ### Notes Mem tags needed to be incorporated into the MST hashing function. This ensures that a heap chunk in the free pool and a chunk backing an arena correctly show up as two separate allocations in an NMT detail report. Even though they have the same allocation site and stack, their tags will differ and allow them to be treated as two unique allocation blocks. Otherwise they'd be lumped together and presented to the user as a single allocation. The "total" malloc counters are only ever touched when a chunk is created/destroyed, not when it changes hands between arenas and the free pool. When chunks are added/removed from arenas, I also account for the chunk header size. This is a small amount but it gets excluded from the arena size (which only includes the chunk payload). The chunk header gets counted toward the arena's tag **malloc** size. Updating the MST upon heap chunk ownership changes also fixes the NMT summary vs detail inconsistency. ### Other Approaches considered I also implemented an alternate solution that changes the internal meaning of malloc vs arena memory. We can consider arena memory as a subset of malloc memory such that per-tag arena size <= per-tag malloc size. And "total" malloc size is simply a sum of all per-tag malloc sizes. In this alternate solution, when a heap chunk is assigned to an arena, the allocation changes tags to the arena tag (like in my current solution) **AND** the new tag's malloc counters are incremented. This means that the chunk memory would show up in per-tag malloc and per-tag arena amounts. The memory is double counted like before this PR's fix, but the semantics have changed so that the per-tag arena amount is interpreted as a subset of the per-tag malloc amount. I decided against this solution because it requires report-time patching of size/counts in order to maintain the existing meanings of values in the NMT report. It also requires extra logic to track "exclusive" malloc peaks, since now the per-tag malloc peaks include arena amounts. --------- - [x] I confirm that I make this contribution in accordance with the [OpenJDK Interim AI Policy](https://openjdk.org/legal/ai). ------------- Commit messages: - Fix heap chunk tracking Changes: https://git.openjdk.org/jdk/pull/32044/files Webrev: https://webrevs.openjdk.org/?repo=jdk&pr=32044&range=00 Issue: https://bugs.openjdk.org/browse/JDK-8325890 Stats: 324 lines in 12 files changed: 273 ins; 43 del; 8 mod Patch: https://git.openjdk.org/jdk/pull/32044.diff Fetch: git fetch https://git.openjdk.org/jdk.git pull/32044/head:pull/32044 PR: https://git.openjdk.org/jdk/pull/32044
