Ensure all ProxyAutoBeans not directly referenced from root object or via a shim can be garbage-collected.
Issue 6193.
Patch by: bobv
Suggested by: t.broyer
Review by: rjrjr
Review at http://gwt-code-reviews.appspot.com/1451819
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10344 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/core/client/impl/WeakMapping.java b/user/src/com/google/gwt/core/client/impl/WeakMapping.java
index 435816c..5a7f345 100644
--- a/user/src/com/google/gwt/core/client/impl/WeakMapping.java
+++ b/user/src/com/google/gwt/core/client/impl/WeakMapping.java
@@ -18,8 +18,10 @@
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
-import java.util.HashMap;
import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
/**
* A class associating a (String, Object) map with arbitrary source objects
@@ -88,12 +90,25 @@
}
}
+ private static class ManagedWeakReference<T> extends WeakReference<T> {
+ public ManagedWeakReference(T object) {
+ super(object);
+ }
+ }
+
+ /**
+ * Provides synchronization around {@link #queue} so at most one thread is
+ * cleaning at any given time.
+ */
+ private static final Lock cleanupLock = new ReentrantLock();
+
/**
* A Map from Objects to <String,Object> maps. Hashing is based on object
* identity. Weak references are used to allow otherwise unreferenced Objects
* to be garbage collected.
*/
- private static Map<IdentityWeakReference, Map<String, Object>> map = new HashMap<IdentityWeakReference, Map<String, Object>>();
+ private static final ConcurrentHashMap<IdentityWeakReference, Map<String, Object>> map =
+ new ConcurrentHashMap<IdentityWeakReference, Map<String, Object>>();
/**
* A ReferenceQueue used to clean up the map as its keys are
@@ -109,15 +124,18 @@
* @param key a String key.
* @return an Object associated with that key on the given instance, or null.
*/
- public static synchronized Object get(Object instance, String key) {
+ public static Object get(Object instance, String key) {
cleanup();
-
Object ref = new IdentityWeakReference(instance, queue);
Map<String, Object> m = map.get(ref);
if (m == null) {
return null;
}
- return m.get(key);
+ Object toReturn = m.get(key);
+ if (toReturn instanceof ManagedWeakReference) {
+ toReturn = ((ManagedWeakReference<?>) toReturn).get();
+ }
+ return toReturn;
}
/**
@@ -128,16 +146,15 @@
* <p>
* Due to restrictions of the Production Mode implementation, the instance
* argument must not be a String.
- *
+ *
* @param instance the source Object, which must not be a String.
* @param key a String key.
* @param value the Object to associate with the key on the given source
* Object.
* @throws IllegalArgumentException if instance is a String.
*/
- public static synchronized void set(Object instance, String key, Object value) {
+ public static void set(Object instance, String key, Object value) {
cleanup();
-
if (instance instanceof String) {
throw new IllegalArgumentException("Cannot use Strings with WeakMapping");
}
@@ -145,10 +162,23 @@
IdentityWeakReference ref = new IdentityWeakReference(instance, queue);
Map<String, Object> m = map.get(ref);
if (m == null) {
- m = new HashMap<String, Object>();
- map.put(ref, m);
+ m = new ConcurrentHashMap<String, Object>();
+ map.putIfAbsent(ref, m);
+ m = map.get(ref);
}
- m.put(key, value);
+ if (value == null) {
+ m.remove(key);
+ } else {
+ m.put(key, value);
+ }
+ }
+
+ /**
+ * Like {@link #set(Object, String, Object)}, but doesn't guarantee that
+ * {@code value} can be retrieved.
+ */
+ public static void setWeak(Object instance, String key, Object value) {
+ set(instance, key, new ManagedWeakReference<Object>(value));
}
/**
@@ -157,13 +187,16 @@
* will be eligible for future garbage collection.
*/
private static void cleanup() {
- Reference<? extends Object> ref;
- while ((ref = queue.poll()) != null) {
- /**
- * Note that we can still remove ref from the map even though its referent
- * has been nulled out since we only need == equality to do so.
- */
- map.remove(ref);
+ if (cleanupLock.tryLock()) {
+ Reference<? extends Object> ref;
+ while ((ref = queue.poll()) != null) {
+ /**
+ * Note that we can still remove ref from the map even though its
+ * referent has been nulled out since we only need == equality to do so.
+ */
+ map.remove(ref);
+ }
+ cleanupLock.unlock();
}
}
}
diff --git a/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
index 4e801d9..bc7a599 100644
--- a/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
+++ b/user/src/com/google/web/bindery/autobean/shared/impl/AbstractAutoBean.java
@@ -15,6 +15,7 @@
*/
package com.google.web.bindery.autobean.shared.impl;
+import com.google.gwt.core.client.impl.WeakMapping;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
@@ -23,7 +24,6 @@
import com.google.web.bindery.autobean.shared.Splittable;
import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.Coder;
import com.google.web.bindery.autobean.shared.impl.AutoBeanCodexImpl.EncodeState;
-import com.google.gwt.core.client.impl.WeakMapping;
import java.util.HashMap;
import java.util.HashSet;
@@ -94,7 +94,7 @@
this.wrapped = wrapped;
// Used by AutoBeanUtils
- WeakMapping.set(wrapped, AutoBean.class.getName(), this);
+ WeakMapping.setWeak(wrapped, AutoBean.class.getName(), this);
}
public void accept(AutoBeanVisitor visitor) {
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
index 4ff8d07..913b50d 100644
--- a/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/JsonSplittable.java
@@ -306,10 +306,10 @@
if (seen == null) {
if (object instanceof JSONObject) {
seen = new JsonSplittable((JSONObject) object);
- WeakMapping.set(object, JsonSplittable.class.getName(), seen);
+ WeakMapping.setWeak(object, JsonSplittable.class.getName(), seen);
} else if (object instanceof JSONArray) {
seen = new JsonSplittable((JSONArray) object);
- WeakMapping.set(object, JsonSplittable.class.getName(), seen);
+ WeakMapping.setWeak(object, JsonSplittable.class.getName(), seen);
} else if (object instanceof String) {
seen = new JsonSplittable(object.toString());
} else if (object instanceof Number) {
diff --git a/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
index d52579d..4c37d1f 100644
--- a/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
+++ b/user/src/com/google/web/bindery/autobean/vm/impl/ProxyAutoBean.java
@@ -124,16 +124,23 @@
* ok if it's deallocated, since it has no interesting state.
*
* <pre>
- *
- * ----------------- --------------
- * | ProxyAutoBean | | Shim |
- * | | <------+-bean |
- * | shim-+---X--->|____________|
- * |_______________| ^
- * ^ |
- * | X
- * |______ WeakMapping ___|
+ * _________________ ______________
+ * | ProxyAutoBean | | Shim |
+ * | | <----------+-bean |
+ * | shim-+---X------> | |
+ * |_______________| |____________|
+ * ^ ^ ^
+ * X X |
+ * |__value__WeakMapping__key__| |
+ * ^ |
+ * | |
+ * GC Roots -> Owner____|
* </pre>
+ * <p>
+ * In the case of a wrapped object (for example, an ArrayList), the weak
+ * reference from WeakMapping to the ProxyAutoBean may cause the AutoBean to
+ * be prematurely collected if neither the bean nor the shim are referenced
+ * elsewhere. The alternative is a massive memory leak.
*/
private WeakReference<T> shim;
@@ -332,7 +339,7 @@
private T createShim() {
T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this, getWrapped()));
- WeakMapping.set(toReturn, AutoBean.class.getName(), this);
+ WeakMapping.setWeak(toReturn, AutoBean.class.getName(), this);
return toReturn;
}
}
diff --git a/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/WeakMapping.java b/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/WeakMapping.java
index 35500be..ee701d0 100644
--- a/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/WeakMapping.java
+++ b/user/super/com/google/gwt/core/translatable/com/google/gwt/core/client/impl/WeakMapping.java
@@ -59,6 +59,10 @@
setNative(instance, key, value);
}
+ public static void setWeak(Object instance, String key, Object value) {
+ set(instance, key, value);
+ }
+
private static native void setNative(Object instance, String key, Object value) /*-{
if (!instance.@java.lang.Object::expando) {
instance.@java.lang.Object::expando = {};