Lines 459-473
Link Here
|
459 |
private boolean clearReferencesStopTimerThreads = false; |
459 |
private boolean clearReferencesStopTimerThreads = false; |
460 |
|
460 |
|
461 |
/** |
461 |
/** |
462 |
* Should Tomcat attempt to clear any ThreadLocal objects that are instances |
|
|
463 |
* of classes loaded by this class loader. Failure to remove any such |
464 |
* objects will result in a memory leak on web application stop, undeploy or |
465 |
* reload. It is disabled by default since the clearing of the ThreadLocal |
466 |
* objects is not performed in a thread-safe manner. |
467 |
*/ |
468 |
private boolean clearReferencesThreadLocals = false; |
469 |
|
470 |
/** |
471 |
* Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()} |
462 |
* Should Tomcat call {@link org.apache.juli.logging.LogFactory#release()} |
472 |
* when the class loader is stopped? If not specified, the default value |
463 |
* when the class loader is stopped? If not specified, the default value |
473 |
* of <code>true</code> is used. Changing the default setting is likely to |
464 |
* of <code>true</code> is used. Changing the default setting is likely to |
Lines 755-779
Link Here
|
755 |
} |
746 |
} |
756 |
|
747 |
|
757 |
|
748 |
|
758 |
/** |
|
|
759 |
* Return the clearReferencesThreadLocals flag for this Context. |
760 |
*/ |
761 |
public boolean getClearReferencesThreadLocals() { |
762 |
return (this.clearReferencesThreadLocals); |
763 |
} |
764 |
|
765 |
|
766 |
/** |
767 |
* Set the clearReferencesThreadLocals feature for this Context. |
768 |
* |
769 |
* @param clearReferencesThreadLocals The new flag value |
770 |
*/ |
771 |
public void setClearReferencesThreadLocals( |
772 |
boolean clearReferencesThreadLocals) { |
773 |
this.clearReferencesThreadLocals = clearReferencesThreadLocals; |
774 |
} |
775 |
|
776 |
|
777 |
// ------------------------------------------------------- Reloader Methods |
749 |
// ------------------------------------------------------- Reloader Methods |
778 |
|
750 |
|
779 |
|
751 |
|
Lines 1951-1957
Link Here
|
1951 |
clearReferencesThreads(); |
1923 |
clearReferencesThreads(); |
1952 |
|
1924 |
|
1953 |
// Clear any ThreadLocals loaded by this class loader |
1925 |
// Clear any ThreadLocals loaded by this class loader |
1954 |
clearReferencesThreadLocals(); |
1926 |
checkThreadLocalsForLeaks(); |
1955 |
|
1927 |
|
1956 |
// Clear RMI Targets loaded by this class loader |
1928 |
// Clear RMI Targets loaded by this class loader |
1957 |
clearReferencesRmiTargets(); |
1929 |
clearReferencesRmiTargets(); |
Lines 2348-2354
Link Here
|
2348 |
} |
2320 |
} |
2349 |
} |
2321 |
} |
2350 |
|
2322 |
|
2351 |
private void clearReferencesThreadLocals() { |
2323 |
private void checkThreadLocalsForLeaks() { |
2352 |
Thread[] threads = getThreads(); |
2324 |
Thread[] threads = getThreads(); |
2353 |
|
2325 |
|
2354 |
try { |
2326 |
try { |
Lines 2372-2444
Link Here
|
2372 |
if (threads[i] != null) { |
2344 |
if (threads[i] != null) { |
2373 |
// Clear the first map |
2345 |
// Clear the first map |
2374 |
threadLocalMap = threadLocalsField.get(threads[i]); |
2346 |
threadLocalMap = threadLocalsField.get(threads[i]); |
2375 |
clearThreadLocalMap(threadLocalMap, tableField); |
2347 |
checkThreadLocalMapForLeaks(threadLocalMap, tableField); |
2376 |
// Clear the second map |
2348 |
// Clear the second map |
2377 |
threadLocalMap = |
2349 |
threadLocalMap = |
2378 |
inheritableThreadLocalsField.get(threads[i]); |
2350 |
inheritableThreadLocalsField.get(threads[i]); |
2379 |
clearThreadLocalMap(threadLocalMap, tableField); |
2351 |
checkThreadLocalMapForLeaks(threadLocalMap, tableField); |
2380 |
} |
2352 |
} |
2381 |
} |
2353 |
} |
2382 |
} catch (SecurityException e) { |
2354 |
} catch (SecurityException e) { |
2383 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2355 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2384 |
contextName), e); |
2356 |
contextName), e); |
2385 |
} catch (NoSuchFieldException e) { |
2357 |
} catch (NoSuchFieldException e) { |
2386 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2358 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2387 |
contextName), e); |
2359 |
contextName), e); |
2388 |
} catch (ClassNotFoundException e) { |
2360 |
} catch (ClassNotFoundException e) { |
2389 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2361 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2390 |
contextName), e); |
2362 |
contextName), e); |
2391 |
} catch (IllegalArgumentException e) { |
2363 |
} catch (IllegalArgumentException e) { |
2392 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2364 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2393 |
contextName), e); |
2365 |
contextName), e); |
2394 |
} catch (IllegalAccessException e) { |
2366 |
} catch (IllegalAccessException e) { |
2395 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2367 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2396 |
contextName), e); |
2368 |
contextName), e); |
2397 |
} catch (NoSuchMethodException e) { |
2369 |
} catch (NoSuchMethodException e) { |
2398 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2370 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2399 |
contextName), e); |
2371 |
contextName), e); |
2400 |
} catch (InvocationTargetException e) { |
2372 |
} catch (InvocationTargetException e) { |
2401 |
log.warn(sm.getString("webappClassLoader.clearThreadLocalFail", |
2373 |
log.warn(sm.getString("webappClassLoader.checkThreadLocalsForLeaksFail", |
2402 |
contextName), e); |
2374 |
contextName), e); |
2403 |
} |
2375 |
} |
2404 |
} |
2376 |
} |
2405 |
|
2377 |
|
2406 |
|
2378 |
|
2407 |
/* |
2379 |
/* |
2408 |
* Clears the given thread local map object. Also pass in the field that |
2380 |
* Analyzes the given thread local map object. Also pass in the field that |
2409 |
* points to the internal table to save re-calculating it on every |
2381 |
* points to the internal table to save re-calculating it on every |
2410 |
* call to this method. |
2382 |
* call to this method. |
2411 |
*/ |
2383 |
*/ |
2412 |
private void clearThreadLocalMap(Object map, Field internalTableField) |
2384 |
private void checkThreadLocalMapForLeaks(Object map, Field internalTableField) |
2413 |
throws NoSuchMethodException, IllegalAccessException, |
2385 |
throws NoSuchMethodException, IllegalAccessException, |
2414 |
NoSuchFieldException, InvocationTargetException { |
2386 |
NoSuchFieldException, InvocationTargetException { |
2415 |
if (map != null) { |
2387 |
if (map != null) { |
2416 |
Method mapRemove = |
|
|
2417 |
map.getClass().getDeclaredMethod("remove", |
2418 |
ThreadLocal.class); |
2419 |
mapRemove.setAccessible(true); |
2420 |
Object[] table = (Object[]) internalTableField.get(map); |
2388 |
Object[] table = (Object[]) internalTableField.get(map); |
2421 |
int staleEntriesCount = 0; |
|
|
2422 |
if (table != null) { |
2389 |
if (table != null) { |
2423 |
for (int j =0; j < table.length; j++) { |
2390 |
for (int j =0; j < table.length; j++) { |
2424 |
if (table[j] != null) { |
2391 |
if (table[j] != null) { |
2425 |
boolean remove = false; |
2392 |
boolean potentialLeak = false; |
2426 |
// Check the key |
2393 |
// Check the key |
2427 |
Object key = ((Reference<?>) table[j]).get(); |
2394 |
Object key = ((Reference<?>) table[j]).get(); |
2428 |
if (this.equals(key) || (key != null && |
2395 |
if (this.equals(key) || objectIsLoadedByThisOrChildClassLoader(key)) { |
2429 |
this == key.getClass().getClassLoader())) { |
2396 |
potentialLeak = true; |
2430 |
remove = true; |
|
|
2431 |
} |
2397 |
} |
2432 |
// Check the value |
2398 |
// Check the value |
2433 |
Field valueField = |
2399 |
Field valueField = |
2434 |
table[j].getClass().getDeclaredField("value"); |
2400 |
table[j].getClass().getDeclaredField("value"); |
2435 |
valueField.setAccessible(true); |
2401 |
valueField.setAccessible(true); |
2436 |
Object value = valueField.get(table[j]); |
2402 |
Object value = valueField.get(table[j]); |
2437 |
if (this.equals(value) || (value != null && |
2403 |
if (this.equals(value) || objectIsLoadedByThisOrChildClassLoader(value)) { |
2438 |
this == value.getClass().getClassLoader())) { |
2404 |
potentialLeak = true; |
2439 |
remove = true; |
|
|
2440 |
} |
2405 |
} |
2441 |
if (remove) { |
2406 |
if (potentialLeak) { |
2442 |
Object[] args = new Object[5]; |
2407 |
Object[] args = new Object[5]; |
2443 |
args[0] = contextName; |
2408 |
args[0] = contextName; |
2444 |
if (key != null) { |
2409 |
if (key != null) { |
Lines 2448-2490
Link Here
|
2448 |
if (value != null) { |
2413 |
if (value != null) { |
2449 |
args[3] = value.getClass().getCanonicalName(); |
2414 |
args[3] = value.getClass().getCanonicalName(); |
2450 |
args[4] = value.toString(); |
2415 |
args[4] = value.toString(); |
2451 |
} |
2416 |
log.error(sm.getString( |
2452 |
if (value == null) { |
2417 |
"webappClassLoader.checkThreadLocalsForLeaks", |
|
|
2418 |
args)); |
2419 |
} else { |
2453 |
if (log.isDebugEnabled()) { |
2420 |
if (log.isDebugEnabled()) { |
2454 |
log.debug(sm.getString( |
2421 |
log.debug(sm.getString( |
2455 |
"webappClassLoader.clearThreadLocalDebug", |
2422 |
"webappClassLoader.checkThreadLocalsForLeaksDebug", |
2456 |
args)); |
2423 |
args)); |
2457 |
if (clearReferencesThreadLocals) { |
|
|
2458 |
log.debug(sm.getString( |
2459 |
"webappClassLoader.clearThreadLocalDebugClear")); |
2460 |
} |
2461 |
} |
2424 |
} |
2462 |
} else { |
|
|
2463 |
log.error(sm.getString( |
2464 |
"webappClassLoader.clearThreadLocal", |
2465 |
args)); |
2466 |
if (clearReferencesThreadLocals) { |
2467 |
log.info(sm.getString( |
2468 |
"webappClassLoader.clearThreadLocalClear")); |
2469 |
} |
2470 |
} |
2425 |
} |
2471 |
if (clearReferencesThreadLocals) { |
|
|
2472 |
if (key == null) { |
2473 |
staleEntriesCount++; |
2474 |
} else { |
2475 |
mapRemove.invoke(map, key); |
2476 |
} |
2477 |
} |
2478 |
} |
2426 |
} |
2479 |
} |
2427 |
} |
2480 |
} |
2428 |
} |
2481 |
} |
2429 |
} |
2482 |
if (staleEntriesCount > 0) { |
|
|
2483 |
Method mapRemoveStale = |
2484 |
map.getClass().getDeclaredMethod("expungeStaleEntries"); |
2485 |
mapRemoveStale.setAccessible(true); |
2486 |
mapRemoveStale.invoke(map); |
2487 |
} |
2488 |
} |
2430 |
} |
2489 |
} |
2431 |
} |
2490 |
|
2432 |
|
Lines 2674-2679
Link Here
|
2674 |
} |
2616 |
} |
2675 |
|
2617 |
|
2676 |
|
2618 |
|
|
|
2619 |
private boolean objectIsLoadedByThisOrChildClassLoader(Object o) { |
2620 |
if(o == null) { |
2621 |
return false; |
2622 |
} |
2623 |
|
2624 |
Class<?> clazz = o.getClass(); |
2625 |
if(o instanceof Class) { |
2626 |
clazz = (Class<?>)clazz; |
2627 |
} |
2628 |
|
2629 |
for(ClassLoader cl = clazz.getClassLoader(); cl != null; cl = cl.getParent()) { |
2630 |
if(cl == this) { |
2631 |
return true; |
2632 |
} |
2633 |
} |
2634 |
return false; |
2635 |
} |
2677 |
/** |
2636 |
/** |
2678 |
* Determine whether a class was loaded by this class loader or one of |
2637 |
* Determine whether a class was loaded by this class loader or one of |
2679 |
* its child class loaders. |
2638 |
* its child class loaders. |