1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18 package org.apache.catalina.core;
19
20 import java.io.IOException;
21 import java.lang.reflect.InvocationTargetException;
22 import java.lang.reflect.Method;
23 import java.net.URLConnection;
24 import java.sql.DriverManager;
25 import java.util.StringTokenizer;
26 import java.util.concurrent.ForkJoinPool;
27
28 import javax.imageio.ImageIO;
29 import javax.xml.parsers.DocumentBuilder;
30 import javax.xml.parsers.DocumentBuilderFactory;
31 import javax.xml.parsers.ParserConfigurationException;
32
33 import org.apache.catalina.Lifecycle;
34 import org.apache.catalina.LifecycleEvent;
35 import org.apache.catalina.LifecycleListener;
36 import org.apache.catalina.startup.SafeForkJoinWorkerThreadFactory;
37 import org.apache.juli.logging.Log;
38 import org.apache.juli.logging.LogFactory;
39 import org.apache.tomcat.util.ExceptionUtils;
40 import org.apache.tomcat.util.compat.JreCompat;
41 import org.apache.tomcat.util.compat.JreVendor;
42 import org.apache.tomcat.util.res.StringManager;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.ls.DOMImplementationLS;
45
46 /**
47  * Provide a workaround for known places where the Java Runtime environment can
48  * cause a memory leak or lock files.
49  * <p>
50  * Memory leaks occur when JRE code uses
51  * the context class loader to load a singleton as this will cause a memory leak
52  * if a web application class loader happens to be the context class loader at
53  * the time. The work-around is to initialise these singletons when Tomcat's
54  * common class loader is the context class loader.
55  * <p>
56  * Locked files usually occur when a resource inside a JAR is accessed without
57  * first disabling Jar URL connection caching. The workaround is to disable this
58  * caching by default.
59  */

60 public class JreMemoryLeakPreventionListener implements LifecycleListener {
61
62     private static final Log log =
63         LogFactory.getLog(JreMemoryLeakPreventionListener.class);
64     private static final StringManager sm =
65         StringManager.getManager(Constants.Package);
66
67     private static final String FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY =
68             "java.util.concurrent.ForkJoinPool.common.threadFactory";
69     /**
70      * Protect against the memory leak caused when the first call to
71      * <code>sun.awt.AppContext.getAppContext()</code> is triggered by a web
72      * application. Defaults to <code>false</code> since Tomcat code no longer
73      * triggers this although application code may.
74      */

75     private boolean appContextProtection = false;
76     public boolean isAppContextProtection() { return appContextProtection; }
77     public void setAppContextProtection(boolean appContextProtection) {
78         this.appContextProtection = appContextProtection;
79     }
80
81     /**
82      * Protect against the memory leak caused when the first call to
83      * <code>java.awt.Toolkit.getDefaultToolkit()</code> is triggered
84      * by a web application. Defaults to <code>false</code> because a new
85      * Thread is launched.
86      */

87     private boolean awtThreadProtection = false;
88     public boolean isAWTThreadProtection() { return awtThreadProtection; }
89     public void setAWTThreadProtection(boolean awtThreadProtection) {
90       this.awtThreadProtection = awtThreadProtection;
91     }
92
93     /**
94      * Protect against the memory leak caused when the first call to
95      * <code>sun.misc.GC.requestLatency(long)</code> is triggered by a web
96      * application. This first call will start a GC Daemon thread with the
97      * thread's context class loader configured to be the web application class
98      * loader. Defaults to <code>true</code>.
99      *
100      * @see "http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8157570"
101      */

102     private boolean gcDaemonProtection = true;
103     public boolean isGcDaemonProtection() { return gcDaemonProtection; }
104     public void setGcDaemonProtection(boolean gcDaemonProtection) {
105         this.gcDaemonProtection = gcDaemonProtection;
106     }
107
108      /**
109      * Protect against the memory leak, when the initialization of the
110      * Java Cryptography Architecture is triggered by initializing
111      * a MessageDigest during web application deployment.
112      * This will occasionally start a Token Poller thread with the thread's
113      * context class loader equal to the web application class loader.
114      * Instead we initialize JCA early.
115      * Defaults to <code>true</code>.
116      */

117     private boolean tokenPollerProtection = true;
118     public boolean isTokenPollerProtection() { return tokenPollerProtection; }
119     public void setTokenPollerProtection(boolean tokenPollerProtection) {
120         this.tokenPollerProtection = tokenPollerProtection;
121     }
122
123     /**
124      * Protect against resources being read for JAR files and, as a side-effect,
125      * the JAR file becoming locked. Note this disables caching for all
126      * {@link URLConnection}s, regardless of type. Defaults to
127      * <code>true</code>.
128      */

129     private boolean urlCacheProtection = true;
130     public boolean isUrlCacheProtection() { return urlCacheProtection; }
131     public void setUrlCacheProtection(boolean urlCacheProtection) {
132         this.urlCacheProtection = urlCacheProtection;
133     }
134
135     /**
136      * XML parsing can pin a web application class loader in memory. There are
137      * multiple root causes for this. Some of these are particularly nasty as
138      * profilers may not identify any GC roots related to the leak. For example,
139      * with YourKit you need to ensure that HPROF format memory snapshots are
140      * used to be able to trace some of the leaks.
141      */

142     private boolean xmlParsingProtection = true;
143     public boolean isXmlParsingProtection() { return xmlParsingProtection; }
144     public void setXmlParsingProtection(boolean xmlParsingProtection) {
145         this.xmlParsingProtection = xmlParsingProtection;
146     }
147
148     /**
149      * <code>com.sun.jndi.ldap.LdapPoolManager</code> class spawns a thread when
150      * it is initialized if the system property
151      * <code>com.sun.jndi.ldap.connect.pool.timeout</code> is greater than 0.
152      * That thread inherits the context class loader of the current thread, so
153      * that there may be a web application class loader leak if the web app
154      * is the first to use <code>LdapPoolManager</code>.
155      *
156      * @see "http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8156824"
157      */

158     private boolean ldapPoolProtection = true;
159     public boolean isLdapPoolProtection() { return ldapPoolProtection; }
160     public void setLdapPoolProtection(boolean ldapPoolProtection) {
161         this.ldapPoolProtection = ldapPoolProtection;
162     }
163
164     /**
165      * The first access to {@link DriverManager} will trigger the loading of
166      * all {@link java.sql.Driver}s in the the current class loader. The web
167      * application level memory leak protection can take care of this in most
168      * cases but triggering the loading here has fewer side-effects.
169      */

170     private boolean driverManagerProtection = true;
171     public boolean isDriverManagerProtection() {
172         return driverManagerProtection;
173     }
174     public void setDriverManagerProtection(boolean driverManagerProtection) {
175         this.driverManagerProtection = driverManagerProtection;
176     }
177
178     /**
179      * {@link ForkJoinPool#commonPool()} creates a thread pool that, by default,
180      * creates threads that retain references to the thread context class
181      * loader.
182      *
183      * @see "http://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8172726"
184      */

185     private boolean forkJoinCommonPoolProtection = true;
186     public boolean getForkJoinCommonPoolProtection() {
187         return forkJoinCommonPoolProtection;
188     }
189     public void setForkJoinCommonPoolProtection(boolean forkJoinCommonPoolProtection) {
190         this.forkJoinCommonPoolProtection = forkJoinCommonPoolProtection;
191     }
192
193     /**
194      * List of comma-separated fully qualified class names to load and initialize during
195      * the startup of this Listener. This allows to pre-load classes that are known to
196      * provoke classloader leaks if they are loaded during a request processing.
197      */

198     private String classesToInitialize = null;
199     public String getClassesToInitialize() {
200         return classesToInitialize;
201     }
202     public void setClassesToInitialize(String classesToInitialize) {
203         this.classesToInitialize = classesToInitialize;
204     }
205
206
207
208     @Override
209     public void lifecycleEvent(LifecycleEvent event) {
210         // Initialise these classes when Tomcat starts
211         if (Lifecycle.BEFORE_INIT_EVENT.equals(event.getType())) {
212
213             /*
214              * First call to this loads all drivers visible to the current class
215              * loader and its parents.
216              *
217              * Note: This is called before the context class loader is changed
218              *       because we want any drivers located in CATALINA_HOME/lib
219              *       and/or CATALINA_HOME/lib to be visible to DriverManager.
220              *       Users wishing to avoid having JDBC drivers loaded by this
221              *       class loader should add the JDBC driver(s) to the class
222              *       path so they are loaded by the system class loader.
223              */

224             if (driverManagerProtection) {
225                 DriverManager.getDrivers();
226             }
227
228             ClassLoader loader = Thread.currentThread().getContextClassLoader();
229
230             try
231             {
232                 // Use the system classloader as the victim for all this
233                 // ClassLoader pinning we're about to do.
234                 Thread.currentThread().setContextClassLoader(
235                         ClassLoader.getSystemClassLoader());
236
237                 /*
238                  * Several components end up calling:
239                  * sun.awt.AppContext.getAppContext()
240                  *
241                  * Those libraries / components known to trigger memory leaks
242                  * due to eventual calls to getAppContext() are:
243                  * - Google Web Toolkit via its use of javax.imageio
244                  * - Batik
245                  * - others TBD
246                  *
247                  * Note tha a call to sun.awt.AppContext.getAppContext() results
248                  * in a thread being started named AWT-AppKit that requires a
249                  * graphical environment to be available.
250                  */

251
252                 // Trigger a call to sun.awt.AppContext.getAppContext(). This
253                 // will pin the system class loader in memory but that shouldn't
254                 // be an issue.
255                 if (appContextProtection) {
256                     ImageIO.getCacheDirectory();
257                 }
258
259                 // Trigger the creation of the AWT (AWT-Windows, AWT-XAWT,
260                 // etc.) thread.
261                 // Note this issue is fixed in Java 8 update 05 onwards.
262                 if (awtThreadProtection && !JreCompat.isJre9Available()) {
263                     java.awt.Toolkit.getDefaultToolkit();
264                 }
265
266                 /*
267                  * Several components end up calling
268                  * sun.misc.GC.requestLatency(long) which creates a daemon
269                  * thread without setting the TCCL.
270                  *
271                  * Those libraries / components known to trigger memory leaks
272                  * due to eventual calls to requestLatency(long) are:
273                  * - javax.management.remote.rmi.RMIConnectorServer.start()
274                  *
275                  * Note: Long.MAX_VALUE is a special case that causes the thread
276                  *       to terminate
277                  *
278                  * Fixed in Java 9 onwards (from early access build 130)
279                  */

280                 if (gcDaemonProtection && !JreCompat.isJre9Available()) {
281                     try {
282                         Class<?> clazz = Class.forName("sun.misc.GC");
283                         Method method = clazz.getDeclaredMethod(
284                                 "requestLatency",
285                                 new Class[] {long.class});
286                         method.invoke(null, Long.valueOf(Long.MAX_VALUE - 1));
287                     } catch (ClassNotFoundException e) {
288                         if (JreVendor.IS_ORACLE_JVM) {
289                             log.error(sm.getString(
290                                     "jreLeakListener.gcDaemonFail"), e);
291                         } else {
292                             log.debug(sm.getString(
293                                     "jreLeakListener.gcDaemonFail"), e);
294                         }
295                     } catch (SecurityException | NoSuchMethodException | IllegalArgumentException |
296                             IllegalAccessException e) {
297                         log.error(sm.getString("jreLeakListener.gcDaemonFail"),
298                                 e);
299                     } catch (InvocationTargetException e) {
300                         ExceptionUtils.handleThrowable(e.getCause());
301                         log.error(sm.getString("jreLeakListener.gcDaemonFail"),
302                                 e);
303                     }
304                 }
305
306                 /*
307                  * Creating a MessageDigest during web application startup
308                  * initializes the Java Cryptography Architecture. Under certain
309                  * conditions this starts a Token poller thread with TCCL equal
310                  * to the web application class loader.
311                  *
312                  * Instead we initialize JCA right now.
313                  *
314                  * Fixed in Java 9 onwards (from early access build 133)
315                  */

316                 if (tokenPollerProtection && !JreCompat.isJre9Available()) {
317                     java.security.Security.getProviders();
318                 }
319
320                 /*
321                  * Several components end up opening JarURLConnections without
322                  * first disabling caching. This effectively locks the file.
323                  * Whilst more noticeable and harder to ignore on Windows, it
324                  * affects all operating systems.
325                  *
326                  * Those libraries/components known to trigger this issue
327                  * include:
328                  * - log4j versions 1.2.15 and earlier
329                  * - javax.xml.bind.JAXBContext.newInstance()
330                  *
331                  * https://bugs.openjdk.java.net/browse/JDK-8163449
332                  *
333                  * Java 9 onwards disables caching for JAR URLConnections
334                  * Java 8 and earlier disables caching for all URLConnections
335                  */

336
337                 // Set the default URL caching policy to not to cache
338                 if (urlCacheProtection) {
339                     try {
340                         JreCompat.getInstance().disableCachingForJarUrlConnections();
341                     } catch (IOException e) {
342                         log.error(sm.getString("jreLeakListener.jarUrlConnCacheFail"), e);
343                     }
344                 }
345
346                 /*
347                  * Fixed in Java 9 onwards (from early access build 133)
348                  */

349                 if (xmlParsingProtection && !JreCompat.isJre9Available()) {
350                     // There are two known issues with XML parsing that affect
351                     // Java 8+. The issues both relate to cached Exception
352                     // instances that retain a link to the TCCL via the
353                     // backtrace field. Note that YourKit only shows this field
354                     // when using the HPROF format memory snapshots.
355                     // https://bz.apache.org/bugzilla/show_bug.cgi?id=58486
356                     // https://bugs.openjdk.java.net/browse/JDK-8146961
357                     DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
358                     try {
359                         DocumentBuilder documentBuilder = factory.newDocumentBuilder();
360                         // Issue 1
361                         // com.sun.org.apache.xml.internal.serialize.DOMSerializerImpl
362                         Document document = documentBuilder.newDocument();
363                         document.createElement("dummy");
364                         DOMImplementationLS implementation =
365                                 (DOMImplementationLS)document.getImplementation();
366                         implementation.createLSSerializer().writeToString(document);
367                         // Issue 1
368                         // com.sun.org.apache.xerces.internal.dom.DOMNormalizer
369                         document.normalize();
370                     } catch (ParserConfigurationException e) {
371                         log.error(sm.getString("jreLeakListener.xmlParseFail"),
372                                 e);
373                     }
374                 }
375
376                 /*
377                  * Fixed in Java 9 onwards (from early access build 130)
378                  */

379                 if (ldapPoolProtection && !JreCompat.isJre9Available()) {
380                     try {
381                         Class.forName("com.sun.jndi.ldap.LdapPoolManager");
382                     } catch (ClassNotFoundException e) {
383                         if (JreVendor.IS_ORACLE_JVM) {
384                             log.error(sm.getString(
385                                     "jreLeakListener.ldapPoolManagerFail"), e);
386                         } else {
387                             log.debug(sm.getString(
388                                     "jreLeakListener.ldapPoolManagerFail"), e);
389                         }
390                     }
391                 }
392
393                 /*
394                  * Present in Java 7 onwards
395                  * Fixed in Java 9 (from early access build 156)
396                  */

397                 if (forkJoinCommonPoolProtection && !JreCompat.isJre9Available()) {
398                     // Don't override any explicitly set property
399                     if (System.getProperty(FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY) == null) {
400                         System.setProperty(FORK_JOIN_POOL_THREAD_FACTORY_PROPERTY,
401                                 SafeForkJoinWorkerThreadFactory.class.getName());
402                     }
403                 }
404
405                 if (classesToInitialize != null) {
406                     StringTokenizer strTok =
407                         new StringTokenizer(classesToInitialize, ", \r\n\t");
408                     while (strTok.hasMoreTokens()) {
409                         String classNameToLoad = strTok.nextToken();
410                         try {
411                             Class.forName(classNameToLoad);
412                         } catch (ClassNotFoundException e) {
413                             log.error(
414                                 sm.getString("jreLeakListener.classToInitializeFail",
415                                     classNameToLoad), e);
416                             // continue with next class to load
417                         }
418                     }
419                 }
420
421             } finally {
422                 Thread.currentThread().setContextClassLoader(loader);
423             }
424         }
425     }
426 }
427