GROOVY-12142: Stop pinning container class loaders: remove PIC-Cleane… - #2798
Open
paulk-asert wants to merge 1 commit into
Open
GROOVY-12142: Stop pinning container class loaders: remove PIC-Cleane…#2798paulk-asert wants to merge 1 commit into
paulk-asert wants to merge 1 commit into
Conversation
…r and DFA-cache-cleaner threads, restore the ClassValue escape hatch A Groovy copy deployed per webapp (the common Tomcat/WEB-INF/lib topology) permanently pinned its class loader, growing metaspace on every parallel (re)deployment. Three independent pins: 1. PIC-Cleaner: linking any indy call site started a never-terminating daemon thread from CacheableCallSite's static initializer, pinning the defining loader and capturing the creating context's protection domains. Stale PIC entries are now swept inline — both callers already hold the lruCache monitor and the cache is bounded (8), so the sweep is trivial; the groovy.indy.callsite.cleaner.inline flag (GROOVY-12092) is gone along with the thread it toggled, which also removes the parked-thread false positive it existed to avoid. 2. DFA-cache-cleaner: each AtnManager started a never-terminating reference-queue thread whose catch-all swallowed even interrupts. The softly referenced AtnWrapper is a GC canary; its collection is now detected on the parse path when the soft reference reads null, clearing the shared DFA cache at the next parse instead of at GC time — no queue, no thread. 3. java.lang.ClassValue associations on immortal (bootstrap) classes never release their value's class loader (JDK-8136353), so one dynamic dispatch on e.g. String pinned the loader. The groovy.use.classvalue=false escape hatch (present through 4.x, lost in the GROOVY-11520 collections cleanup) is restored, with the fallback rebuilt on ManagedIdentityConcurrentMap (~40 lines) rather than the removed deprecated collections. Default stays ClassValue. With 1+2 fixed, a dropped loader is collectable either with the flag or with a shutdown sweep over ClassInfo.getAllClassInfo() calling ClassInfo.remove — both verified by classloader-churn harness against the rebuilt jar; out-of-the-box default config still requires one of the two (the JVM bug is not ours to fix).
paulk-asert
force-pushed
the
groovy12142
branch
from
August 17, 2026 07:06
8bba0f9 to
7442977
Compare
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #2798 +/- ##
==================================================
+ Coverage 70.1064% 70.1412% +0.0348%
- Complexity 35774 35819 +45
==================================================
Files 1561 1563 +2
Lines 132373 132390 +17
Branches 24332 24353 +21
==================================================
+ Hits 92802 92860 +58
+ Misses 31170 31133 -37
+ Partials 8401 8397 -4
🚀 New features to boost your workflow:
|
✅ All tests passed ✅🏷️ Commit: 7442977 Learn more about TestLens at testlens.app. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…r and DFA-cache-cleaner threads, restore the ClassValue escape hatch
A Groovy copy deployed per webapp (the common Tomcat/WEB-INF/lib topology) permanently pinned its class loader, growing metaspace on every parallel (re)deployment. Three independent pins:
PIC-Cleaner: linking any indy call site started a never-terminating daemon thread from CacheableCallSite's static initializer, pinning the defining loader and capturing the creating context's protection domains. Stale PIC entries are now swept inline — both callers already hold the lruCache monitor and the cache is bounded (8), so the sweep is trivial; the groovy.indy.callsite.cleaner.inline flag (GROOVY-12092) is gone along with the thread it toggled, which also removes the parked-thread false positive it existed to avoid.
DFA-cache-cleaner: each AtnManager started a never-terminating reference-queue thread whose catch-all swallowed even interrupts. The softly referenced AtnWrapper is a GC canary; its collection is now detected on the parse path when the soft reference reads null, clearing the shared DFA cache at the next parse instead of at GC time — no queue, no thread.
java.lang.ClassValue associations on immortal (bootstrap) classes never release their value's class loader (JDK-8136353), so one dynamic dispatch on e.g. String pinned the loader. The groovy.use.classvalue=false escape hatch (present through 4.x, lost in the GROOVY-11520 collections cleanup) is restored, with the fallback rebuilt on ManagedIdentityConcurrentMap (~40 lines) rather than the removed deprecated collections. Default stays ClassValue.
With 1+2 fixed, a dropped loader is collectable either with the flag or with a shutdown sweep over ClassInfo.getAllClassInfo() calling ClassInfo.remove — both verified by classloader-churn harness against the rebuilt jar; out-of-the-box default config still requires one of the two (the JVM bug is not ours to fix).