Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8146961

Fix PermGen memory leaks caused by static final Exceptions

    Details

    • Subcomponent:
    • Resolved In Build:
      b133
    • CPU:
      generic
    • OS:
      windows_8

      Backports

        Description

        FULL PRODUCT VERSION :
        java version "1.8.0_60"
        Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
        Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

        ADDITIONAL OS VERSION INFORMATION :
        Microsoft Windows [Version 10.0.10240]
        (This issue relates to code in Java classes in rt.jar, so I don't believe the OS or OS version is important here. Still, that's the version I'm using right now.)

        A DESCRIPTION OF THE PROBLEM :
        The classes com.sun.org.apache.xerces.internal.dom.DOMNormalizer and com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl, both within rt.jar, each contain a static final field of type RuntimeException named 'abort'.

        When each of these two classes is statically initialized, its 'abort' exception is created, and its stacktrace is filled in. If a class in a web application is unfortunate enough to be in the call stack when this happens, that class can no longer be garbage collected as the exception's stack trace now contains a reference to this class. Consequently, this class, and all of the other classes loaded by the web app's classloader, cannot be removed from PermGen space within the JVM. This causes a memory leak.

        I would suggest the following fix:
        (1): Create a custom subclass of RuntimeException, which overrides fillInStackTrace to just return this. Use an instance of this subclass instead of a RuntimeException for DOMNormalizer.abort. (This appears to be the approach taken for two other static final fields which also contain exceptions: com.sun.org.apache.xerces.internal.impl.XMLEntityScanner.END_OF_DOCUMENT_ENTITY and com.sun.org.apache.xerces.internal.parsers.AbstractDOMParser$Abort.INSTANCE. Neither of these exceptions causes the same problem.)
        (2): Delete the field com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl.abort, as it appears to be unused.


        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        This assumes you have an Apache Tomcat web server, as it makes use of Tomcat's memory-leak diagnostics:

        1. Create a WAR file with the servlet class and web.xml detailed below.
        2. Deploy the WAR file to a Tomcat application server.
        3. View the index.html page generated by the servlet. (This should display the text 'done' in the browser window.)
        4. Use the Tomcat manager to reload the web application.
        5. Click 'Find leaks' in the Diagnostics section of the Tomcat manager.
        6. Use JVisualVM (or another profiler) to look in the Tomcat application for instances of org.apache.catalina.loader.WebappClassLoader with a 'state' of 'DESTROYED'.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        At step 5, Tomcat reports no memory leaks. At step 6, there are no destroyed Tomcat classloader instances, i.e. the classloader used for the application that was reloaded has now been garbage collected.
        ACTUAL -
        At step 5, Tomcat reports a memory leak, naming the web application created. At step 6, JVisualVM finds one destroyed Tomcat classloader, which is being kept alive because of one of the two 'abort' exceptions described above.

        ERROR MESSAGES/STACK TRACES THAT OCCUR :
        Here's the path from the destroyed webapp classloader to one of the 'abort' exceptions, obtained using JVisualVM:

        this - value: org.apache.catalina.loader.WebappClassLoader #3
         <- <classLoader> - class: com.example.DOMNormalizerLeakServlet, value: org.apache.catalina.loader.WebappClassLoader #3
          <- [2] - class: java.lang.Object[], value: com.example.DOMNormalizerLeakServlet class DOMNormalizerLeakServlet
           <- [2] - class: java.lang.Object[], value: java.lang.Object[] #4319
            <- backtrace - class: java.lang.RuntimeException, value: java.lang.Object[] #4318
             <- abort (sticky class) - class: com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl, value: java.lang.RuntimeException #1

        (The class is named DOMNormalizerLeakServlet as I initially noticed this issue with DOMNormalizer in a real app. The test case code happens to trigger the situation with DOMSerializerImpl instead. It may be possible to adjust the test case code to trigger it with DOMNormalizer.abort instead, e.g. by normalizing the document created before serializing.)

        REPRODUCIBILITY :
        This bug can be reproduced always.

        ---------- BEGIN SOURCE ----------
        package com.example;

        import javax.servlet.ServletException;
        import javax.servlet.http.*;
        import javax.xml.parsers.DocumentBuilderFactory;
        import org.w3c.dom.Document;
        import org.w3c.dom.ls.DOMImplementationLS;

        public class DOMNormalizerLeakServlet extends HttpServlet {
            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException {
                try {
                    Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
                    document.createElement("test");
                    DOMImplementationLS implementation = (DOMImplementationLS)document.getImplementation();
                    implementation.createLSSerializer().writeToString(document);
                    response.getWriter().write("done");
                }
                catch (Exception e) {
                    throw new ServletException(e);
                }
            }
        }

        <?xml version="1.0" encoding="UTF-8"?>
        <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
        version="3.1">
            <servlet>
                <servlet-name>test</servlet-name>
                <servlet-class>com.example.DOMNormalizerLeakServlet</servlet-class>
            </servlet>
            <servlet-mapping>
                <servlet-name>test</servlet-name>
                <url-pattern>/index.html</url-pattern>
            </servlet-mapping>
        </web-app>

        ---------- END SOURCE ----------

        CUSTOMER SUBMITTED WORKAROUND :
        Add the names of the two classes com.sun.org.apache.xerces.internal.dom.DOMNormalizer and com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl to the classesToInitialize parameter of the JreMemoryLeakPreventionListener in TOMCAT_HOME/conf/server.xml.

        1. DOMNormalizerLeakServlet.java
          2 kB
          Pallavi Sonal
        2. web.xml
          0.9 kB
          Pallavi Sonal

          Issue Links

            Activity

            Hide
            psonal Pallavi Sonal added a comment - - edited
            Attached is the test case provided by the submitter. I could not reproduce the issue , at step 5 when I reload the application and click on Find leaks, I get " No web applications appear to have triggered a memory leak on stop, reload or undeploy."
            Tested on apache-tomcat-8.0.30 with
            JDK 8u66 -Pass
            JDK 8u60 -Pass.
            Show
            psonal Pallavi Sonal added a comment - - edited Attached is the test case provided by the submitter. I could not reproduce the issue , at step 5 when I reload the application and click on Find leaks, I get " No web applications appear to have triggered a memory leak on stop, reload or undeploy." Tested on apache-tomcat-8.0.30 with JDK 8u66 -Pass JDK 8u60 -Pass.
            Hide
            psonal Pallavi Sonal added a comment -
            Tried with Tomcat 8.0.28 as suggested by submitter. Issue reproducible with it. Following is the output on clicking "Find Leaks" after "Reload"ing the application:

            The following web applications were stopped (reloaded, undeployed), but their
            classes from previous runs are still loaded in memory, thus causing a memory
            leak (use a profiler to confirm):
            /JI9025281
            Show
            psonal Pallavi Sonal added a comment - Tried with Tomcat 8.0.28 as suggested by submitter. Issue reproducible with it. Following is the output on clicking "Find Leaks" after "Reload"ing the application: The following web applications were stopped (reloaded, undeployed), but their classes from previous runs are still loaded in memory, thus causing a memory leak (use a profiler to confirm): /JI9025281
            Hide
            aefimov Aleksej Efimov added a comment -
            XERCESJ-1667 fix solves the reported issue and provided reproducer doesn't cause memory leak with the fix integrated
            Show
            aefimov Aleksej Efimov added a comment - XERCESJ-1667 fix solves the reported issue and provided reproducer doesn't cause memory leak with the fix integrated
            Hide
            hgupdate HG Updates added a comment -
            URL: http://hg.openjdk.java.net/jdk9/dev/jaxp/rev/4271b37ff91f
            User: aefimov
            Date: 2016-08-18 13:57:04 +0000
            Show
            hgupdate HG Updates added a comment - URL: http://hg.openjdk.java.net/jdk9/dev/jaxp/rev/4271b37ff91f User: aefimov Date: 2016-08-18 13:57:04 +0000
            Hide
            hgupdate HG Updates added a comment -
            URL: http://hg.openjdk.java.net/jdk9/jdk9/jaxp/rev/4271b37ff91f
            User: lana
            Date: 2016-08-24 21:12:38 +0000
            Show
            hgupdate HG Updates added a comment - URL: http://hg.openjdk.java.net/jdk9/jdk9/jaxp/rev/4271b37ff91f User: lana Date: 2016-08-24 21:12:38 +0000
            Hide
            psonal Pallavi Sonal added a comment -
            To submitter:
            You will be pleased to know that this has been fixed in JDK 8u122. The version is still in early access. Can you please verify at your end and confirm the resolution with the JDK 8u122 –ea. The same can be downloaded from https://jdk8.java.net/download.html.
            Show
            psonal Pallavi Sonal added a comment - To submitter: You will be pleased to know that this has been fixed in JDK 8u122. The version is still in early access. Can you please verify at your end and confirm the resolution with the JDK 8u122 –ea. The same can be downloaded from https://jdk8.java.net/download.html .
            Hide
            psonal Pallavi Sonal added a comment -
            From submitter:
            ------------------
            Thanks for getting back to me. Over the weekend I confirmed that this bug has been fixed to my satisfaction in Java 8 update 122 early access.
            Show
            psonal Pallavi Sonal added a comment - From submitter: ------------------ Thanks for getting back to me. Over the weekend I confirmed that this bug has been fixed to my satisfaction in Java 8 update 122 early access.

              People

              • Assignee:
                aefimov Aleksej Efimov
                Reporter:
                webbuggrp Webbug Group
              • Votes:
                0 Vote for this issue
                Watchers:
                8 Start watching this issue

                Dates

                • Created:
                  Updated:
                  Resolved: