Index: java/org/apache/catalina/core/StandardHost.java =================================================================== --- java/org/apache/catalina/core/StandardHost.java (Revision 1124979) +++ java/org/apache/catalina/core/StandardHost.java (Arbeitskopie) @@ -17,12 +17,15 @@ package org.apache.catalina.core; +import java.lang.reflect.Field; + import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.WeakHashMap; import java.util.regex.Pattern; +import java.util.concurrent.ConcurrentHashMap; import org.apache.catalina.Container; import org.apache.catalina.Context; @@ -635,6 +638,8 @@ */ public String[] findReloadedContextMemoryLeaks() { + preventObjectStreamFalsePositives(); + System.gc(); List result = new ArrayList(); @@ -653,6 +658,82 @@ } /** + * This method performs additional cleanup to work around the issue described in LBCORE-205 (Logback JIRA) + * and https://issues.apache.org/bugzilla/show_bug.cgi?id=51195 + * + * The class java.io.ObjectStreamClass, which is used for serialization, has an internal class Caches + * that keeps SoftReferences to classes previously (de)serialized. This causes a semi-leak since it prevents + * unloading of those classes (and their classloader, including all other loaded classes) until memory is + * running really low, forcing the SoftReferences to be collected by the garbage collection. + * + * This, in turn, causes a leak warning in Tomcat 7 leak-detection. + * One could argue that this is a false positive since the webapp will, at some point, be completely collected. + * + * Regardless of this point, the fix below will, beside fixing the warning, relieve stress from the garbage + * collector, since the amount of memory kept by a webapp classloader can be quite significant. + * Freeing up those resources as soon as possible does make sense in any case. + * + * Calling the method below should be quite safe. + * All relevant exceptions (including SecurityException) are handled properly. + * Clearing the maps in Caches is a safe operation, too, since they are instances of ConcurrentMap and clear() + * is performed atomic. Sudden cleanup of those maps would occur "naturally" in case of low memory, anyway. + * + * The only small "downside" will be a little bit of slowdown in case of further (de)serialization directly + * afterwards since the caches will need to be repopulated. + */ + private void preventObjectStreamFalsePositives() { + Throwable t=null; + String className = "java.io.ObjectStreamClass$Caches"; + try { + Class clazz = Class.forName(className); + clearStaticConcurrentHashMap(clazz, "localDescs"); + clearStaticConcurrentHashMap(clazz, "reflectors"); + } catch (ClassNotFoundException e) { + t = e; + } catch (SecurityException e) { + t = e; + } + + if(t != null) { + // ignore + // this would be the right place to add some info that flushing + // did not work out. + // no harm is done, though + } + } + + + private void clearStaticConcurrentHashMap(Class clazz, String fieldName) { + Throwable t=null; + try { + Field field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + Object value = field.get(null); + if(value instanceof ConcurrentHashMap) { + ConcurrentHashMap map = (ConcurrentHashMap) value; + map.clear(); + } else { + // ignore + // this would be the right place to add some info that flushing + // of the field did not work out. + // no harm is done, though + } + } catch (NoSuchFieldException e) { + t = e; + } catch (IllegalAccessException e) { + t = e; + } catch (SecurityException e) { + t = e; + } + if(t != null) { + // ignore + // this would be the right place to add some info that flushing + // of the field did not work out. + // no harm is done, though + } + } + + /** * Return the set of alias names for this Host. If none are defined, * a zero length array is returned. */