View | Details | Raw Unified | Return to bug 51195
Collapse All | Expand All

(-)java/org/apache/catalina/core/StandardHost.java (+81 lines)
Lines 17-28 Link Here
17
package org.apache.catalina.core;
17
package org.apache.catalina.core;
18
18
19
19
20
import java.lang.reflect.Field;
21
20
import java.util.ArrayList;
22
import java.util.ArrayList;
21
import java.util.List;
23
import java.util.List;
22
import java.util.Locale;
24
import java.util.Locale;
23
import java.util.Map;
25
import java.util.Map;
24
import java.util.WeakHashMap;
26
import java.util.WeakHashMap;
25
import java.util.regex.Pattern;
27
import java.util.regex.Pattern;
28
import java.util.concurrent.ConcurrentHashMap;
26
29
27
import org.apache.catalina.Container;
30
import org.apache.catalina.Container;
28
import org.apache.catalina.Context;
31
import org.apache.catalina.Context;
Lines 635-640 Link Here
635
     */
638
     */
636
    public String[] findReloadedContextMemoryLeaks() {
639
    public String[] findReloadedContextMemoryLeaks() {
637
        
640
        
641
        preventObjectStreamFalsePositives();
642
        
638
        System.gc();
643
        System.gc();
639
        
644
        
640
        List<String> result = new ArrayList<String>();
645
        List<String> result = new ArrayList<String>();
Lines 653-658 Link Here
653
    }
658
    }
654
659
655
    /**
660
    /**
661
     * This method performs additional cleanup to work around the issue described in LBCORE-205 (Logback JIRA)
662
     * and https://issues.apache.org/bugzilla/show_bug.cgi?id=51195
663
     *
664
     * The class java.io.ObjectStreamClass, which is used for serialization, has an internal class Caches
665
     * that keeps SoftReferences to classes previously (de)serialized. This causes a semi-leak since it prevents
666
     * unloading of those classes (and their classloader, including all other loaded classes) until memory is
667
     * running really low, forcing the SoftReferences to be collected by the garbage collection.
668
     *
669
     * This, in turn, causes a leak warning in Tomcat 7 leak-detection.
670
     * One could argue that this is a false positive since the webapp will, at some point, be completely collected.
671
     *
672
     * Regardless of this point, the fix below will, beside fixing the warning, relieve stress from the garbage
673
     * collector, since the amount of memory kept by a webapp classloader can be quite significant.
674
     * Freeing up those resources as soon as possible does make sense in any case.
675
     *
676
     * Calling the method below should be quite safe.
677
     * All relevant exceptions (including SecurityException) are handled properly.
678
     * Clearing the maps in Caches is a safe operation, too, since they are instances of ConcurrentMap and clear()
679
     * is performed atomic. Sudden cleanup of those maps would occur "naturally" in case of low memory, anyway.
680
     *
681
     * The only small "downside" will be a little bit of slowdown in case of further (de)serialization directly
682
     * afterwards since the caches will need to be repopulated.
683
     */
684
    private void preventObjectStreamFalsePositives() {
685
        Throwable t=null;
686
        String className = "java.io.ObjectStreamClass$Caches";
687
        try {
688
            Class clazz = Class.forName(className);
689
            clearStaticConcurrentHashMap(clazz, "localDescs");
690
            clearStaticConcurrentHashMap(clazz, "reflectors");
691
        } catch (ClassNotFoundException e) {
692
            t = e;
693
        } catch (SecurityException e) {
694
            t = e;
695
        }
696
        
697
        if(t != null) {
698
            // ignore
699
            // this would be the right place to add some info that flushing
700
            // did not work out.
701
            // no harm is done, though
702
        }
703
    }
704
    
705
    
706
    private void clearStaticConcurrentHashMap(Class clazz, String fieldName) {
707
        Throwable t=null;
708
        try {
709
            Field field = clazz.getDeclaredField(fieldName);
710
            field.setAccessible(true);
711
            Object value = field.get(null);
712
            if(value instanceof ConcurrentHashMap) {
713
                ConcurrentHashMap map = (ConcurrentHashMap) value;
714
                map.clear();
715
            } else {
716
                // ignore
717
                // this would be the right place to add some info that flushing
718
                // of the field did not work out.
719
                // no harm is done, though
720
            }
721
        } catch (NoSuchFieldException e) {
722
            t = e;
723
        } catch (IllegalAccessException e) {
724
            t = e;
725
        } catch (SecurityException e) {
726
            t = e;
727
        }
728
        if(t != null) {
729
            // ignore
730
            // this would be the right place to add some info that flushing
731
            // of the field did not work out.
732
            // no harm is done, though
733
        }
734
    }
735
736
    /**
656
     * Return the set of alias names for this Host.  If none are defined,
737
     * Return the set of alias names for this Host.  If none are defined,
657
     * a zero length array is returned.
738
     * a zero length array is returned.
658
     */
739
     */

Return to bug 51195