Skip to content

Stackchunk reuse old gen - #228

Draft
franz1981 wants to merge 2 commits into
openjdk:fibersfrom
franz1981:stackchunk-reuse-old-gen
Draft

Stackchunk reuse old gen#228
franz1981 wants to merge 2 commits into
openjdk:fibersfrom
franz1981:stackchunk-reuse-old-gen

Conversation

@franz1981

@franz1981 franz1981 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

This is an experimental patch, used to write https://quarkus.io/blog/to-cache-or-not-to-cache-virtual-threads/

The purpose of the patch is to enable stack chunk reuse for collectors which have not concurrent phases and are usually suitable for "small heaps".
The patch doesn't try to be smart, as it doesn't have any heuristic to decide if is worthy to reuse only C2 compiled stack frames nor any average mean exponential decay (or similar) algorithm to decide IF is better to stick with a specific stack chunk capacity, and allow detachment to happen.
Which means that, similarly to heuristics built for native allocators, which have the same exact pooling problem vs unknown user-driven lifecycle usage, maybe there's a way to make it right.

I'm not (at all!) a GC expert, but I hope the article help to clarify what's the intent 🙏

That said, pooling (as caching), is one (if not THE) most complex CS problem - since none knows the future - and specifically for FJP, it introduces a "stealthy" scheduling advantage to pooled VTs which I haven't (on purpose) mentioned in the article, as unparking a VT from a carrier can enable local (with signaling) submission, which was the primary reason I was playing with fire trying to understand and dissect scientifically the pros/cons of the known rule "never pool virtual threads".


@theRealAph @tstuefe this is the patch I've mentioned. If it's ugly, it's all my fault :P


Progress

  • Change must not contain extraneous whitespace

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/loom.git pull/228/head:pull/228
$ git checkout pull/228

Update a local copy of the PR:
$ git checkout pull/228
$ git pull https://git.openjdk.org/loom.git pull/228/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 228

View PR using the GUI difftool:
$ git pr show -t 228

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/loom/pull/228.diff

@bridgekeeper

bridgekeeper Bot commented Jul 24, 2026

Copy link
Copy Markdown

👋 Welcome back franz1981! A progress list of the required criteria for merging this PR into fibers will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk

openjdk Bot commented Jul 24, 2026

Copy link
Copy Markdown

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@franz1981

franz1981 commented Jul 26, 2026

Copy link
Copy Markdown
Contributor Author

@fisk hi! Let me know wdyt of this change 🙏
I am not a GC expert but, I have the feeling that if this change I made is made smarter in what level of pooling of stackchunks allows, it could be super beneficial for a more idiomatic pattern (users which not pool VT) for small containers which run with parallel and serial and would see, due to the scarse capacity of heap, aging of stackchunks despite no VT pooling, paying the old gen bloating that can lead to more frequent STW old gen pauses

@fisk

fisk commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

@fisk hi! Let me know wdyt of this change 🙏 I am not a GC expert but, I have the feeling that if this change I made is made smarter in what level of pooling of stackchunks allows, it could be super beneficial for a more idiomatic pattern (users which not pool VT) for small containers which run with parallel and serial and would see, due to the scarse capacity of heap, aging of stackchunks despite no VT pooling, paying the old gen bloating that can lead to more frequent STW old gen pauses

Glancing over this, my main question isn't really if we can make stack chunks further reusable for Serial/Parallel (of course we can), but rather whether we should do that or not. We had more of this kind of opportunistic GC-specific shenanigans earlier on, including allowing G1 to do some more reuse in situations when it's safe. The risk is that you end up without a clear model for when stack chunks are reused and end up with different models for different GCs. So we sort of walked away from that in favour of simplifying the code.

It's also worth mentioning that now G1 will be the default, even in small environments (cf. https://openjdk.org/jeps/523). That means we have an optimization for users that explicitly select Serial/Parallel in constrained environments. And adds back different modes to an area that is already rather complicated, which we have previously walked away from.

On a tangentially related note, we are currently playing around with adding a form of reference counting to the old generation of ZGC and should be able to eagerly reclaim old stack chunks easily without the churn typically induced by major collections. Sounds like it would help with the problem you described. Just saying!

@franz1981

franz1981 commented Jul 27, 2026

Copy link
Copy Markdown
Contributor Author

Hi @fisk, thanks a lot for taking the time to review this and for the deep context 🙏.

Answering with quotes:

“but rather whether we should do that or not”

As a JDK maintainer (which I'm not, but I can certainly imagine...), I agree that baking GC-specific paths into the freeze/thaw core is not ideal. The parallel/serial focus was a limit on my side—specifically my GC knowledge—to make this patch work safely, not a declaration that these are the only collectors that need it. In fact, if you read the blog post, you'll see that my first attempt failed spectacularly 😅 (I hope you enjoy the read!).

“now G1 will be the default, even in small environments”

I actually had this same conversation with Alan this morning, so I am fully aware—and honestly a bit scared. The problem isn't just small heaps, it's small CPU time (real CPU time, which is not the same as shared CPU quotas). The stark backpressure of STW pauses from Serial/Parallel is dreadful for tail latencies, yet it is remarkably effective at keeping memory tight and predictable on these broadly deployed, resource-constrained environments.

“Sounds like it would help with the problem you described”

The ZGC reference counting news is extremely exciting! But the cost/benefit analysis here isn't just about allocation. Pooling/reusing chunks offers memory warmth: it reduces zeroing costs (where applicable), reduces cache misses, and limits the card-marking overhead of constantly replacing old chunks with new ones.

To summarize my point:

  1. Please don't over-index on my choice of Parallel/Serial in the patch.
  2. In CPU-squeezed microservices, minimising background work (like concurrent GC scanning) is critical to avoid OS-level CPU throttling
  3. We'd love to keep memory warm where we can. Reusing old-gen chunks avoids the cycle of allocating, zeroing, and promoting fresh chunks every few milliseconds, and eliminates the dirty card scanning overhead from dead chunks littering old gen during young GC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants