7064279: Introspector.getBeanInfo() should release some resources in timely manner
authormalenkov
Thu, 10 Nov 2011 17:27:40 +0400
changeset 11086 09b1d6383d0b
parent 11085 18d8201c795b
child 11087 3217e8ad1d4a
7064279: Introspector.getBeanInfo() should release some resources in timely manner Reviewed-by: art, alexp
jdk/src/share/classes/java/beans/Beans.java
jdk/src/share/classes/java/beans/Introspector.java
jdk/src/share/classes/java/beans/PropertyEditorManager.java
jdk/src/share/classes/java/beans/ThreadGroupContext.java
jdk/test/java/beans/Beans/6669869/TestDesignTime.java
jdk/test/java/beans/Beans/6669869/TestGuiAvailable.java
jdk/test/java/beans/Introspector/6380849/TestBeanInfo.java
jdk/test/java/beans/Introspector/7064279/Test7064279.java
jdk/test/java/beans/Introspector/7064279/test.jar
jdk/test/java/beans/Introspector/Test6660539.java
jdk/test/java/beans/PropertyEditor/6380849/TestPropertyEditor.java
--- a/jdk/src/share/classes/java/beans/Beans.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/src/share/classes/java/beans/Beans.java	Thu Nov 10 17:27:40 2011 +0400
@@ -32,7 +32,6 @@
 import java.applet.AppletStub;
 import java.applet.AudioClip;
 
-import java.awt.GraphicsEnvironment;
 import java.awt.Image;
 
 import java.beans.beancontext.BeanContext;
@@ -53,15 +52,11 @@
 import java.util.Iterator;
 import java.util.Vector;
 
-import sun.awt.AppContext;
-
 /**
  * This class provides some general purpose beans control methods.
  */
 
 public class Beans {
-    private static final Object DESIGN_TIME = new Object();
-    private static final Object GUI_AVAILABLE = new Object();
 
     /**
      * <p>
@@ -395,8 +390,7 @@
      * @see DesignMode
      */
     public static boolean isDesignTime() {
-        Object value = AppContext.getAppContext().get(DESIGN_TIME);
-        return (value instanceof Boolean) && (Boolean) value;
+        return ThreadGroupContext.getContext().isDesignTime();
     }
 
     /**
@@ -413,8 +407,7 @@
      *
      */
     public static boolean isGuiAvailable() {
-        Object value = AppContext.getAppContext().get(GUI_AVAILABLE);
-        return (value instanceof Boolean) ? (Boolean) value : !GraphicsEnvironment.isHeadless();
+        return ThreadGroupContext.getContext().isGuiAvailable();
     }
 
     /**
@@ -440,7 +433,7 @@
         if (sm != null) {
             sm.checkPropertiesAccess();
         }
-        AppContext.getAppContext().put(DESIGN_TIME, Boolean.valueOf(isDesignTime));
+        ThreadGroupContext.getContext().setDesignTime(isDesignTime);
     }
 
     /**
@@ -466,7 +459,7 @@
         if (sm != null) {
             sm.checkPropertiesAccess();
         }
-        AppContext.getAppContext().put(GUI_AVAILABLE, Boolean.valueOf(isGuiAvailable));
+        ThreadGroupContext.getContext().setGuiAvailable(isGuiAvailable);
     }
 }
 
--- a/jdk/src/share/classes/java/beans/Introspector.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/src/share/classes/java/beans/Introspector.java	Thu Nov 10 17:27:40 2011 +0400
@@ -26,7 +26,6 @@
 package java.beans;
 
 import com.sun.beans.WeakCache;
-import com.sun.beans.finder.BeanInfoFinder;
 import com.sun.beans.finder.ClassFinder;
 
 import java.awt.Component;
@@ -44,9 +43,7 @@
 import java.util.EventObject;
 import java.util.List;
 import java.util.TreeMap;
-import java.util.WeakHashMap;
 
-import sun.awt.AppContext;
 import sun.reflect.misc.ReflectUtil;
 
 /**
@@ -98,10 +95,7 @@
     public final static int IGNORE_ALL_BEANINFO        = 3;
 
     // Static Caches to speed up introspection.
-    private static WeakCache<Class<?>, Method[]> declaredMethodCache =
-            new WeakCache<Class<?>, Method[]>();
-
-    private static final Object BEANINFO_CACHE = new Object();
+    private static final WeakCache<Class<?>, Method[]> declaredMethodCache = new WeakCache<>();
 
     private Class beanClass;
     private BeanInfo explicitBeanInfo;
@@ -134,8 +128,6 @@
     static final String SET_PREFIX = "set";
     static final String IS_PREFIX = "is";
 
-    private static final Object FINDER_KEY = new Object();
-
     //======================================================================
     //                          Public methods
     //======================================================================
@@ -160,20 +152,15 @@
         if (!ReflectUtil.isPackageAccessible(beanClass)) {
             return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
         }
-        Map<Class<?>, BeanInfo> beanInfoCache;
+        ThreadGroupContext context = ThreadGroupContext.getContext();
         BeanInfo beanInfo;
-        synchronized (BEANINFO_CACHE) {
-            beanInfoCache = (Map<Class<?>, BeanInfo>) AppContext.getAppContext().get(BEANINFO_CACHE);
-            if (beanInfoCache == null) {
-                beanInfoCache = new WeakHashMap<Class<?>, BeanInfo>();
-                AppContext.getAppContext().put(BEANINFO_CACHE, beanInfoCache);
-            }
-            beanInfo = beanInfoCache.get(beanClass);
+        synchronized (declaredMethodCache) {
+            beanInfo = context.getBeanInfo(beanClass);
         }
         if (beanInfo == null) {
             beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
-            synchronized (BEANINFO_CACHE) {
-                beanInfoCache.put(beanClass, beanInfo);
+            synchronized (declaredMethodCache) {
+                context.putBeanInfo(beanClass, beanInfo);
             }
         }
         return beanInfo;
@@ -306,7 +293,7 @@
      */
 
     public static String[] getBeanInfoSearchPath() {
-        return getFinder().getPackages();
+        return ThreadGroupContext.getContext().getBeanInfoFinder().getPackages();
     }
 
     /**
@@ -330,7 +317,7 @@
         if (sm != null) {
             sm.checkPropertiesAccess();
         }
-        getFinder().setPackages(path);
+        ThreadGroupContext.getContext().getBeanInfoFinder().setPackages(path);
     }
 
 
@@ -342,11 +329,8 @@
      */
 
     public static void flushCaches() {
-        synchronized (BEANINFO_CACHE) {
-            Map beanInfoCache = (Map) AppContext.getAppContext().get(BEANINFO_CACHE);
-            if (beanInfoCache != null) {
-                beanInfoCache.clear();
-            }
+        synchronized (declaredMethodCache) {
+            ThreadGroupContext.getContext().clearBeanInfoCache();
             declaredMethodCache.clear();
         }
     }
@@ -370,11 +354,8 @@
         if (clz == null) {
             throw new NullPointerException();
         }
-        synchronized (BEANINFO_CACHE) {
-            Map beanInfoCache = (Map) AppContext.getAppContext().get(BEANINFO_CACHE);
-            if (beanInfoCache != null) {
-                beanInfoCache.put(clz, null);
-            }
+        synchronized (declaredMethodCache) {
+            ThreadGroupContext.getContext().removeBeanInfo(clz);
             declaredMethodCache.put(clz, null);
         }
     }
@@ -452,7 +433,7 @@
      * @return Instance of an explicit BeanInfo class or null if one isn't found.
      */
     private static BeanInfo findExplicitBeanInfo(Class beanClass) {
-        return getFinder().find(beanClass);
+        return ThreadGroupContext.getContext().getBeanInfoFinder().find(beanClass);
     }
 
     /**
@@ -1275,7 +1256,7 @@
         if (!ReflectUtil.isPackageAccessible(clz)) {
             return new Method[0];
         }
-        synchronized (BEANINFO_CACHE) {
+        synchronized (declaredMethodCache) {
             Method[] result = declaredMethodCache.get(clz);
             if (result == null) {
                 result = clz.getMethods();
@@ -1426,17 +1407,6 @@
         return false;
     }
 
-    private static BeanInfoFinder getFinder() {
-        AppContext context = AppContext.getAppContext();
-        Object object = context.get(FINDER_KEY);
-        if (object instanceof BeanInfoFinder) {
-            return (BeanInfoFinder) object;
-        }
-        BeanInfoFinder finder = new BeanInfoFinder();
-        context.put(FINDER_KEY, finder);
-        return finder;
-    }
-
     /**
      * Try to create an instance of a named class.
      * First try the classloader of "sibling", then try the system
--- a/jdk/src/share/classes/java/beans/PropertyEditorManager.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/src/share/classes/java/beans/PropertyEditorManager.java	Thu Nov 10 17:27:40 2011 +0400
@@ -25,9 +25,6 @@
 
 package java.beans;
 
-import com.sun.beans.finder.PropertyEditorFinder;
-import sun.awt.AppContext;
-
 /**
  * The PropertyEditorManager can be used to locate a property editor for
  * any given type name.  This property editor must support the
@@ -55,8 +52,6 @@
 
 public class PropertyEditorManager {
 
-    private static final Object FINDER_KEY = new Object();
-
     /**
      * Registers an editor class to edit values of the given target class.
      * If the editor class is {@code null},
@@ -81,7 +76,7 @@
         if (sm != null) {
             sm.checkPropertiesAccess();
         }
-        getFinder().register(targetType, editorClass);
+        ThreadGroupContext.getContext().getPropertyEditorFinder().register(targetType, editorClass);
     }
 
     /**
@@ -92,7 +87,7 @@
      * The result is null if no suitable editor can be found.
      */
     public static PropertyEditor findEditor(Class<?> targetType) {
-        return getFinder().find(targetType);
+        return ThreadGroupContext.getContext().getPropertyEditorFinder().find(targetType);
     }
 
     /**
@@ -104,7 +99,7 @@
      *         e.g. Sun implementation initially sets to  {"sun.beans.editors"}.
      */
     public static String[] getEditorSearchPath() {
-        return getFinder().getPackages();
+        return ThreadGroupContext.getContext().getPropertyEditorFinder().getPackages();
     }
 
     /**
@@ -125,17 +120,6 @@
         if (sm != null) {
             sm.checkPropertiesAccess();
         }
-        getFinder().setPackages(path);
-    }
-
-    private static PropertyEditorFinder getFinder() {
-        AppContext context = AppContext.getAppContext();
-        Object object = context.get(FINDER_KEY);
-        if (object instanceof PropertyEditorFinder) {
-            return (PropertyEditorFinder) object;
-        }
-        PropertyEditorFinder finder = new PropertyEditorFinder();
-        context.put(FINDER_KEY, finder);
-        return finder;
+        ThreadGroupContext.getContext().getPropertyEditorFinder().setPackages(path);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/java/beans/ThreadGroupContext.java	Thu Nov 10 17:27:40 2011 +0400
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package java.beans;
+
+import com.sun.beans.finder.BeanInfoFinder;
+import com.sun.beans.finder.PropertyEditorFinder;
+
+import java.awt.GraphicsEnvironment;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+/**
+ * The {@code ThreadGroupContext} is an application-dependent
+ * context referenced by the specific {@link ThreadGroup}.
+ * This is a replacement for the {@link sun.awt.AppContext}.
+ *
+ * @author  Sergey Malenkov
+ */
+final class ThreadGroupContext {
+
+    private static final Map<ThreadGroup, ThreadGroupContext> contexts = new WeakHashMap<>();
+
+    /**
+     * Returns the appropriate {@code AppContext} for the caller,
+     * as determined by its {@code ThreadGroup}.
+     *
+     * @return  the application-dependent context
+     */
+    static ThreadGroupContext getContext() {
+        ThreadGroup group = Thread.currentThread().getThreadGroup();
+        synchronized (contexts) {
+            ThreadGroupContext context = contexts.get(group);
+            if (context == null) {
+                context = new ThreadGroupContext();
+                contexts.put(group, context);
+            }
+            return context;
+        }
+    }
+
+    private volatile boolean isDesignTime;
+    private volatile Boolean isGuiAvailable;
+
+    private Map<Class<?>, BeanInfo> beanInfoCache;
+    private BeanInfoFinder beanInfoFinder;
+    private PropertyEditorFinder propertyEditorFinder;
+
+
+    boolean isDesignTime() {
+        return this.isDesignTime;
+    }
+
+    void setDesignTime(boolean isDesignTime) {
+        this.isDesignTime = isDesignTime;
+    }
+
+
+    boolean isGuiAvailable() {
+        Boolean isGuiAvailable = this.isGuiAvailable;
+        return (isGuiAvailable != null)
+                ? isGuiAvailable.booleanValue()
+                : !GraphicsEnvironment.isHeadless();
+    }
+
+    void setGuiAvailable(boolean isGuiAvailable) {
+        this.isGuiAvailable = Boolean.valueOf(isGuiAvailable);
+    }
+
+
+    BeanInfo getBeanInfo(Class<?> type) {
+        return (this.beanInfoCache != null)
+                ? this.beanInfoCache.get(type)
+                : null;
+    }
+
+    BeanInfo putBeanInfo(Class<?> type, BeanInfo info) {
+        if (this.beanInfoCache == null) {
+            this.beanInfoCache = new WeakHashMap<>();
+        }
+        return this.beanInfoCache.put(type, info);
+    }
+
+    void removeBeanInfo(Class<?> type) {
+        if (this.beanInfoCache != null) {
+            this.beanInfoCache.remove(type);
+        }
+    }
+
+    void clearBeanInfoCache() {
+        if (this.beanInfoCache != null) {
+            this.beanInfoCache.clear();
+        }
+    }
+
+
+    synchronized BeanInfoFinder getBeanInfoFinder() {
+        if (this.beanInfoFinder == null) {
+            this.beanInfoFinder = new BeanInfoFinder();
+        }
+        return this.beanInfoFinder;
+    }
+
+    synchronized PropertyEditorFinder getPropertyEditorFinder() {
+        if (this.propertyEditorFinder == null) {
+            this.propertyEditorFinder = new PropertyEditorFinder();
+        }
+        return this.propertyEditorFinder;
+    }
+}
--- a/jdk/test/java/beans/Beans/6669869/TestDesignTime.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/test/java/beans/Beans/6669869/TestDesignTime.java	Thu Nov 10 17:27:40 2011 +0400
@@ -29,7 +29,6 @@
  */
 
 import java.beans.Beans;
-import sun.awt.SunToolkit;
 
 public class TestDesignTime implements Runnable {
     public static void main(String[] args) throws InterruptedException {
@@ -44,7 +43,6 @@
     }
 
     public void run() {
-        SunToolkit.createNewAppContext();
         if (Beans.isDesignTime()) {
             throw new Error("shared DesignTime property");
         }
--- a/jdk/test/java/beans/Beans/6669869/TestGuiAvailable.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/test/java/beans/Beans/6669869/TestGuiAvailable.java	Thu Nov 10 17:27:40 2011 +0400
@@ -30,7 +30,6 @@
 
 import java.awt.GraphicsEnvironment;
 import java.beans.Beans;
-import sun.awt.SunToolkit;
 
 public class TestGuiAvailable implements Runnable {
     public static void main(String[] args) throws InterruptedException {
@@ -45,7 +44,6 @@
     }
 
     public void run() {
-        SunToolkit.createNewAppContext();
         if (Beans.isGuiAvailable() == GraphicsEnvironment.isHeadless()) {
             throw new Error("shared GuiAvailable property");
         }
--- a/jdk/test/java/beans/Introspector/6380849/TestBeanInfo.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/test/java/beans/Introspector/6380849/TestBeanInfo.java	Thu Nov 10 17:27:40 2011 +0400
@@ -41,8 +41,6 @@
 import java.lang.ref.Reference;
 import java.lang.reflect.Field;
 
-import sun.awt.SunToolkit;
-
 public class TestBeanInfo implements Runnable {
 
     private static final String[] SEARCH_PATH = { "infos" }; // NON-NLS: package name
@@ -81,9 +79,6 @@
     private boolean passed;
 
     public void run() {
-        if (this.passed) {
-            SunToolkit.createNewAppContext();
-        }
         Introspector.flushCaches();
 
         test(FirstBean.class, FirstBeanBeanInfo.class);
@@ -98,7 +93,5 @@
         test(SecondBean.class, SecondBeanBeanInfo.class);
         test(ThirdBean.class, null);
         test(ThirdBeanBeanInfo.class, ThirdBeanBeanInfo.class);
-
-        this.passed = true;
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/beans/Introspector/7064279/Test7064279.java	Thu Nov 10 17:27:40 2011 +0400
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 7064279
+ * @summary Tests that Introspector does not have strong references to context class loader
+ * @author Sergey Malenkov
+ */
+
+import java.beans.Introspector;
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.net.URLClassLoader;
+
+public class Test7064279 {
+
+    public static void main(String[] args) throws Exception {
+        WeakReference ref = new WeakReference(test("test.jar", "test.Test"));
+        try {
+            int[] array = new int[1024];
+            while (true) {
+                array = new int[array.length << 1];
+            }
+        }
+        catch (OutOfMemoryError error) {
+            System.gc();
+        }
+        if (null != ref.get()) {
+            throw new Error("ClassLoader is not released");
+        }
+    }
+
+    private static Object test(String jarName, String className) throws Exception {
+        StringBuilder sb = new StringBuilder(256);
+        sb.append("file:");
+        sb.append(System.getProperty("test.src", "."));
+        sb.append(File.separatorChar);
+        sb.append(jarName);
+
+        ClassLoader newLoader = new URLClassLoader(new URL[] { new URL(sb.toString()) });
+        ClassLoader oldLoader = Thread.currentThread().getContextClassLoader();
+
+        Thread.currentThread().setContextClassLoader(newLoader);
+        test(newLoader.loadClass(className));
+        Thread.currentThread().setContextClassLoader(oldLoader);
+
+        return newLoader;
+    }
+
+    private static void test(Class type) throws Exception {
+        Introspector.getBeanInfo(type);
+    }
+}
Binary file jdk/test/java/beans/Introspector/7064279/test.jar has changed
--- a/jdk/test/java/beans/Introspector/Test6660539.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/test/java/beans/Introspector/Test6660539.java	Thu Nov 10 17:27:40 2011 +0400
@@ -28,8 +28,6 @@
  * @author Sergey Malenkov
  */
 
-import sun.awt.SunToolkit;
-
 import java.beans.BeanInfo;
 import java.beans.IntrospectionException;
 import java.beans.Introspector;
@@ -49,7 +47,6 @@
     }
 
     public void run() {
-        SunToolkit.createNewAppContext();
         for (PropertyDescriptor pd : getPropertyDescriptors()) {
             if (pd.getDisplayName().equals(NAME))
                 throw new Error("shared BeanInfo cache");
--- a/jdk/test/java/beans/PropertyEditor/6380849/TestPropertyEditor.java	Thu Nov 10 17:15:15 2011 +0400
+++ b/jdk/test/java/beans/PropertyEditor/6380849/TestPropertyEditor.java	Thu Nov 10 17:27:40 2011 +0400
@@ -36,7 +36,6 @@
 import java.beans.PropertyEditor;
 import java.beans.PropertyEditorManager;
 
-import sun.awt.SunToolkit;
 import sun.beans.editors.BooleanEditor;
 import sun.beans.editors.ByteEditor;
 import sun.beans.editors.ColorEditor;
@@ -77,12 +76,7 @@
         }
     }
 
-    private boolean passed;
-
     public void run() {
-        if (this.passed) {
-            SunToolkit.createNewAppContext();
-        }
         PropertyEditorManager.registerEditor(ThirdBean.class, ThirdBeanEditor.class);
 
         test(FirstBean.class, FirstBeanEditor.class);
@@ -135,7 +129,5 @@
         test(Color.class, null);
         test(Font.class, null);
         test(Enumeration.class, EnumEditor.class);
-
-        this.passed = true;
     }
 }