Skip to content

Enforce io.netty.maxDirectMemory accounting on all Java versions#16489

Merged
chrisvest merged 3 commits intonetty:4.2from
j-bahr:memory-tracking
Mar 23, 2026
Merged

Enforce io.netty.maxDirectMemory accounting on all Java versions#16489
chrisvest merged 3 commits intonetty:4.2from
j-bahr:memory-tracking

Conversation

@j-bahr
Copy link
Copy Markdown
Contributor

@j-bahr j-bahr commented Mar 16, 2026

Motivation:

Netty selects a Cleaner implementation based on the Java version and whether sun.misc.Unsafe is available. The selection matrix is:

  Java version   | Unsafe available | Cleaner selected
  ───────────────|──────────────────|─────────────────────
  6-8            | Yes              | DirectCleaner
  9-23           | Yes              | DirectCleaner
  24             | Yes (warnings)   | DirectCleaner
  24             | No, native access| CleanerJava24Linker
  25+            | No, native access| CleanerJava24Linker
  25+            | No               | CleanerJava25

Before this change, direct memory accounting (incrementMemoryCounter / decrementMemoryCounter) was coupled to USE_DIRECT_BUFFER_NO_CLEANER, which was only true when Unsafe was available. This had two consequences:

  1. DIRECT_MEMORY_COUNTER was only initialized inside the USE_DIRECT_BUFFER_NO_CLEANER=true branch, so on any Java version without Unsafe the counter was always null even if the user explicitly set io.netty.maxDirectMemory.

  2. The accounting calls themselves lived only in PlatformDependent's legacy NoCleaner methods (allocateDirectNoCleaner, reallocateDirectNoCleaner, freeDirectNoCleaner), which were only called by DirectCleaner. The other Cleaner implementations (CleanerJava9, CleanerJava6, CleanerJava24Linker, CleanerJava25) never called these methods and performed no accounting.

The combined effect was that the configured limit was silently ignored on every path that didn't go through DirectCleaner:

  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava25       | No           | No  ✗

On Java 25+, where Unsafe is disabled by default, this means io.netty.maxDirectMemory has no effect at all.

Modifications:

  • Decouple DIRECT_MEMORY_COUNTER initialization from Unsafe availability. The counter is now created based solely on the value of io.netty.maxDirectMemory, independent of USE_DIRECT_BUFFER_NO_CLEANER.

  • Move accounting into each Cleaner's CleanableDirectBufferImpl so that every allocation/deallocation pair tracks memory regardless of which Cleaner is active:

    • DirectCleaner: increment in CleanableDirectBufferImpl(int capacity) constructor, decrement in clean().
    • CleanerJava9: increment in constructor, decrement in clean().
    • CleanerJava6: increment in constructor, decrement in clean().
    • CleanerJava24Linker: increment before malloc(), decrement in clean(), with rollback on allocation failure.
    • CleanerJava25: increment in allocate() before MethodHandle invoke, decrement in clean() via finally block.
  • Change incrementMemoryCounter/decrementMemoryCounter from private to package-private so Cleaner implementations (same package) can call them directly.

  • Add a default reallocate(CleanableDirectBuffer, int) method to the Cleaner interface with an allocate-copy-free fallback. DirectCleaner overrides this with in-place Unsafe.reallocateMemory, adjusting the counter by the delta.

  • Add PlatformDependent.reallocateDirect() as the unified public entry point for reallocation.

  • Remove the legacy NoCleaner API surface from PlatformDependent: allocateDirectNoCleaner, allocateDirectBufferNoCleaner, reallocateDirectNoCleaner, reallocateDirectBufferNoCleaner, and freeDirectNoCleaner.

  • Remove USE_DIRECT_BUFFER_NO_CLEANER and DIRECT_CLEANER fields. CLEANER is now the single entry point; useDirectBufferNoCleaner() returns whether CLEANER is an instance of DirectCleaner.

  • Update UnpooledUnsafeNoCleanerDirectByteBuf to use the new unified API: remove the allocateDirectBuffer() override (parent's impl now does the right thing via PlatformDependent.allocateDirect()), and delegate reallocateDirect() to PlatformDependent.reallocateDirect().

  • Update PlatformDependentTest.testAllocateWithCapacity0() to use the new CleanableDirectBuffer-based API.

Result:

After this change, the accounting matrix becomes:

  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava25       | Yes          | Yes ✓

io.netty.maxDirectMemory is now enforced on all Java versions and all Cleaner implementations. The legacy raw-ByteBuffer NoCleaner API surface is eliminated, and each CleanableDirectBuffer is responsible for its own accounting.

@j-bahr
Copy link
Copy Markdown
Contributor Author

j-bahr commented Mar 16, 2026

@normanmaurer, I took a stab at fixing the issue outlined in the PR above. I am new to the allocator APIs so I would appreciate a through review and would be happy to accept feedback or suggestions on how to improve this PR.

I did create a little test harness to help validate the changes across java versions. The simple java program can be found here DirectMemoryAccountingTest.java, which can then be run with shell script here run.sh. Based on the raw output from the script (netty-common-4.2.10.Final.jar and netty-common-4.2.11.Final-SNAPSHOT.jar), you can see that the memory accounting fails in a number of cases. After my fix everything passes except in the cases where we don't have native access or unsafe, which results in the NOOP cleaner being used. In these cases the bytebuffers are tracked by the VM. The result matrix is shown below.

   #  | Config                                          | OLD  | NEW
  ----|-------------------------------------------------|------|-----
   1  | Java 8, Unsafe (default)                        | PASS | PASS
   2  | Java 11, Unsafe + opens + tryReflect            | PASS | PASS
   3  | Java 17, Unsafe + opens + tryReflect            | PASS | PASS
   4  | Java 21, Unsafe + opens + tryReflect            | PASS | PASS
   5  | Java 11, Unsafe, no opens                       | FAIL | PASS ✓ fixed
   6  | Java 17, Unsafe, no opens                       | FAIL | PASS ✓ fixed
   7  | Java 21, Unsafe, no opens                       | FAIL | PASS ✓ fixed
   8  | Java 11, noUnsafe=true (NOOP)                   | FAIL | FAIL (expected)
   9  | Java 17, noUnsafe=true (NOOP)                   | FAIL | FAIL (expected)
  10  | Java 21, noUnsafe=true (NOOP)                   | FAIL | FAIL (expected)
  11  | Java 24, Unsafe + opens + tryReflect            | PASS | PASS
  12  | Java 24, Unsafe, no opens                       | FAIL | PASS ✓ fixed
  13  | Java 24, noUnsafe + native access               | FAIL | PASS ✓ fixed
  14  | Java 24, noUnsafe, no native access (NOOP)      | FAIL | FAIL (expected)
  15  | Java 25, Unsafe force-enabled                   | PASS | PASS
  16  | Java 25, noUnsafe, no native access             | FAIL | PASS ✓ fixed
  17  | Java 25, noUnsafe + native access               | FAIL | PASS ✓ fixed
  18  | Java 25, default (no flags)                     | FAIL | PASS ✓ fixed

Thanks, I am looking forward to any feedback you might have.

@franz1981
Copy link
Copy Markdown
Contributor

franz1981 commented Mar 16, 2026

Tbh I like a lot this work @j-bahr !
Well done!
As we are moving to a mostly no unsafe world (still able to use glibc powered memory segments) this means to be able to still make sense of the max direct memory settings as we are used to be

Motivation:

Netty selects a Cleaner implementation based on the Java version and
whether sun.misc.Unsafe is available. The selection matrix is:

  Java version   | Unsafe available | Cleaner selected
  ───────────────|──────────────────|─────────────────────
  6-8            | Yes              | DirectCleaner
  9-23           | Yes              | DirectCleaner
  24             | Yes (warnings)   | DirectCleaner
  24             | No, native access| CleanerJava24Linker
  25+            | No, native access| CleanerJava24Linker
  25+            | No               | CleanerJava25

Before this change, direct memory accounting (incrementMemoryCounter /
decrementMemoryCounter) was coupled to USE_DIRECT_BUFFER_NO_CLEANER,
which was only true when Unsafe was available. This had two consequences:

1. DIRECT_MEMORY_COUNTER was only initialized inside the
   USE_DIRECT_BUFFER_NO_CLEANER=true branch, so on any Java version
   without Unsafe the counter was always null — even if the user
   explicitly set io.netty.maxDirectMemory.

2. The accounting calls themselves lived only in PlatformDependent's
   legacy NoCleaner methods (allocateDirectNoCleaner,
   reallocateDirectNoCleaner, freeDirectNoCleaner), which were only
   called by DirectCleaner. The other Cleaner implementations
   (CleanerJava9, CleanerJava6, CleanerJava24Linker, CleanerJava25)
   never called these methods and performed no accounting.

The combined effect was that the configured limit was silently ignored
on every path that didn't go through DirectCleaner:

  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava25       | No           | No  ✗

On Java 25+, where Unsafe is disabled by default, this means
io.netty.maxDirectMemory has no effect at all.

Modifications:

- Decouple DIRECT_MEMORY_COUNTER initialization from Unsafe
  availability. The counter is now created based solely on the value
  of io.netty.maxDirectMemory, independent of USE_DIRECT_BUFFER_NO_CLEANER.

- Move accounting into each Cleaner's CleanableDirectBufferImpl so that
  every allocation/deallocation pair tracks memory regardless of which
  Cleaner is active:
  - DirectCleaner: increment in CleanableDirectBufferImpl(int capacity)
    constructor, decrement in clean().
  - CleanerJava9: increment in constructor, decrement in clean().
  - CleanerJava6: increment in constructor, decrement in clean().
  - CleanerJava24Linker: increment before malloc(), decrement in clean(),
    with rollback on allocation failure.
  - CleanerJava25: increment in allocate() before MethodHandle invoke,
    decrement in clean() via finally block.

- Change incrementMemoryCounter/decrementMemoryCounter from private to
  package-private so Cleaner implementations (same package) can call
  them directly.

- Add a default reallocate(CleanableDirectBuffer, int) method to the
  Cleaner interface with an allocate-copy-free fallback. DirectCleaner
  overrides this with in-place Unsafe.reallocateMemory, adjusting the
  counter by the delta.

- Add PlatformDependent.reallocateDirect() as the unified public entry
  point for reallocation.

- Remove the legacy NoCleaner API surface from PlatformDependent:
  allocateDirectNoCleaner, allocateDirectBufferNoCleaner,
  reallocateDirectNoCleaner, reallocateDirectBufferNoCleaner, and
  freeDirectNoCleaner.

- Remove USE_DIRECT_BUFFER_NO_CLEANER and DIRECT_CLEANER fields. CLEANER
  is now the single entry point; useDirectBufferNoCleaner() returns
  whether CLEANER is an instance of DirectCleaner.

- Update UnpooledUnsafeNoCleanerDirectByteBuf to use the new unified
  API: remove the allocateDirectBuffer() override (parent's impl now
  does the right thing via PlatformDependent.allocateDirect()), and
  delegate reallocateDirect() to PlatformDependent.reallocateDirect().

- Update PlatformDependentTest.testAllocateWithCapacity0() to use the
  new CleanableDirectBuffer-based API.

Result:

After this change, the accounting matrix becomes:

  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava25       | Yes          | Yes ✓

io.netty.maxDirectMemory is now enforced on all Java versions and all
Cleaner implementations. The legacy raw-ByteBuffer NoCleaner API
surface is eliminated, and each CleanableDirectBuffer is responsible
for its own accounting.
@normanmaurer normanmaurer modified the milestone: 4.2.11.Final Mar 17, 2026
@normanmaurer
Copy link
Copy Markdown
Member

@chrisvest @yawkat PTAL...

@j-bahr I like it!

Copy link
Copy Markdown
Member

@chrisvest chrisvest left a comment

Choose a reason for hiding this comment

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

Two small comments, otherwise looks good.

@chrisvest chrisvest added the needs-cherry-pick-5.0 This PR should be cherry-picked to 5.0 once merged. label Mar 23, 2026
j-bahr and others added 2 commits March 23, 2026 12:54
Co-authored-by: Chris Vest <mr.chrisvest@gmail.com>
Co-authored-by: Chris Vest <mr.chrisvest@gmail.com>
@j-bahr
Copy link
Copy Markdown
Contributor Author

j-bahr commented Mar 23, 2026

Thanks @chrisvest for the review, I applied your suggested fixes.

@chrisvest chrisvest enabled auto-merge (squash) March 23, 2026 20:15
@chrisvest chrisvest merged commit 7937553 into netty:4.2 Mar 23, 2026
19 checks passed
@netty-project-bot
Copy link
Copy Markdown
Contributor

Could not create auto-port PR.
Got conflicts when cherry-picking onto 5.0.

chrisvest pushed a commit to chrisvest/netty that referenced this pull request Mar 23, 2026
…ty#16489)

**Motivation**:

Netty selects a Cleaner implementation based on the Java version and
whether `sun.misc.Unsafe` is available. The selection matrix is:
```
  Java version   | Unsafe available | Cleaner selected
  ───────────────|──────────────────|─────────────────────
  6-8            | Yes              | DirectCleaner
  9-23           | Yes              | DirectCleaner
  24             | Yes (warnings)   | DirectCleaner
  24             | No, native access| CleanerJava24Linker
  25+            | No, native access| CleanerJava24Linker
  25+            | No               | CleanerJava25
```
Before this change, direct memory accounting (`incrementMemoryCounter` /
`decrementMemoryCounter`) was coupled to `USE_DIRECT_BUFFER_NO_CLEANER`,
which was only true when Unsafe was available. This had two
consequences:

1. `DIRECT_MEMORY_COUNTER` was only initialized inside the
`USE_DIRECT_BUFFER_NO_CLEANER=true` branch, so on any Java version
without Unsafe the counter was always null even if the user explicitly
set `io.netty.maxDirectMemory`.

2. The accounting calls themselves lived only in PlatformDependent's
legacy NoCleaner methods (`allocateDirectNoCleaner`,
`reallocateDirectNoCleaner`, `freeDirectNoCleaner`), which were only
called by DirectCleaner. The other Cleaner implementations
(`CleanerJava9`, `CleanerJava6`, `CleanerJava24Linker`, `CleanerJava25`)
never called these methods and performed no accounting.

The combined effect was that the configured limit was silently ignored
on every path that didn't go through DirectCleaner:
```
  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava25       | No           | No  ✗
```
On Java 25+, where Unsafe is disabled by default, this means
`io.netty.maxDirectMemory` has no effect at all.

**Modifications**:

- Decouple `DIRECT_MEMORY_COUNTER` initialization from Unsafe
availability. The counter is now created based solely on the value of
`io.netty.maxDirectMemory`, independent of
`USE_DIRECT_BUFFER_NO_CLEANER`.

- Move accounting into each Cleaner's `CleanableDirectBufferImpl` so
that every allocation/deallocation pair tracks memory regardless of
which Cleaner is active:
- `DirectCleaner`: increment in `CleanableDirectBufferImpl(int
capacity)` constructor, decrement in `clean()`.
  - `CleanerJava9`: increment in constructor, decrement in `clean()`.
  - `CleanerJava6`: increment in constructor, decrement in `clean()`.
- `CleanerJava24Linker`: increment before `malloc()`, decrement in
`clean()`, with rollback on allocation failure.
- `CleanerJava25`: increment in `allocate()` before MethodHandle invoke,
decrement in `clean()` via finally block.

- Change `incrementMemoryCounter`/`decrementMemoryCounter` from private
to package-private so Cleaner implementations (same package) can call
them directly.

- Add a default `reallocate(CleanableDirectBuffer, int)` method to the
Cleaner interface with an allocate-copy-free fallback. DirectCleaner
overrides this with in-place `Unsafe.reallocateMemory`, adjusting the
counter by the delta.

- Add `PlatformDependent.reallocateDirect()` as the unified public entry
point for reallocation.

- Remove the legacy NoCleaner API surface from PlatformDependent:
`allocateDirectNoCleaner`, `allocateDirectBufferNoCleaner`,
`reallocateDirectNoCleaner`, `reallocateDirectBufferNoCleaner`, and
`freeDirectNoCleaner`.

- Remove `USE_DIRECT_BUFFER_NO_CLEANER` and `DIRECT_CLEANER` fields.
`CLEANER` is now the single entry point; `useDirectBufferNoCleaner()`
returns whether `CLEANER` is an instance of DirectCleaner.

- Update `UnpooledUnsafeNoCleanerDirectByteBuf` to use the new unified
API: remove the `allocateDirectBuffer()` override (parent's impl now
does the right thing via `PlatformDependent.allocateDirect()`), and
delegate `reallocateDirect()` to `PlatformDependent.reallocateDirect()`.

- Update `PlatformDependentTest.testAllocateWithCapacity0()` to use the
new CleanableDirectBuffer-based API.

**Result**:

After this change, the accounting matrix becomes:
```
  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava25       | Yes          | Yes ✓
```
`io.netty.maxDirectMemory` is now enforced on all Java versions and all
Cleaner implementations. The legacy raw-ByteBuffer NoCleaner API surface
is eliminated, and each `CleanableDirectBuffer` is responsible for its
own accounting.

---------

Co-authored-by: Chris Vest <mr.chrisvest@gmail.com>

(cherry picked from commit 7937553)
@chrisvest
Copy link
Copy Markdown
Member

The 5.0 port: #16531

@chrisvest chrisvest removed the needs-cherry-pick-5.0 This PR should be cherry-picked to 5.0 once merged. label Mar 23, 2026
normanmaurer pushed a commit that referenced this pull request Mar 24, 2026
) (#16531)

**Motivation**:

Netty selects a Cleaner implementation based on the Java version and
whether `sun.misc.Unsafe` is available. The selection matrix is:
```
  Java version   | Unsafe available | Cleaner selected
  ───────────────|──────────────────|─────────────────────
  6-8            | Yes              | DirectCleaner
  9-23           | Yes              | DirectCleaner
  24             | Yes (warnings)   | DirectCleaner
  24             | No, native access| CleanerJava24Linker
  25+            | No, native access| CleanerJava24Linker
  25+            | No               | CleanerJava25
```
Before this change, direct memory accounting (`incrementMemoryCounter` /
`decrementMemoryCounter`) was coupled to `USE_DIRECT_BUFFER_NO_CLEANER`,
which was only true when Unsafe was available. This had two
consequences:

1. `DIRECT_MEMORY_COUNTER` was only initialized inside the
`USE_DIRECT_BUFFER_NO_CLEANER=true` branch, so on any Java version
without Unsafe the counter was always null even if the user explicitly
set `io.netty.maxDirectMemory`.

2. The accounting calls themselves lived only in PlatformDependent's
legacy NoCleaner methods (`allocateDirectNoCleaner`,
`reallocateDirectNoCleaner`, `freeDirectNoCleaner`), which were only
called by DirectCleaner. The other Cleaner implementations
(`CleanerJava9`, `CleanerJava6`, `CleanerJava24Linker`, `CleanerJava25`)
never called these methods and performed no accounting.

The combined effect was that the configured limit was silently ignored
on every path that didn't go through DirectCleaner:
```
  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava24Linker | No           | No  ✗
  25+            | No     | CleanerJava25       | No           | No  ✗
```
On Java 25+, where Unsafe is disabled by default, this means
`io.netty.maxDirectMemory` has no effect at all.

**Modifications**:

- Decouple `DIRECT_MEMORY_COUNTER` initialization from Unsafe
availability. The counter is now created based solely on the value of
`io.netty.maxDirectMemory`, independent of
`USE_DIRECT_BUFFER_NO_CLEANER`.

- Move accounting into each Cleaner's `CleanableDirectBufferImpl` so
that every allocation/deallocation pair tracks memory regardless of
which Cleaner is active:
- `DirectCleaner`: increment in `CleanableDirectBufferImpl(int
capacity)` constructor, decrement in `clean()`.
  - `CleanerJava9`: increment in constructor, decrement in `clean()`.
  - `CleanerJava6`: increment in constructor, decrement in `clean()`.
- `CleanerJava24Linker`: increment before `malloc()`, decrement in
`clean()`, with rollback on allocation failure.
- `CleanerJava25`: increment in `allocate()` before MethodHandle invoke,
decrement in `clean()` via finally block.

- Change `incrementMemoryCounter`/`decrementMemoryCounter` from private
to package-private so Cleaner implementations (same package) can call
them directly.

- Add a default `reallocate(CleanableDirectBuffer, int)` method to the
Cleaner interface with an allocate-copy-free fallback. DirectCleaner
overrides this with in-place `Unsafe.reallocateMemory`, adjusting the
counter by the delta.

- Add `PlatformDependent.reallocateDirect()` as the unified public entry
point for reallocation.

- Remove the legacy NoCleaner API surface from PlatformDependent:
`allocateDirectNoCleaner`, `allocateDirectBufferNoCleaner`,
`reallocateDirectNoCleaner`, `reallocateDirectBufferNoCleaner`, and
`freeDirectNoCleaner`.

- Remove `USE_DIRECT_BUFFER_NO_CLEANER` and `DIRECT_CLEANER` fields.
`CLEANER` is now the single entry point; `useDirectBufferNoCleaner()`
returns whether `CLEANER` is an instance of DirectCleaner.

- Update `UnpooledUnsafeNoCleanerDirectByteBuf` to use the new unified
API: remove the `allocateDirectBuffer()` override (parent's impl now
does the right thing via `PlatformDependent.allocateDirect()`), and
delegate `reallocateDirect()` to `PlatformDependent.reallocateDirect()`.

- Update `PlatformDependentTest.testAllocateWithCapacity0()` to use the
new CleanableDirectBuffer-based API.

**Result**:

After this change, the accounting matrix becomes:
```
  Java version   | Unsafe | Cleaner             | Counter init | Accounting
  ───────────────|────────|─────────────────────|──────────────|───────────
  6-8            | Yes    | DirectCleaner       | Yes          | Yes ✓
  9-23           | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | Yes    | DirectCleaner       | Yes          | Yes ✓
  24             | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava24Linker | Yes          | Yes ✓
  25+            | No     | CleanerJava25       | Yes          | Yes ✓
```
`io.netty.maxDirectMemory` is now enforced on all Java versions and all
Cleaner implementations. The legacy raw-ByteBuffer NoCleaner API surface
is eliminated, and each `CleanableDirectBuffer` is responsible for its
own accounting.

---------

Co-authored-by: Chris Vest <mr.chrisvest@gmail.com>

(cherry picked from commit 7937553)

Co-authored-by: Jeff Bahr <jbahr@fb.com>
dongjoon-hyun added a commit to apache/spark-kubernetes-operator that referenced this pull request Mar 25, 2026
### What changes were proposed in this pull request?

This PR upgrades `Netty` to 4.2.12.Final.

### Why are the changes needed?

To bring in the latest bug fixes.
- https://netty.io/news/2026/03/24/4-2-12-Final.html
  - netty/netty#16550
- https://netty.io/news/2026/03/24/4-2-11-Final.html
  - netty/netty#16489
  - netty/netty#16536
  - netty/netty#16412

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

CI should pass with the existing tests.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 4.6)

Closes #589 from dongjoon-hyun/SPARK-56213.

Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
dongjoon-hyun added a commit to apache/spark that referenced this pull request Mar 26, 2026
### What changes were proposed in this pull request?

This PR upgrades `Netty` to 4.2.12.Final.

### Why are the changes needed?

To bring in the latest bug fixes.
- https://netty.io/news/2026/03/24/4-2-12-Final.html
  - netty/netty#16550
- https://netty.io/news/2026/03/24/4-2-11-Final.html
  - netty/netty#16489
  - netty/netty#16536
  - netty/netty#16412

### Does this PR introduce _any_ user-facing change?

No.

### How was this patch tested?

CI should pass with the existing tests.

### Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code (Claude Opus 4.6)

Closes #55016 from dongjoon-hyun/SPARK-56214.

Authored-by: Dongjoon Hyun <dongjoon@apache.org>
Signed-off-by: Dongjoon Hyun <dongjoon@apache.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants