You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Daniel Sun (JIRA)" <ji...@apache.org> on 2018/03/21 09:55:00 UTC

[jira] [Updated] (GROOVY-6655) GroovyClassLoader parallelLockMap memory leak on Java 7

     [ https://issues.apache.org/jira/browse/GROOVY-6655?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]

Daniel Sun updated GROOVY-6655:
-------------------------------
    Priority: Critical  (was: Major)

> GroovyClassLoader parallelLockMap memory leak on Java 7
> -------------------------------------------------------
>
>                 Key: GROOVY-6655
>                 URL: https://issues.apache.org/jira/browse/GROOVY-6655
>             Project: Groovy
>          Issue Type: Bug
>          Components: groovy-runtime
>    Affects Versions: 2.2.2
>         Environment: Java 7
>            Reporter: Henri Pihkala
>            Priority: Critical
>              Labels: classloader, java, leak, memory, metaprogramming
>
> Java 7 introduced parallel classloading. There is a map in {{java.lang.ClassLoader}} called {{parallelLockMap}}. This map holds String keys for class names and the associated Objects used to synchronize on for each class.
> Now, I found that dynamically parsing (perhaps also loading?) and unloading classes with {{GroovyClassLoader}} leaves obsolete keys in this map. Any time {{java.lang.ClassLoader#loadClass(..)}} is called, an entry is created for that class name if it does not exist. 
> In the default Java delegating classloader paradigm the delegate classloader is asked for a class first, so a call to the root loader's {{java.lang.ClassLoader#loadClass(..)}} is made for a whole lot of classes, including Groovy metaclasses, polluting the map with keys.
> !https://dl.dropboxusercontent.com/u/4655232/parallelLockMapMemoryLeak.png!
> The entries that remain in the map even after the class and associated metaclasses themselves have been unloaded have the following two types of keys:
> {{"groovy.runtime.metaclass.MyDynamicallyLoadedClassNameMetaClass"}}
> {{"MyDynamicallyLoadedClassNameBeanInfo"}}
> A key for the class name itself, ie. {{MyDynamicallyLoadedClassName}}, does not appear in the map. I am assuming it is never inserted there, but instead defined by {{GroovyClassLoader}} without asking the parent first. But these metaclass keys *do* leak into the map, eventually exhausting heap memory even if the classes themselves get nicely unloaded (from PermGen) when the {{GroovyClassLoader}} is GC'ed.
> Here's a very simple test script that will eventually run out of memory (it does take some time):
> {code:title=MemoryLeakTest.groovy}
>     String newClass = "class CLASSNAME {}"
>     while (true) {
>         GroovyClassLoader gcl = new GroovyClassLoader()
>         Class clazz = gcl.parseClass(newClass.replace("CLASSNAME", "NewClass"+System.nanoTime()))
>         clazz.newInstance()
>     }
> {code}
> Be sure to run JVM with
> {noformat}
> -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled
> {noformat}
> to make sure you run out of *heap* memory and not *PermGen* space.
> One fix for this would be to add a check to {{GroovyClassLoader}} that would prevent it from delegating to the parent first if the class to be loaded is a metaclass of a class loaded by that classloader. I am definitely no expert in this so you might come up with a much better idea.
> This has been tested on Groovy 2.0.5 and 2.2.2 and I expect that it affects a lot of versions running on Java 7.
> Sure, it doesn't leak memory at a very fast pace, only a {{ConcurrentHashMap}} entry for each class loaded and unloaded. I can see how in many applications this is not a major problem, but please consider for example a server that automatically checks students' programming assignments or such. Please do change the issue priority if appropriate.
> !https://dl.dropboxusercontent.com/u/4655232/parallelLockMapMemoryLeak2.png!



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)