Skip to content

Prevent classloader memory leak in ScratchBytes/ScratchChars#804

Merged
garydgregory merged 1 commit intomasterfrom
fix/scratch-bytes
Oct 19, 2025
Merged

Prevent classloader memory leak in ScratchBytes/ScratchChars#804
garydgregory merged 1 commit intomasterfrom
fix/scratch-bytes

Conversation

@ppkarwasz
Copy link
Contributor

Commit 0698bd9 introduced convenient AutoCloseable usage for ScratchBytes and ScratchChars. However, it also introduced a classloader memory leak risk in application server environments by storing custom wrapper instances directly in a ThreadLocal.

This PR keeps the ergonomic AutoCloseable pattern while eliminating the classloader leak risk:

  • Store only primitive buffers (byte[] / char[]) in the ThreadLocal, not custom classes.

  • Introduce two types of ScratchBytes / ScratchChars instances:

    • Global instance (buffer == null) that fetches its buffer from the ThreadLocal.
    • Reentrant instances (buffer != null) for nested usage without interfering with shared buffers.

Note: While this revision keeps the readability of using the AutoCloseable API, it also introduces a performance regression compared to the original #801 design: retrieving a buffer now requires two ThreadLocal lookups: once in get() and once in array(). The original design avoided this overhead intentionally. Since these classes are package-private and used in performance-sensitive paths, we should carefully weigh the trade-off between API convenience and runtime cost.

Commit 0698bd9 introduced convenient `AutoCloseable` usage for `ScratchBytes` and `ScratchChars`. However, it also introduced a **classloader memory leak risk** in application server environments by storing custom wrapper instances directly in a `ThreadLocal`.

This PR keeps the ergonomic `AutoCloseable` pattern while eliminating the classloader leak risk:

* Store **only primitive buffers** (`byte[]` / `char[]`) in the `ThreadLocal`, not custom classes.
* Introduce two types of `ScratchBytes` / `ScratchChars` instances:

  * **Global instance** (`buffer == null`) that fetches its buffer from the `ThreadLocal`.
  * **Reentrant instances** (`buffer != null`) for nested usage without interfering with shared buffers.

**Note:** While this revision keeps the readability of using the `AutoCloseable` API, it also introduces a performance regression compared to the original #801 design: retrieving a buffer now requires two `ThreadLocal` lookups: once in `get()` and once in `array()`. The original design avoided this overhead intentionally. Since these classes are package-private and used in performance-sensitive paths, we should carefully weigh the trade-off between API convenience and runtime cost.
@apache apache deleted a comment from Tonyl1980 Oct 18, 2025
@garydgregory garydgregory merged commit 2707d1f into master Oct 19, 2025
21 checks passed
@garydgregory
Copy link
Member

Hi @ppkarwasz
Good catch. Merged. Too bad there's no way to unit test this rentention use case. This likely warrants a review of other Commons components for thread local usage.

@ppkarwasz ppkarwasz deleted the fix/scratch-bytes branch October 19, 2025 07:15
@apache apache deleted a comment from Tonyl1980 Oct 19, 2025
@apache apache deleted a comment from Tonyl1980 Oct 19, 2025
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.

2 participants