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