Daniel Sun created GROOVY-12223:
-----------------------------------

             Summary: Introduce hidden class support
                 Key: GROOVY-12223
                 URL: https://issues.apache.org/jira/browse/GROOVY-12223
             Project: Groovy
          Issue Type: Improvement
            Reporter: Daniel Sun


h2. Background

Groovy generates many short-lived synthetic classes at runtime, including:

* map/interface proxies ({{ProxyGeneratorAdapter}})
* reflection dispatch helpers ({{Reflector}} / {{ReflectorLoader}})
* per-class meta-method artifacts ({{ClassLoaderForClassArtifacts}})

Today these are defined with {{ClassLoader#defineClass}} as ordinary *named* 
classes. That has three practical downsides:

# *Name pollution* — the synthetic types are discoverable via {{Class.forName}} 
/ {{ClassLoader#loadClass}}.
# *Metaspace pressure* — their lifetime is tied to the defining class loader; 
long-running applications that generate many artifacts retain them until the 
loader itself is collected.
# *Access friction* — without nest membership, generated code cannot share 
private access with the host class the way a true nestmate can.

JDK 15 introduced *hidden classes* ([JEP 371|https://openjdk.org/jeps/371]): 
classes defined through {{Lookup#defineHiddenClass}} that are non-discoverable 
by name, may join an access-control nest ({{NESTMATE}}), and may be unloaded 
independently of the defining loader when not marked {{STRONG}}.

Groovy 6 requires JDK 17+, so the API is always present on supported runtimes.

h2. Proposal

Centralise hidden-class definition behind a single utility and prefer it for 
the dynamic class-generation sites listed above, with a transparent fallback to 
the existing {{ClassLoader#defineClass}} path.

h3. New API

{{org.apache.groovy.util.HiddenClassDefiner}} — the only call-site that invokes 
{{Lookup#defineHiddenClass}}:

* {{defineHiddenClass(lookup, bytes, initialize, nestmate, strong)}} — full 
control
* {{defineNestmateClass(lookup, bytes, initialize)}} — nestmate + weak 
lifecycle (default for proxies / reflectors / artifacts)
* {{defineStrongHiddenClass(lookup, bytes, initialize)}} — non-discoverable, 
loader-tied lifetime
* helpers: {{privateLookupIn(hostClass)}}, {{findConstructor(hiddenClass, 
...parameterTypes)}}

Kill-switch (evaluated once at class-init for hot-path cost):

{noformat}
-Dgroovy.hidden.classes.disable=true
{noformat}

When disabled (or when private lookup / definition fails), callers fall back to 
defining a normal visible class.

h3. Integration points

|| Site || Nest host || Preferred options || Fallback ||
| {{ClassLoaderForClassArtifacts#define}} | target (klazz) | nestmate, weak | 
{{ClassLoader#defineClass}} + protection domain |
| {{ProxyGeneratorAdapter}} | non-{{Object}} superclass if present; else 
{{ProxyGeneratorAdapter}} | nestmate, weak | {{InnerLoader#defineClass}} |
| {{ReflectorLoader#defineClass}} | {{Reflector}} | nestmate, weak | 
{{ClassLoader#defineClass}} + protection domain |

Behaviour for callers of these generators is unchanged: proxies still implement 
the requested interfaces, reflectors still dispatch, artifacts still construct. 
The only observable differences when the hidden path succeeds are the synthetic 
name form (contains {{/}}) and {{Class#isHidden() == true}}.

h2. Benefits

* Non-discoverable synthetic types (cleaner class-space / tooling view).
* Nestmate private access where the nest host can be opened for private lookup.
* Eager unloading of weak hidden classes reduces long-run metaspace retention 
for short-lived proxies and artifacts.
* One policy / upgrade point if future JDKs add further {{Lookup.ClassOption}} 
values.

h2. Compatibility

* Default-on when the JVM can obtain a full-privilege lookup for the chosen 
nest host; silent fallback otherwise (e.g. sealed / unopened module packages).
* Opt-out: {{-Dgroovy.hidden.classes.disable=true}}.
* No public language-surface change; no change to successful proxy / reflector 
/ artifact *behaviour*, only to how the {{Class}} is defined.




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

Reply via email to