After undeploying modules GC will not remove classes nor decrease metaspace size.

      Steps to reproduce

      1. Setup Liferay DXP bundle
      2. Deploy the attached hello-world-portlet-
      3. Undeploy the porltet (delete it from osgi/war)
      4. Wait at least 1 minute (see additional information for reason)
      5. Execute a GC through JVisualVM or similar
      6. Create a heapdump through JVisualVM or by using jmap (e.g. jmap -dump:format=b,file=FILENAME.hprof <pid>)
      7. Open created heapdump in Eclipse MAT
      8. Click on the second down arrow in the top bar, then: Java Basics -> Class Loader Explorer (the second down arrow is called: "Open Query Browser")
      9. Find hello-world-portlet, right click on it, then: Class Loader -> Path to GC Roots -> exclude all Phantom/Weak/Soft etc. references
      10. Open the tree until you see "com.liferay.petra.lang.ClassLoaderPool"

      Expected result: The hello-world-portlet shouldn't show up in the Class Loader Explorer
      Actual result: The hello-world-portlet's classloader is being referenced by com.liferay.petra.lang.ClassLoaderPool

      Reproduced on
      Liferay 7.0 de-55
      Reproduced on Master (06dd26d1f7e6d672ddc8551698af56e41d028095)

      Additional Information:

      • The reason why we have to wait at least 1 minute in step 4 is because during that time a "RequiredPluginsUtil" thread is still executing and holding reference to the classloader. After a minute the thread terminates, and as such, is a false positive.
      • The main issue is that when deploying a WAB module, the WAB's classloader gets added with two different contextNames. This results in the unregister methods to fail to completely remove the classLoader from its own reference.

      If we look at the register and unregister methods, we'll see the following logic (see corresponding sources):
      Register: add classloader -> contextName mapping and contextName -> classloader mapping
      Unregister1: Remove classLoader -> contextName mapping, and if it was present, remove contextName -> classLoader mapping
      Unregister2: Remove contextName -> classLoader mapping, and if it was present, remove classLoader -> contextName mapping

      Straightforward, however, with WAB portlets, the register method is called twice, with different contextNames. Once with hello-world-portlet and once with hello-world-portlet_7.0.1 (or something similar), but with the same Classloader.

      This is a problem, because during register, the following happens:
      Register 1:

      1. Add "hello-world-portlet" -> classloader
      2. Add classLoader -> "hello-world-portlet"

      Register 2:

      1. Add "hello-world-portlet_7.0.1" -> classloader
      2. Update classloader -> "hello-world-portlet_7.0.1"

      Just to clarify: at the end, _classLoaders will contain two entries for the WAB, whereas _contextNames will only contain one. At this point, during unregister, "hello-world-portlet_7.0.1" will be removed from both, however, "hello-world-portlet" mapping will remain in there.

      The root cause of this is due to different ways of getting the contextName when registering the plugin.

      The first execution is coming from ClassLoaderTracker.initBundle (so technically bundle deploy time), where the contextName is decided via the private _toClassLoaderName method.

      The second execution comes from WebBundleDeployer.doStart -> _initWabBundle -> ... -> PluginContextListener.contextInitialized, where the contextName is decided by the standard javax.servlet.ServletContext.getServletContextName() method call.

      To debug the issue, simply put a breakpoint in the register and unregister methods within ClassLoaderPool, and check the contents of the static _classLoaders and _contextNames maps.


        Issue Links



     Raven Song
              peter.petrekanics Peter Petrekanics (Inactive)
              Kiyoshi Lee Kiyoshi Lee
              0 Vote for this issue
              3 Start watching this issue


                3 years, 44 weeks, 5 days ago


                  Version Package
                  7.0.0 DXP FP68
                  7.1.10 DXP FP5
                  7.1.2 CE GA3
                  7.3.10 DXP FP1
         DXP SP1