Uploaded image for project: 'Jenkins'
  1. Jenkins
  2. JENKINS-75879

GStringTemplateEngine causes memory leak in ClassLoading

XMLWordPrintable

      When using GStringTemplateEngine in a Pipeline Shared Library, each usage leaks a groovy.tmp.templates.GStringTemplateScript* class in every PluginClassLoader.

      This heap memory leak may be significant depending on the scale at which GStringTemplateEngine is being used.

      Stacktrace during the loading looks like the following:

      "Running CpsFlowExecution[testPipeline2#11]" id=363 state=RUNNABLE
          at java.base@17.0.8/java.lang.Throwable.fillInStackTrace(Native Method)
          at java.base@17.0.8/java.lang.Throwable.fillInStackTrace(Throwable.java:798)
          at java.base@17.0.8/java.lang.Throwable.<init>(Throwable.java:293)
          at java.base@17.0.8/java.lang.Exception.<init>(Exception.java:85)
          at java.base@17.0.8/java.lang.ReflectiveOperationException.<init>(ReflectiveOperationException.java:76)
          at java.base@17.0.8/java.lang.ClassNotFoundException.<init>(ClassNotFoundException.java:71)
          at java.base@17.0.8/java.net.URLClassLoader.findClass(URLClassLoader.java:445)
          at jenkins.util.URLClassLoader2.findClass(URLClassLoader2.java:64)
          at jenkins.ClassLoaderReflectionToolkit.loadClass(ClassLoaderReflectionToolkit.java:148)
            - locked java.lang.Object@5ad2e734
          at hudson.PluginManager$UberClassLoader.computeValue(PluginManager.java:2422)
          at hudson.PluginManager$UberClassLoader$$Lambda$420/0x0000000131471da0.apply(Unknown Source)
          at java.base@17.0.8/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1708)
            - locked java.util.concurrent.ConcurrentHashMap$ReservationNode@448fe56f
          at hudson.PluginManager$UberClassLoader.findClass(PluginManager.java:2415)
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:592)
            - locked java.lang.Object@3e8eed00
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:525)
          at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.lambda$loadClass$0(SandboxResolvingClassLoader.java:108)
          at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader$$Lambda$1031/0x0000000131d5dc50.get(Unknown Source)
          at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.lambda$load$2(SandboxResolvingClassLoader.java:144)
          at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader$$Lambda$1035/0x0000000131d5de78.apply(Unknown Source)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.LocalCache.lambda$statsAware$2(LocalCache.java:167)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.LocalCache$$Lambda$1032/0x0000000131d2dcf8.apply(Unknown Source)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.BoundedLocalCache.lambda$doComputeIfAbsent$14(BoundedLocalCache.java:2704)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.BoundedLocalCache$$Lambda$985/0x0000000131d2ca48.apply(Unknown Source)
          at java.base@17.0.8/java.util.concurrent.ConcurrentHashMap.compute(ConcurrentHashMap.java:1916)
            - locked java.util.concurrent.ConcurrentHashMap$ReservationNode@2b9cacb6
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.BoundedLocalCache.doComputeIfAbsent(BoundedLocalCache.java:2702)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.BoundedLocalCache.computeIfAbsent(BoundedLocalCache.java:2684)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.LocalCache.computeIfAbsent(LocalCache.java:112)
          at PluginClassLoader for caffeine-api//com.github.benmanes.caffeine.cache.LocalManualCache.get(LocalManualCache.java:63)
          at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.load(SandboxResolvingClassLoader.java:138)
          at PluginClassLoader for script-security//org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader.loadClass(SandboxResolvingClassLoader.java:106)
            - locked org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.SandboxResolvingClassLoader@20863865
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked org.jenkinsci.plugins.workflow.cps.GroovySourceFileAllowlist$ClassLoaderImpl@5e88245d
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$TimingLoader@42ff36a9
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$TimingLoader.loadClass(CpsGroovyShell.java:216)
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$CleanGroovyClassLoader@59ce95cc
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:702)
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:812)
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$TimingLoader@467be864
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$TimingLoader.loadClass(CpsGroovyShell.java:216)
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked org.jenkinsci.plugins.workflow.cps.CpsGroovyShell$CleanGroovyClassLoader@63c3957e
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:702)
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:812)
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked groovy.lang.GroovyClassLoader@1c97f649
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:702)
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:812)
          at java.base@17.0.8/java.lang.ClassLoader.loadClass(ClassLoader.java:579)
            - locked groovy.lang.GroovyClassLoader@59951061
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:702)
          at groovy.lang.GroovyClassLoader$InnerLoader.loadClass(GroovyClassLoader.java:450)
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:812)
          at groovy.lang.GroovyClassLoader.loadClass(GroovyClassLoader.java:800)
          at java.base@17.0.8/java.lang.Class.forName0(Native Method)
          at java.base@17.0.8/java.lang.Class.forName(Class.java:467)
          at java.desktop@17.0.8/com.sun.beans.finder.ClassFinder.findClass(ClassFinder.java:103)
          at java.desktop@17.0.8/java.beans.Introspector.findCustomizerClass(Introspector.java:1125)
          at java.desktop@17.0.8/java.beans.Introspector.getTargetBeanDescriptor(Introspector.java:1119)
          at java.desktop@17.0.8/java.beans.Introspector.getBeanInfo(Introspector.java:445)
          at java.desktop@17.0.8/java.beans.Introspector.getBeanInfo(Introspector.java:195)
          at groovy.lang.MetaClassImpl$15.run(MetaClassImpl.java:3328)
          at java.base@17.0.8/java.security.AccessController.executePrivileged(AccessController.java:807)
          at java.base@17.0.8/java.security.AccessController.doPrivileged(AccessController.java:569)
          at groovy.lang.MetaClassImpl.addProperties(MetaClassImpl.java:3326)
          at groovy.lang.MetaClassImpl.initialize(MetaClassImpl.java:3303)
            - locked groovy.lang.MetaClassImpl@663a15b6
          at org.codehaus.groovy.reflection.ClassInfo.getMetaClassUnderLock(ClassInfo.java:289)
          at org.codehaus.groovy.reflection.ClassInfo.getMetaClass(ClassInfo.java:331)
          at org.codehaus.groovy.runtime.metaclass.MetaClassRegistryImpl.getMetaClass(MetaClassRegistryImpl.java:277)
          at org.codehaus.groovy.runtime.InvokerHelper.getMetaClass(InvokerHelper.java:905)
          at groovy.lang.GroovyObjectSupport.getDefaultMetaClass(GroovyObjectSupport.java:61)
          at groovy.lang.GroovyObjectSupport.<init>(GroovyObjectSupport.java:34)
          at groovy.lang.Script.<init>(Script.java:42)
          at groovy.lang.Script.<init>(Script.java:39)
          at groovy.tmp.templates.GStringTemplateScript2403.<init>(GStringTemplateScript2403.groovy)
          at java.base@17.0.8/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
          at java.base@17.0.8/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
          at java.base@17.0.8/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
          at java.base@17.0.8/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
          at java.base@17.0.8/java.lang.reflect.ReflectAccess.newInstance(ReflectAccess.java:128)
          at java.base@17.0.8/jdk.internal.reflect.ReflectionFactory.newInstance(ReflectionFactory.java:347)
          at java.base@17.0.8/java.lang.Class.newInstance(Class.java:645)
          at groovy.text.GStringTemplateEngine$GStringTemplate.<init>(GStringTemplateEngine.java:207)
          at groovy.text.GStringTemplateEngine.createTemplate(GStringTemplateEngine.java:115)
          at groovy.text.TemplateEngine.createTemplate(TemplateEngine.java:41)
          at jdk.internal.reflect.GeneratedMethodAccessor172.invoke(Unknown Source)
          at java.base@17.0.8/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568)
          at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
          at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
          at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1225)
          at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
          at org.codehaus.groovy.runtime.callsite.PojoMetaClassSite.call(PojoMetaClassSite.java:46)
          at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
          at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:116)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.sandbox.DefaultInvoker.methodCall(DefaultInvoker.java:20)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.LoggingInvoker.methodCall(LoggingInvoker.java:118)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.ContinuationGroup.methodCall(ContinuationGroup.java:90)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.dispatchOrArg(FunctionCallBlock.java:114)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.FunctionCallBlock$ContinuationImpl.fixArg(FunctionCallBlock.java:83)
          at jdk.internal.reflect.GeneratedMethodAccessor163.invoke(Unknown Source)
          at java.base@17.0.8/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
          at java.base@17.0.8/java.lang.reflect.Method.invoke(Method.java:568)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.ContinuationPtr$ContinuationImpl.receive(ContinuationPtr.java:72)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.LocalVariableBlock$LocalVariable.get(LocalVariableBlock.java:39)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.LValueBlock$GetAdapter.receive(LValueBlock.java:30)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.impl.LocalVariableBlock.evalLValue(LocalVariableBlock.java:28)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.LValueBlock$BlockImpl.eval(LValueBlock.java:55)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.LValueBlock.eval(LValueBlock.java:16)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.Next.step(Next.java:83)
          at PluginClassLoader for workflow-cps//com.cloudbees.groovy.cps.Continuable.run0(Continuable.java:147)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.SandboxContinuable.access$001(SandboxContinuable.java:17)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.SandboxContinuable.run0(SandboxContinuable.java:49)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThread.runNextChunk(CpsThread.java:180)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThreadGroup.run(CpsThreadGroup.java:419)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:327)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsThreadGroup$2.call(CpsThreadGroup.java:292)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.lambda$wrap$4(CpsVmExecutorService.java:140)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$$Lambda$1105/0x0000000131de64c8.call(Unknown Source)
          at java.base@17.0.8/java.util.concurrent.FutureTask.run(FutureTask.java:264)
          at hudson.remoting.SingleLaneExecutorService$1.run(SingleLaneExecutorService.java:139)
          at jenkins.util.ContextResettingExecutorService$1.run(ContextResettingExecutorService.java:28)
          at jenkins.security.ImpersonatingExecutorService$1.run(ImpersonatingExecutorService.java:68)
          at jenkins.util.ErrorLoggingExecutorService.lambda$wrap$0(ErrorLoggingExecutorService.java:51)
          at jenkins.util.ErrorLoggingExecutorService$$Lambda$1102/0x0000000131dc3708.run(Unknown Source)
          at java.base@17.0.8/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
          at java.base@17.0.8/java.util.concurrent.FutureTask.run(FutureTask.java:264)
          at java.base@17.0.8/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
          at java.base@17.0.8/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$1.call(CpsVmExecutorService.java:53)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$1.call(CpsVmExecutorService.java:50)
          at org.codehaus.groovy.runtime.GroovyCategorySupport$ThreadCategoryInfo.use(GroovyCategorySupport.java:136)
          at org.codehaus.groovy.runtime.GroovyCategorySupport.use(GroovyCategorySupport.java:275)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService.lambda$categoryThreadFactory$0(CpsVmExecutorService.java:50)
          at PluginClassLoader for workflow-cps//org.jenkinsci.plugins.workflow.cps.CpsVmExecutorService$$Lambda$1103/0x0000000131de4228.run(Unknown Source)
          at java.base@17.0.8/java.lang.Thread.run(Thread.java:833)
      
          Locked synchronizers: count = 1
            - java.util.concurrent.ThreadPoolExecutor$Worker@72de4195
      

      How To Reproduce

      • Create a Shared Library with a variable like the following:
      # vars/gstringTemplate.groovy
      def call(templateStr, binding) {
          def engine = new groovy.text.GStringTemplateEngine()
          def template = engine.createTemplate(templateStr).make(binding)
          return template.toString()
      }
      
      • Create a Pipeline with the following script:
      @Library("testLib@main") _
      def defaultTemplateStr = 'Hello ${name}!'
      node {
          for(int i=0; i<10000; i++) {
              println gstringTemplate(defaultTemplateStr, ["name": "Leak" + i])
          }
      }
      
      • Build the pipeline
      • Generate a Heapdump and observe the number of objects in the parallelLockMap of the URLClassLoader2 objects (each being the PluginClassLoader of a particular plugin. They contain the 10000 references to the groovy.tmp.templates.GStringTemplateScript* objects generate by the engine.

      References

            jgarciacloudbees Javier GarcĂ­a
            allan_burdajewicz Allan BURDAJEWICZ
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated:
              Resolved: