JDK-8146961 : Fix PermGen memory leaks caused by static final Exceptions
  • Type: Bug
  • Component: xml
  • Sub-Component: org.w3c.dom
  • Affected Version: 8u60,9
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: windows_8
  • CPU: generic
  • Submitted: 2015-10-06
  • Updated: 2017-11-29
  • Resolved: 2016-08-18
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 8 JDK 9
8u152Fixed 9 b133Fixed
Related Reports
Relates :  
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.


Comments
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.
09-01-2017

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.
06-01-2017

XERCESJ-1667 fix solves the reported issue and provided reproducer doesn't cause memory leak with the fix integrated
15-08-2016

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.
13-01-2016

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
13-01-2016