7064279: Introspector.getBeanInfo() should release some resources in timely manner
Reviewed-by: art, alexp
--- 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;
}
}