United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: JDK-5102804 Memory leak in Introspector.getBeanInfo(Class) for custom BeanInfo: Class param
JDK-5102804 : Memory leak in Introspector.getBeanInfo(Class) for custom BeanInfo: Class param

Details
Type:
Bug
Submit Date:
2004-09-15
Status:
Resolved
Updated Date:
2010-07-02
Project Name:
JDK
Resolved Date:
2009-12-16
Component:
client-libs
OS:
linux_redhat_9.0,windows
Sub-Component:
java.beans
CPU:
x86
Priority:
P3
Resolution:
Fixed
Affected Versions:
5.0u23-rev,6
Fixed Versions:

Related Reports
Backport:
Backport:
Backport:
Backport:
Backport:
Backport:
Backport:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:

Sub Tasks

Description
Run the following test program:

---%<---
package testintrospectorleak;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.SimpleBeanInfo;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) throws Exception {
        ClassLoader l = new L();
        Class c = Class.forName("testintrospectorleak.Main$B", true, l);
        if (c.getClassLoader() != l) throw new Error("Wrong loader for B!");
        l = null; // release ref
        BeanInfo bi = Introspector.getBeanInfo(c);
        System.out.println("Got BeanInfo " + bi + " for " + c + " with defaultPropertyIndex " + bi.getDefaultPropertyIndex());
        bi = null; // release ref
        Reference r = new WeakReference(c);
        c = null; // release ref
        System.out.println("Class collected? " + collectible(r));
        Introspector.flushCaches();
        System.out.println("Class collected after I.fC? " + collectible(r));
    }
    // Try as hard as possible to collect a reference, even if soft.
    // Copied from org.netbeans.junit.NbTestCase.assertGC.
    private static boolean collectible(Reference ref) {
        List alloc = new ArrayList();
        int size = 100000;
        for (int i = 0; i < 50; i++) {
            if (ref.get() == null) {
                return true;
            }
            System.gc();
            System.runFinalization();
            try {
                alloc.add(new byte[size]);
                size = (int) (((double) size) * 1.3);
            } catch (OutOfMemoryError error) {
                size = size / 2;
            }
            try {
                if (i % 3 == 0) {
                    Thread.sleep(321);
                }
            } catch (InterruptedException t) {}
        }
        return false;
    }
    // Class loader which specifically loads B by itself (does not delegate).
    // Could also load it from a different code source, but this is easier to set up.
    private static final class L extends URLClassLoader {
        public L() {
            super(new URL[] {Main.class.getProtectionDomain().getCodeSource().getLocation()});
        }
        protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class c = findLoadedClass(name);
            if (c == null) {
                if (!name.equals("testintrospectorleak.Main$B") && !name.equals("testintrospectorleak.Main$BBeanInfo")) {
                    try {
                        c = getParent().loadClass(name);
                    } catch (ClassNotFoundException e) {
                        c = findClass(name);
                    }
                } else {
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
    // A simple bean. Loaded from L, not main app class loader.
    public static final class B {
        public B() {}
        public int getX() {return 0;}
        public void setX(int x) {}
    }
    // Its BeanInfo. Again loaded only from L.
    public static final class BBeanInfo extends SimpleBeanInfo {
        public BBeanInfo() {}
        public int getDefaultPropertyIndex() {
            return 0;
        }
        public PropertyDescriptor[] getPropertyDescriptors() {
            try {
                return new PropertyDescriptor[] {
                    new PropertyDescriptor("x", Class.forName("testintrospectorleak.Main$B")),
                };
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }
}
---%<---

Under either JDK 1.4.2_04-b02 or 1.5.0-rc-b63 on Linux, the results are similar:

---%<---
Got BeanInfo java.beans.GenericBeanInfo@757aef for class testintrospectorleak.Main$B with defaultPropertyIndex 0
Class collected? false
Class collected after I.fC? true
---%<---

If you comment out BBeanInfo, recompile (taking care to delete the old Main$BBeanInfo.class!), and run it again, then it works as desired on JDK 1.5:

---%<---
Got BeanInfo java.beans.GenericBeanInfo@757aef for class testintrospectorleak.Main$B with defaultPropertyIndex -1
Class collected? true
Class collected after I.fC? true
---%<---

though not on JDK 1.4.2:

---%<---
Got BeanInfo java.beans.GenericBeanInfo@1be2d65 for class testintrospectorleak.Main$B with defaultPropertyIndex -1
Class collected? false
Class collected after I.fC? true
---%<---

Meaning that the basic case - no custom BeanInfo - was fixed in JDK 1.5, but not the case where there is custom BeanInfo.

Interpretation of results: Introspector's cache (beanInfoCache) uses strong refs as values, meaning that GenericBeanInfo instances are held strongly. With no custom BeanInfo, it seems that all of the GBI's fields refer at most softly to the original Class. However if there is a custom BeanInfo, GBI seems to hold this strongly in the targetBeanInfo field. In that case, the fact that bIC is a WeakHashMap is irrelevant; the key cannot be collected since the value holds it strongly anyway.

Why is this relevant? All sorts of code can call Introspector.getBeanInfo for a variety of reasons, it is pretty common. If some class is loaded from a temporary class loader (e.g. an Ant <taskdef> with nested <classpath>, but many other examples are possible), and it has a specific BeanInfo loaded by I.gBI, then the class will not be collectible. This means the ClassLoader is not collectible either, nor any of the other classes loaded by it, nor their static fields, nor any parent class loaders, etc. This can cause a huge memory leak in some cases. (And on Windows could result in a JAR file lock being held open for the life of the VM.) In particular, it is thought to be the root cause of the NetBeans IDE memory leak bug described at

http://www.netbeans.org/issues/show_bug.cgi?id=46532

Petr Nejedly claims that the bug is reproducible in the stock JDK using an unpatched NetBeans but that if Introspector's cache is patched to not refer strongly to values, it is not reproducible.
###@###.### 2004-09-15

                                    

Comments
WORK AROUND

Call Introspector.flushCaches() whenever in doubt, contrary to its Javadoc which claims

"This method is not normally required.  It is normally only needed by advanced tools that update existing "Class" objects in-place and need to make the Introspector re-analyze existing Class objects."
###@###.### 2004-09-15
                                     
2004-09-15
SUGGESTED FIX

Perhaps make Introspector.GenericBeanInfo hold targetBeanInfo as a SoftReference.
###@###.### 2004-09-15
                                     
2004-09-15
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
mustang


                                     
2004-09-22
EVALUATION

Reproducible as described w/ 1.5, 1.4.2, & 1.4.1, though not with 1.4.0_04.  Perhaps some 1.4.1 change caused this to start happening.  In any case, this should be fixed.
###@###.### 2004-09-20

Description gives a good investigation on what is happening. However, we need to carefully examine the suggested solution - it could be imperfect.
###@###.### 2005-06-06 15:24:02 GMT
                                     
2005-06-06
EVALUATION

WeakHashMap holds reference as a value.
                                     
2009-11-26



Hardware and Software, Engineered to Work Together