[ 
https://issues.apache.org/jira/browse/GROOVY-12281?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18106129#comment-18106129
 ] 

ASF GitHub Bot commented on GROOVY-12281:
-----------------------------------------

paulk-asert opened a new pull request, #2820:
URL: https://github.com/apache/groovy/pull/2820

   ### What this PR is
   
   The GROOVY-12281 investigation branch, opened for review of one shippable 
change plus its
   supporting evidence. **Only `2ab40501e2` ("soft GroovyClassValue mode — soft 
values with
   resurrection (opt-in)") is intended to land on master** — ideally 
cherry-picked or landed
   alone rather than squash-merging the branch. The other commits are 
investigation artifacts
   that should stay on the branch:
   
   - `45e3fc6a56` / `a429dd7415` — the *hybrid* prototype (measured, 
**declined**) and the guide
     update quantifying the existing `=false` escape hatch (the guide change is 
independently
     master-worthy if wanted);
   - `12d59e847b` — the assessment document (v2) and the loader/perf spike 
harnesses:
     
[GROOVY-12281-assessment.html](https://github.com/apache/groovy/blob/groovy12281/GROOVY-12281-assessment.html).
   
   ### Problem
   
   A `java.lang.ClassValue` association lives as long as its key class, so
   `ClassInfo.globalClassValue` entries on immortal platform classes (String, 
Integer, …) hold a
   strong chain to the ClassInfo and through it to Groovy's class loader — 
leaking every Groovy
   copy a container deploys and undeploys (JDK-8136353 / GROOVY-12142). The 
existing remedy,
   `-Dgroovy.use.classvalue=false`, gives up the per-Class fast path for every 
key
   (~4× on the raw lookup, +4.8% geomean on classic dispatch, hot platform 
idioms to 1.38×).
   
   ### Change
   
   `-Dgroovy.use.classvalue=soft` keeps `ClassValue` for all keys but stores 
each value behind a
   bootstrap-loaded `SoftReference`, cutting the only strong chain from 
immortal keys to the
   Groovy island. Three cooperating pieces make that safe (details and file 
references in the
   assessment):
   
   - **Resurrection** — a weak-key/weak-value side map is the identity 
authority: a value still
     reachable anywhere is re-associated, never replaced, so a fresh instance 
can only exist once
     no guard can still observe the old one. Classic call-site version guards 
therefore stay
     sound: legacy-compiled jars using `groovy-callsite` from the classpath are 
unaffected.
   - **Dirty roots** — ClassInfos carrying non-reconstructible state (installed 
MetaClass,
     per-instance MetaClasses, registry-written DGM/extension arrays) are 
strong-rooted
     Groovy-side; `ClassInfo.remove()` unroots, keeping its hard-detach 
undeploy semantics.
   - **Per-Class indy domain continuity** — SwitchPoint domains are keyed by 
`Class` in soft
     mode, so a successor ClassInfo adopts its predecessor's domain and 
mutations
     deterministically retire guards that captured only the SwitchPoint (POJO 
direct dispatch).
     Indy-only; the default mode's behaviour is unchanged.
   
   Default-mode behaviour is byte-identical apart from no-op hook calls; all 
soft-mode structures
   are unallocated unless the flag is set.
   
   ### Evidence
   
   - **Acceptance** (the ticket's point; JDK 17/21/23): a dropped child-loader 
Groovy copy is
     pinned forever under the default and **collected under soft mode** once 
pressure clears
     soft references.
   - **Correctness**: unit tests + a deterministic child-JVM probe 
(resurrection identity/version
     continuity, DGM rooting, EMC/per-instance survival, recreation dispatch, 
classic
     `CallSiteArray` soundness, predecessor-SwitchPoint retirement); a real-GC 
concurrency
     stress probe (128 MB heap, `SoftRefLRUPolicyMSPerMB=0`): 66M dispatches, 
~130 EMC
     generations, 33/36 platform-receiver ClassInfos collected and recreated 
mid-run, zero
     invariant violations; full core suite passes under soft mode (16,800 
tests).
   - **Cost**: raw `getClassInfo` 1.2 → 1.7 ns; compiler harness +0.0%; 
classic-bytecode JMH gate
     0 of 37 significant (geomean +1.7%; the declined map default failed the 
same rule with 12,
     worst 1.38×); classic *polymorphic* miss traffic ≈ +3.7% pooled, flagged 
for the idiom-suite
     sweep before any default-flip discussion.
   
   ### Open questions for review
   
   Assessment §9 consolidates the resulting option landscape 
(`true`/`soft`/`false` ×
   static/dynamic × indy/classic, with guidance); §10 lists the open items: 
adoption path
   (opt-in now — recommended — vs eventual default), whether domain re-homing 
should become
   unconditional rather than soft-gated, and container soak experience. The 
integration guide is
   deliberately untouched until the mode's fate is decided.
   
   JIRA: https://issues.apache.org/jira/browse/GROOVY-12281 (assessment v2 
supersedes v1 in place;
   this prototype responds to the review comments there)
   
   🤖 Generated with [Claude Code](https://claude.com/claude-code)




> Investigate mitigating class-loader pinning by ClassInfo.globalClassValue 
> under the default ClassValue implementation
> ---------------------------------------------------------------------------------------------------------------------
>
>                 Key: GROOVY-12281
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12281
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Paul King
>            Priority: Major
>         Attachments: GROOVY-12281-Assessment.pdf
>
>
> Follow-up from review discussion on [PR 
> #2798|https://github.com/apache/groovy/pull/2798] (GROOVY-12142), preserving 
> the analysis from that review. A {{ClassValue}} realizes the chain _key class 
> → association → value → everything reachable from it_, and the association 
> lives as long as the key class 
> ([JDK-8136353|https://bugs.openjdk.org/browse/JDK-8136353], working as 
> intended). The {{ClassValue}} implementation class itself prevents no 
> unloading; what matters is the key's origin: a Groovy-loaded key class dies 
> with Groovy's loader (fine), but a JDK/platform key class is effectively 
> immortal, so any value reachable from it that was loaded by Groovy's loader 
> pins that loader forever. Indirection counts — a JDK-typed value (e.g. an 
> {{ArrayList}}) whose _elements_ are Groovy-loaded re-creates the pin one 
> level down.
> {{ClassInfo.globalClassValue}} is static with unknown keys, including 
> platform classes ({{String}} receives a {{ClassInfo}} in essentially every 
> Groovy program), so the default {{ClassValue}} path pins the loader; today's 
> only remedy is the global {{groovy.use.classvalue=false}} escape hatch, which 
> trades away the per-class fast path for all keys.
> The general mitigation — {{SoftReference}}-wrapped values with a 
> check-remove-recompute protocol — requires that recomputation be legal, and 
> for {{ClassInfo}} it is not in general: a {{ClassInfo}} can carry 
> non-recomputable state (modified metaclasses, category state), so a 
> softly-collected value could silently discard user metaclass customizations. 
> Approaches to investigate:
> * soft values while a {{ClassInfo}} is pristine, hardening the reference on 
> first mutation — only classes with customized metaclasses would then pin, a 
> far smaller set;
> * per-key policy: platform-loader keys get soft/map treatment, Groovy-loader 
> keys stay strong (the key-origin rule applied mechanically);
> * splitting {{ClassInfo}} into recomputable and stateful parts;
> * revisiting whether the map-based implementation should become the default, 
> with {{ClassValue}} as the opt-in fast path.
> Recompute cost and dispatch-path performance need measurement for any 
> candidate.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to