Lines 464-478
Link Here
|
464 |
private boolean clearReferencesStopTimerThreads = false; |
464 |
private boolean clearReferencesStopTimerThreads = false; |
465 |
|
465 |
|
466 |
/** |
466 |
/** |
467 |
* Should Tomcat attempt to clear any ThreadLocal objects that are instances |
|
|
468 |
* of classes loaded by this class loader. Failure to remove any such |
469 |
* objects will result in a memory leak on web application stop, undeploy or |
470 |
* reload. It is disabled by default since the clearing of the ThreadLocal |
471 |
* objects is not performed in a thread-safe manner. |
472 |
*/ |
473 |
private boolean clearReferencesThreadLocals = false; |
474 |
|
475 |
/** |
476 |
* Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()} |
467 |
* Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()} |
477 |
* when the class loader is stopped? If not specified, the default value |
468 |
* when the class loader is stopped? If not specified, the default value |
478 |
* of <code>true</code> is used. Changing the default setting is likely to |
469 |
* of <code>true</code> is used. Changing the default setting is likely to |
Lines 740-764
Link Here
|
740 |
} |
731 |
} |
741 |
|
732 |
|
742 |
|
733 |
|
743 |
/** |
|
|
744 |
* Return the clearReferencesThreadLocals flag for this Context. |
745 |
*/ |
746 |
public boolean getClearReferencesThreadLocals() { |
747 |
return (this.clearReferencesThreadLocals); |
748 |
} |
749 |
|
750 |
|
751 |
/** |
752 |
* Set the clearReferencesThreadLocals feature for this Context. |
753 |
* |
754 |
* @param clearReferencesThreadLocals The new flag value |
755 |
*/ |
756 |
public void setClearReferencesThreadLocals( |
757 |
boolean clearReferencesThreadLocals) { |
758 |
this.clearReferencesThreadLocals = clearReferencesThreadLocals; |
759 |
} |
760 |
|
761 |
|
762 |
// ------------------------------------------------------- Reloader Methods |
734 |
// ------------------------------------------------------- Reloader Methods |
763 |
|
735 |
|
764 |
|
736 |
|
Lines 1884-1891
Link Here
|
1884 |
// Stop any threads the web application started |
1856 |
// Stop any threads the web application started |
1885 |
clearReferencesThreads(); |
1857 |
clearReferencesThreads(); |
1886 |
|
1858 |
|
1887 |
// Clear any ThreadLocals loaded by this class loader |
1859 |
// Check for leaks triggered by ThreadLocals loaded by this class loader |
1888 |
clearReferencesThreadLocals(); |
1860 |
checkThreadLocalsForLeaks(); |
1889 |
|
1861 |
|
1890 |
// Clear RMI Targets loaded by this class loader |
1862 |
// Clear RMI Targets loaded by this class loader |
1891 |
clearReferencesRmiTargets(); |
1863 |
clearReferencesRmiTargets(); |
Lines 2079-2085
Link Here
|
2079 |
Object value = field.get(instance); |
2051 |
Object value = field.get(instance); |
2080 |
if (null != value) { |
2052 |
if (null != value) { |
2081 |
Class<? extends Object> valueClass = value.getClass(); |
2053 |
Class<? extends Object> valueClass = value.getClass(); |
2082 |
if (!loadedByThisOrChild(valueClass)) { |
2054 |
if (!objectIsLoadedByThisOrChildClassLoader(valueClass)) { |
2083 |
if (log.isDebugEnabled()) { |
2055 |
if (log.isDebugEnabled()) { |
2084 |
log.debug("Not setting field " + field.getName() + |
2056 |
log.debug("Not setting field " + field.getName() + |
2085 |
" to null in object of class " + |
2057 |
" to null in object of class " + |
Lines 2279-2285
Link Here
|
2279 |
} |
2251 |
} |
2280 |
} |
2252 |
} |
2281 |
|
2253 |
|
2282 |
private void clearReferencesThreadLocals() { |
2254 |
private void checkThreadLocalsForLeaks() { |
2283 |
Thread[] threads = getThreads(); |
2255 |
Thread[] threads = getThreads(); |
2284 |
|
2256 |
|
2285 |
try { |
2257 |
try { |
Lines 2303-2375
Link Here
|
2303 |
if (threads[i] != null) { |
2275 |
if (threads[i] != null) { |
2304 |
// Clear the first map |
2276 |
// Clear the first map |
2305 |
threadLocalMap = threadLocalsField.get(threads[i]); |
2277 |
threadLocalMap = threadLocalsField.get(threads[i]); |
2306 |
clearThreadLocalMap(threadLocalMap, tableField); |
2278 |
checkThreadLocalMapForLeaks(threadLocalMap, tableField); |
2307 |
// Clear the second map |
2279 |
// Clear the second map |
2308 |
threadLocalMap = |
2280 |
threadLocalMap = |
2309 |
inheritableThreadLocalsField.get(threads[i]); |
2281 |
inheritableThreadLocalsField.get(threads[i]); |
2310 |
clearThreadLocalMap(threadLocalMap, tableField); |
2282 |
checkThreadLocalMapForLeaks(threadLocalMap, tableField); |
2311 |
} |
2283 |
} |
2312 |
} |
2284 |
} |
2313 |
} catch (SecurityException e) { |
2285 |
} catch (SecurityException e) { |
2314 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2286 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2315 |
contextName), e); |
2287 |
contextName), e); |
2316 |
} catch (NoSuchFieldException e) { |
2288 |
} catch (NoSuchFieldException e) { |
2317 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2289 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2318 |
contextName), e); |
2290 |
contextName), e); |
2319 |
} catch (ClassNotFoundException e) { |
2291 |
} catch (ClassNotFoundException e) { |
2320 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2292 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2321 |
contextName), e); |
2293 |
contextName), e); |
2322 |
} catch (IllegalArgumentException e) { |
2294 |
} catch (IllegalArgumentException e) { |
2323 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2295 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2324 |
contextName), e); |
2296 |
contextName), e); |
2325 |
} catch (IllegalAccessException e) { |
2297 |
} catch (IllegalAccessException e) { |
2326 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2298 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2327 |
contextName), e); |
2299 |
contextName), e); |
2328 |
} catch (NoSuchMethodException e) { |
2300 |
} catch (NoSuchMethodException e) { |
2329 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2301 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2330 |
contextName), e); |
2302 |
contextName), e); |
2331 |
} catch (InvocationTargetException e) { |
2303 |
} catch (InvocationTargetException e) { |
2332 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2304 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2333 |
contextName), e); |
2305 |
contextName), e); |
2334 |
} |
2306 |
} |
2335 |
} |
2307 |
} |
2336 |
|
2308 |
|
2337 |
|
2309 |
|
2338 |
/* |
2310 |
/* |
2339 |
* Clears the given thread local map object. Also pass in the field that |
2311 |
* Analyzes the given thread local map object. Also pass in the field that |
2340 |
* points to the internal table to save re-calculating it on every |
2312 |
* points to the internal table to save re-calculating it on every |
2341 |
* call to this method. |
2313 |
* call to this method. |
2342 |
*/ |
2314 |
*/ |
2343 |
private void clearThreadLocalMap(Object map, Field internalTableField) |
2315 |
private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) |
2344 |
throws NoSuchMethodException, IllegalAccessException, |
2316 |
throws NoSuchMethodException, IllegalAccessException, |
2345 |
NoSuchFieldException, InvocationTargetException { |
2317 |
NoSuchFieldException, InvocationTargetException { |
2346 |
if (map != null) { |
2318 |
if (map != null) { |
2347 |
Method mapRemove = |
|
|
2348 |
map.getClass().getDeclaredMethod("remove", |
2349 |
ThreadLocal.class); |
2350 |
mapRemove.setAccessible(true); |
2351 |
Object[] table = (Object[]) internalTableField.get(map); |
2319 |
Object[] table = (Object[]) internalTableField.get(map); |
2352 |
int staleEntriesCount = 0; |
|
|
2353 |
if (table != null) { |
2320 |
if (table != null) { |
2354 |
for (int j =0; j < table.length; j++) { |
2321 |
for (int j =0; j < table.length; j++) { |
2355 |
if (table[j] != null) { |
2322 |
if (table[j] != null) { |
2356 |
boolean remove = false; |
2323 |
boolean potentialLeak = false; |
2357 |
// Check the key |
2324 |
// Check the key |
2358 |
Object key = ((Reference<?>) table[j]).get(); |
2325 |
Object key = ((Reference<?>) table[j]).get(); |
2359 |
if (this.equals(key) || (key != null && |
2326 |
if (this.equals(key) || objectIsLoadedByThisOrChildClassLoader(key)) { |
2360 |
this == key.getClass().getClassLoader())) { |
2327 |
potentialLeak = true; |
2361 |
remove = true; |
|
|
2362 |
} |
2328 |
} |
2363 |
// Check the value |
2329 |
// Check the value |
2364 |
Field valueField = |
2330 |
Field valueField = |
2365 |
table[j].getClass().getDeclaredField("value"); |
2331 |
table[j].getClass().getDeclaredField("value"); |
2366 |
valueField.setAccessible(true); |
2332 |
valueField.setAccessible(true); |
2367 |
Object value = valueField.get(table[j]); |
2333 |
Object value = valueField.get(table[j]); |
2368 |
if (this.equals(value) || (value != null && |
2334 |
if (this.equals(value) || objectIsLoadedByThisOrChildClassLoader(value)) { |
2369 |
this == value.getClass().getClassLoader())) { |
2335 |
potentialLeak = true; |
2370 |
remove = true; |
|
|
2371 |
} |
2336 |
} |
2372 |
if (remove) { |
2337 |
if (potentialLeak) { |
2373 |
Object[] args = new Object[5]; |
2338 |
Object[] args = new Object[5]; |
2374 |
args[0] = contextName; |
2339 |
args[0] = contextName; |
2375 |
if (key != null) { |
2340 |
if (key != null) { |
Lines 2379-2421
Link Here
|
2379 |
if (value != null) { |
2344 |
if (value != null) { |
2380 |
args[3] = value.getClass().getCanonicalName(); |
2345 |
args[3] = value.getClass().getCanonicalName(); |
2381 |
args[4] = value.toString(); |
2346 |
args[4] = value.toString(); |
2382 |
} |
2347 |
log.error(sm.getString( |
2383 |
if (value == null) { |
2348 |
"webappClassLoader.checkThreadLocalsForLeaks", |
|
|
2349 |
args)); |
2350 |
} else { |
2384 |
if (log.isDebugEnabled()) { |
2351 |
if (log.isDebugEnabled()) { |
2385 |
log.debug(sm.getString( |
2352 |
log.debug(sm.getString( |
2386 |
"webappClassLoader.clearThreadLocalDebug", |
2353 |
"webappClassLoader.checkThreadLocalsForLeaksDebug", |
2387 |
args)); |
2354 |
args)); |
2388 |
if (clearReferencesThreadLocals) { |
|
|
2389 |
log.debug(sm.getString( |
2390 |
"webappClassLoader.clearThreadLocalDebugClear")); |
2391 |
} |
2392 |
} |
2355 |
} |
2393 |
} else { |
|
|
2394 |
log.error(sm.getString( |
2395 |
"webappClassLoader.clearThreadLocal", |
2396 |
args)); |
2397 |
if (clearReferencesThreadLocals) { |
2398 |
log.info(sm.getString( |
2399 |
"webappClassLoader.clearThreadLocalClear")); |
2400 |
} |
2401 |
} |
2356 |
} |
2402 |
if (clearReferencesThreadLocals) { |
|
|
2403 |
if (key == null) { |
2404 |
staleEntriesCount++; |
2405 |
} else { |
2406 |
mapRemove.invoke(map, key); |
2407 |
} |
2408 |
} |
2409 |
} |
2357 |
} |
2410 |
} |
2358 |
} |
2411 |
} |
2359 |
} |
2412 |
} |
2360 |
} |
2413 |
if (staleEntriesCount > 0) { |
|
|
2414 |
Method mapRemoveStale = |
2415 |
map.getClass().getDeclaredMethod("expungeStaleEntries"); |
2416 |
mapRemoveStale.setAccessible(true); |
2417 |
mapRemoveStale.invoke(map); |
2418 |
} |
2419 |
} |
2361 |
} |
2420 |
} |
2362 |
} |
2421 |
|
2363 |
|
Lines 2604-2626
Link Here
|
2604 |
} |
2546 |
} |
2605 |
} |
2547 |
} |
2606 |
|
2548 |
|
2607 |
|
|
|
2608 |
/** |
2549 |
/** |
2609 |
* Determine whether a class was loaded by this class loader or one of |
2550 |
* @param o |
2610 |
* its child class loaders. |
2551 |
* an instance may be null |
|
|
2552 |
* @return true if o os either a Class or an object of a Class that was |
2553 |
* loaded by this ClassLoader. |
2611 |
*/ |
2554 |
*/ |
2612 |
protected boolean loadedByThisOrChild(Class clazz) |
2555 |
private boolean objectIsLoadedByThisOrChildClassLoader(Object o) { |
2613 |
{ |
2556 |
if (o == null) { |
2614 |
boolean result = false; |
2557 |
return false; |
2615 |
for (ClassLoader classLoader = clazz.getClassLoader(); |
2558 |
} |
2616 |
null != classLoader; classLoader = classLoader.getParent()) { |
2559 |
|
2617 |
if (classLoader.equals(this)) { |
2560 |
Class<?> clazz; |
2618 |
result = true; |
2561 |
if (o instanceof Class) { |
2619 |
break; |
2562 |
clazz = (Class<?>) o; |
|
|
2563 |
} else { |
2564 |
clazz = o.getClass(); |
2565 |
} |
2566 |
|
2567 |
for (ClassLoader cl = clazz.getClassLoader(); cl != null; cl = cl.getParent()) { |
2568 |
if (cl == this) { |
2569 |
return true; |
2620 |
} |
2570 |
} |
2621 |
} |
2571 |
} |
2622 |
return result; |
2572 |
return false; |
2623 |
} |
2573 |
} |
2624 |
|
2574 |
|
2625 |
|
2575 |
|
2626 |
/** |
2576 |
/** |