[
https://issues.apache.org/jira/browse/GROOVY-12142?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18105391#comment-18105391
]
ASF GitHub Bot commented on GROOVY-12142:
-----------------------------------------
blackdrag commented on code in PR #2798:
URL: https://github.com/apache/groovy/pull/2798#discussion_r3799723985
##########
src/spec/doc/guide-integrating.adoc:
##########
@@ -378,6 +378,43 @@ stub generation is done, for the joint compiler.
However, overriding `CompilationUnit` is not recommended and should only be
done if no other standard solution works.
+== Class loader release in managed environments
+
+Applications that load and discard Groovy repeatedly — a web container
performing (parallel)
+redeployments with Groovy inside each web application, plugin systems, or any
host that expects
+class loaders to be garbage collected — need one extra consideration.
+
+Groovy associates runtime metadata (`ClassInfo`) with every class it
dispatches on, including
+JDK classes such as `String`. By default those associations use
`java.lang.ClassValue`, which
+gives the fastest possible lookup, but a long-standing JVM issue
+(https://bugs.openjdk.org/browse/JDK-8136353[JDK-8136353]) prevents such
associations on
+long-lived classes from ever releasing their value — and with it the Groovy
class loader the
+value belongs to. In a container this shows up as metaspace growth on every
redeployment, even
+after the old application is undeployed.
+
Review Comment:
This is fine for the integration guide, so I do not want to block this.
But for us developers I think that is a bit too short for the complexity of
ClassValue. First of all the bug you mentioned has been closed. There are
follow-ups, but they are documentation issues, not something to be fixed. in
that manner it works as designed.
Think of it like this. With ClassValue we basically realize a chain: `key
class - class association -> class value instance -> value`
Important to note here is that the class of the ClassValue implementation
itself does not prevent any unloading. It is different from a static field in
the key class for example. Now if we add the value, then there is a strong
association between the key class and the value. If the value is for example an
empty ArrayList, then it will not prevent the unloading of the classloader for
the ClassValue. But if in the Tomcat scenario with Groovy the value class is
from Groovy, then the loader for Groovy will be prevented from unloading as
long as the key class exists. If the key class is an immortal class like
String, then even unloading Tomcat and having had Groovy loaded in an isolated
class loader, would still keep that class loader around.
The issue mentioned at openjdk.org is actually more complicated than this.
Assume we store an ArrayList again, but the element is any instance of a class
of that Groovy defining class loader. Then we avoided the class problem for the
value, but again got it through one level of indirection. And the real problem
in this scenario is that this can be a long chain. The sort formula for Groovy
is then, key = Groovy class is fine. key == JDK class, bad. The issue itself
was actually for the stored value referencing the ClassValue instance. I put
emphasis on the class loader here, because that is actually the case we care
about.
There is a strategy against that: use a SoftReference for the value of
ClassValue. Since the chain is now always broken there would be no issue
anymore. Of course then the usage of the ClassValue must fulfill some
constraints. Only getting the value no longer works, you have to check the
reference and if it did not retain the class value, remove the association and
recompute it. That means it must be legal to recompute the value in the first
place and the recomputing cost is to be considered.
Based on that I see some potentially problematic parts in Groovy:
* ClassInfo.globalClassValue -> static + unknown keys -> problem
* AwaitableAdapterRegistry.awaitableCache -> static + unknown keys -> problem
* Closure.CALL_OVERRIDES -> should be ok
I think we should investigate the two problems above further. So maybe to
not loose this kind of context we create a followup issue?
> Metaspace memory leak with parallel deployments on Tomcat
> ---------------------------------------------------------
>
> Key: GROOVY-12142
> URL: https://issues.apache.org/jira/browse/GROOVY-12142
> Project: Groovy
> Issue Type: Bug
> Affects Versions: 4.0.24, 4.0.29, 5.0.8, 5.1.0, 6.0.0-beta-2
> Environment: Tomcat: 10.1.55
> openjdk version "17.0.19" 2026-04-21 LTS
> OpenJDK Runtime Environment (Red_Hat-17.0.19.0.10-1) (build 17.0.19+10-LTS)
> OpenJDK 64-Bit Server VM (Red_Hat-17.0.19.0.10-1) (build 17.0.19+10-LTS,
> mixed mode, sharing)
> Reporter: Jan Gosmann
> Assignee: Paul King
> Priority: Major
> Attachments: Screenshot 2026-07-09 at 09.46.46.png, spring-test.tar.gz
>
>
> We are using [Tomcat parallel
> deployments|https://tomcat.apache.org/tomcat-10.0-doc/config/context.html#Parallel_deployment]
> to achieve downtimeless deployments. However, we with each new deployment
> the metaspace grows (see attached image). Even after undeploying the old
> version it does not shrink. If we configure a maximum metaspace size, new
> parallel deployments fail with an out-of-memory error.
> I was able to reproduce this with a very simple Spring Boot application
> (attached). As long as I had no Groovy in there, metaspace was reclaimed
> before hitting the maximum on new deploys. After introducing Groovy,
> metaspace is no longer reclaimed.
--
This message was sent by Atlassian Jira
(v8.20.10#820010)