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

Reply via email to