You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by gd...@apache.org on 2005/10/24 23:29:13 UTC
svn commit: r328153 - in /geronimo/trunk/modules/kernel/src:
java/org/apache/geronimo/kernel/basic/ test/org/apache/geronimo/kernel/basic/
Author: gdamour
Date: Mon Oct 24 14:29:04 2005
New Revision: 328153
URL: http://svn.apache.org/viewcvs?rev=328153&view=rev
Log:
GERONIMO-983 Memory used by proxies isn't released by dereferencing the
proxy (I speculate)
Patch submitted by Kevan Miller. Geir has confirmed that the borrowed code
from commons-collection is properly identified as such.
Added:
geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyMap.java
geronimo/trunk/modules/kernel/src/test/org/apache/geronimo/kernel/basic/
geronimo/trunk/modules/kernel/src/test/org/apache/geronimo/kernel/basic/BasicProxyMapTest.java
Modified:
geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyManager.java
Modified: geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyManager.java
URL: http://svn.apache.org/viewcvs/geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyManager.java?rev=328153&r1=328152&r2=328153&view=diff
==============================================================================
--- geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyManager.java (original)
+++ geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyManager.java Mon Oct 24 14:29:04 2005
@@ -45,8 +45,7 @@
private final static Log log = LogFactory.getLog(BasicProxyManager.class);
private final Kernel kernel;
- // todo use weak keys for this
- private final IdentityHashMap interceptors = new IdentityHashMap();
+ private final BasicProxyMap interceptors = new BasicProxyMap();
public BasicProxyManager(Kernel kernel) {
this.kernel = kernel;
Added: geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyMap.java
URL: http://svn.apache.org/viewcvs/geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyMap.java?rev=328153&view=auto
==============================================================================
--- geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyMap.java (added)
+++ geronimo/trunk/modules/kernel/src/java/org/apache/geronimo/kernel/basic/BasicProxyMap.java Mon Oct 24 14:29:04 2005
@@ -0,0 +1,485 @@
+/**
+ *
+ * Copyright 2002-2005 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.geronimo.kernel.basic;
+
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
+import java.lang.ref.ReferenceQueue;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Streamlined version of a WeakIdentityHashMap. Provides Identity semantics with
+ * Weak References to keys. This allows proxies to be GC'ed when no longer referenced
+ * by clients. <code>BasicProxymanager.destroyProxy()</code> need not be invoked when a
+ * proxy is no longer needed. Note that this is not a full Map implementation.
+ * The iteration and collection capabilities of Map have been discarded to keep the
+ * implementation lightweight.
+ * <p>
+ * Much of this code was cribbed from the Commons Collection 3.1 implementation of
+ * <code>ReferenceIdentityMap</code> and <code>AbstractReferenceMap</code>.
+ */
+public class BasicProxyMap implements Map {
+
+ /** The default capacity to use. Always use a power of 2!!! */
+ private static final int DEFAULT_CAPACITY = 16;
+ /** The default load factor to use */
+ private static final float DEFAULT_LOAD_FACTOR = 0.75f;
+ /** The maximum capacity allowed */
+ private static final int MAXIMUM_CAPACITY = 1 << 30;
+
+ /** Load factor, normally 0.75 */
+ private float loadFactor;
+ /** The size of the map */
+ private transient int size;
+ /** Map entries */
+ private transient ReferenceEntry[] data;
+ /** Size at which to rehash */
+ private transient int threshold;
+
+ /**
+ * ReferenceQueue used to eliminate GC'ed entries.
+ */
+ private ReferenceQueue purgeQueue;
+
+ public BasicProxyMap() {
+ this.loadFactor = DEFAULT_LOAD_FACTOR;
+ this.data = new ReferenceEntry[DEFAULT_CAPACITY];
+ this.threshold = calculateThreshold(DEFAULT_CAPACITY, loadFactor);
+ this.purgeQueue = new ReferenceQueue();
+ }
+
+ /**
+ * Gets the size of the map.
+ *
+ * @return the size
+ */
+ public int size() {
+ purge();
+ return size;
+ }
+
+ /**
+ * Checks whether the map is currently empty.
+ *
+ * @return true if the map is currently size zero
+ */
+ public boolean isEmpty() {
+ purge();
+ return (size == 0);
+ }
+
+ /**
+ * Checks whether the map contains the specified key.
+ *
+ * @param key the key to search for
+ * @return true if the map contains the key
+ */
+ public boolean containsKey(Object key) {
+ purge();
+ ReferenceEntry entry = getEntry(key);
+ if (entry == null) {
+ return false;
+ }
+ return (entry.getValue() != null);
+ }
+
+ /**
+ * Checks whether the map contains the specified value.
+ *
+ * @param value the value to search for
+ * @return true if the map contains the value
+ */
+ public boolean containsValue(Object value) {
+ purge();
+ if (value == null || size == 0) {
+ return false;
+ }
+ ReferenceEntry [] table = data;
+ for (int i = 0; i < table.length; i++) {
+ ReferenceEntry entry = table[i];
+ while (entry != null) {
+ if (value.equals(entry.getValue())) {
+ return true;
+ }
+ entry = entry.next;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Gets the value mapped to the key specified.
+ *
+ * @param key the key
+ * @return the mapped value, null if no match
+ */
+ public Object get(Object key) {
+ purge();
+ ReferenceEntry entry = getEntry(key);
+ if (entry == null) {
+ return null;
+ }
+ return entry.getValue();
+ }
+
+
+ /**
+ * Puts a key-value entry into this map.
+ * Neither the key nor the value may be null.
+ *
+ * @param key the key to add, must not be null
+ * @param value the value to add, must not be null
+ * @return the value previously mapped to this key, null if none
+ */
+ public Object put(Object key, Object value) {
+ assert key != null: "key is null";
+ assert value != null: "value is null";
+
+ purge();
+
+ int hashCode = hash(key);
+ int index = hashIndex(hashCode, data.length);
+ ReferenceEntry entry = data[index];
+ while (entry != null) {
+ if (entry.hashCode == hashCode && key == entry.getKey()) {
+ return entry.setValue(value);
+ }
+ entry = entry.next;
+ }
+
+ createEntry(index, hashCode, key, value);
+ return null;
+ }
+
+ /**
+ * Removes the specified mapping from this map.
+ *
+ * @param key the mapping to remove
+ * @return the value mapped to the removed key, null if key not in map
+ */
+ public Object remove(Object key) {
+ if (key == null) {
+ return null;
+ }
+ purge();
+ int hashCode = hash(key);
+ int index = hashIndex(hashCode, data.length);
+ ReferenceEntry entry = data[index];
+ ReferenceEntry previous = null;
+ while (entry != null) {
+ if (entry.hashCode == hashCode && (key == entry.getKey())) {
+ Object oldValue = entry.getValue();
+ removeEntry(entry, index, previous);
+ return oldValue;
+ }
+ previous = entry;
+ entry = entry.next;
+ }
+ return null;
+ }
+
+ /**
+ * Clears the map, resetting the size to zero and nullifying references
+ * to avoid garbage collection issues.
+ */
+ public void clear() {
+ ReferenceEntry[] data = this.data;
+ for (int i = data.length - 1; i >= 0; i--) {
+ data[i] = null;
+ }
+ size = 0;
+ while (purgeQueue.poll() != null) {} // drain the queue
+ }
+
+ public Collection values() {
+ throw new UnsupportedOperationException();
+ }
+
+ public void putAll(Map t) {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set entrySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Set keySet() {
+ throw new UnsupportedOperationException();
+ }
+
+ // end of public methods
+
+ /**
+ * Gets the entry mapped to the key specified.
+ * @param key the key
+ * @return the entry, null if no match
+ */
+ private ReferenceEntry getEntry(Object key) {
+ if (key == null) {
+ return null;
+ }
+ int hashCode = hash(key);
+ ReferenceEntry entry = data[hashIndex(hashCode, data.length)];
+ while (entry != null) {
+ if (entry.hashCode == hashCode && (key == entry.getKey())) {
+ return entry;
+ }
+ entry = entry.next;
+ }
+ return null;
+ }
+
+ /**
+ * Creates a new ReferenceEntry.
+ *
+ * @param index the index into the data map
+ * @param hashCode the hash code for the new entry
+ * @param key the key to store
+ * @param value the value to store
+ * @return the newly created entry
+ */
+ private ReferenceEntry createEntry(int index, int hashCode, Object key, Object value) {
+ ReferenceEntry newEntry = new ReferenceEntry(this, data[index], hashCode, key, value);
+ data[index] = newEntry;
+ size++;
+ checkCapacity();
+ return newEntry;
+ }
+
+ /**
+ * Removes an entry from the chain stored in a particular index.
+ * <p>
+ * This implementation removes the entry from the data storage table.
+ * The size is not updated.
+ *
+ * @param entry the entry to remove
+ * @param hashIndex the index into the data structure
+ * @param previous the previous entry in the chain
+ */
+ private void removeEntry(ReferenceEntry entry, int hashIndex, ReferenceEntry previous) {
+ if (previous == null) {
+ data[hashIndex] = entry.next;
+ } else {
+ previous.next = entry.next;
+ }
+ size--;
+ entry.next = null;
+ entry.clear();
+ entry.value = null;
+ }
+
+ /**
+ * Checks the capacity of the map and enlarges it if necessary.
+ * <p>
+ * This implementation uses the threshold to check if the map needs enlarging
+ */
+ private void checkCapacity() {
+ if (size >= threshold) {
+ int newCapacity = data.length * 2;
+ if (newCapacity <= MAXIMUM_CAPACITY) {
+ ensureCapacity(newCapacity);
+ }
+ }
+ }
+
+ /**
+ * Changes the size of the data structure to the capacity proposed.
+ *
+ * @param newCapacity the new capacity of the array (a power of two, less or equal to max)
+ */
+ private void ensureCapacity(int newCapacity) {
+ int oldCapacity = data.length;
+ if (newCapacity <= oldCapacity) {
+ return;
+ }
+
+ ReferenceEntry oldEntries[] = data;
+ ReferenceEntry newEntries[] = new ReferenceEntry[newCapacity];
+
+ for (int i = oldCapacity - 1; i >= 0; i--) {
+ ReferenceEntry entry = oldEntries[i];
+ if (entry != null) {
+ oldEntries[i] = null; // gc
+ do {
+ ReferenceEntry next = entry.next;
+ int index = hashIndex(entry.hashCode, newCapacity);
+ entry.next = newEntries[index];
+ newEntries[index] = entry;
+ entry = next;
+ } while (entry != null);
+ }
+ }
+ threshold = calculateThreshold(newCapacity, loadFactor);
+ data = newEntries;
+ }
+
+ /**
+ * Calculates the new threshold of the map, where it will be resized.
+ * This implementation uses the load factor.
+ *
+ * @param newCapacity the new capacity
+ * @param factor the load factor
+ * @return the new resize threshold
+ */
+ private int calculateThreshold(int newCapacity, float factor) {
+ return (int) (newCapacity * factor);
+ }
+
+ /**
+ * Gets the hash code for the key specified.
+ * <p>
+ * This implementation uses the identity hash code.
+ *
+ * @param key the key to get a hash code for
+ * @return the hash code
+ */
+ private int hash(Object key) {
+ return System.identityHashCode(key);
+ }
+
+ /**
+ * Gets the index into the data storage for the hashCode specified.
+ * This implementation uses the least significant bits of the hashCode.
+ *
+ * @param hashCode the hash code to use
+ * @param dataSize the size of the data to pick a bucket from
+ * @return the bucket index
+ */
+ private int hashIndex(int hashCode, int dataSize) {
+ return hashCode & (dataSize - 1);
+ }
+
+ // Code that handles WeakReference cleanup... Invoked prior to
+ // any operation accessing the ReferenceEntry array...
+
+ /**
+ * Purges stale mappings from this map.
+ * <p>
+ * Note that this method is not synchronized! Special
+ * care must be taken if, for instance, you want stale
+ * mappings to be removed on a periodic basis by some
+ * background thread.
+ */
+ private void purge() {
+ Reference entryRef = purgeQueue.poll();
+ while (entryRef != null) {
+ purge(entryRef);
+ entryRef = purgeQueue.poll();
+ }
+ }
+
+ /**
+ * Purges the specified reference.
+ *
+ * @param ref the reference to purge
+ */
+ private void purge(Reference purgedEntry) {
+ int hash = ((ReferenceEntry)purgedEntry).hashCode;
+ int index = hashIndex(hash, data.length);
+ ReferenceEntry previous = null;
+ ReferenceEntry currentEntry = data[index];
+ while (currentEntry != null) {
+ if (currentEntry == purgedEntry) {
+ currentEntry.purged();
+ if (previous == null) {
+ data[index] = currentEntry.next;
+ } else {
+ previous.next = currentEntry.next;
+ }
+ this.size--;
+ return;
+ }
+ previous = currentEntry;
+ currentEntry = currentEntry.next;
+ }
+ }
+
+ /**
+ * Each entry in the Map is represented with a ReferenceEntry.
+ * <p>
+ * If getKey() or getValue() returns null, it means
+ * the mapping is stale and should be removed.
+ *
+ * @since Commons Collections 3.1
+ */
+ private static class ReferenceEntry extends WeakReference {
+ /** The next entry in the hash chain */
+ private ReferenceEntry next;
+ /** The hash code of the key */
+ private int hashCode;
+ /** The value */
+ private Object value;
+
+ /**
+ * Creates a new entry object for the ReferenceMap.
+ *
+ * @param parent the parent map
+ * @param next the next entry in the hash bucket
+ * @param hashCode the hash code of the key
+ * @param key the key
+ * @param value the value
+ */
+ private ReferenceEntry(BasicProxyMap parent, ReferenceEntry next, int hashCode, Object key, Object value) {
+ super(key, parent.purgeQueue);
+ this.next = next;
+ this.hashCode = hashCode;
+ this.value = value;
+ }
+
+ /**
+ * Gets the key from the entry.
+ * This method dereferences weak and soft keys and thus may return null.
+ *
+ * @return the key, which may be null if it was garbage collected
+ */
+ private Object getKey() {
+ return this.get();
+ }
+
+ /**
+ * Gets the value from the entry.
+ * This method dereferences weak and soft value and thus may return null.
+ *
+ * @return the value, which may be null if it was garbage collected
+ */
+ private Object getValue() {
+ return value;
+ }
+
+ /**
+ * Sets the value of the entry.
+ *
+ * @param obj the object to store
+ * @return the previous value
+ */
+ private Object setValue(Object obj) {
+ Object old = getValue();
+ value = obj;
+ return old;
+ }
+
+ /**
+ * Purges this entry.
+ */
+ private void purged() {
+ this.clear();
+ value = null;
+ }
+ }
+}
Added: geronimo/trunk/modules/kernel/src/test/org/apache/geronimo/kernel/basic/BasicProxyMapTest.java
URL: http://svn.apache.org/viewcvs/geronimo/trunk/modules/kernel/src/test/org/apache/geronimo/kernel/basic/BasicProxyMapTest.java?rev=328153&view=auto
==============================================================================
--- geronimo/trunk/modules/kernel/src/test/org/apache/geronimo/kernel/basic/BasicProxyMapTest.java (added)
+++ geronimo/trunk/modules/kernel/src/test/org/apache/geronimo/kernel/basic/BasicProxyMapTest.java Mon Oct 24 14:29:04 2005
@@ -0,0 +1,157 @@
+package org.apache.geronimo.kernel.basic;
+
+import junit.framework.TestCase;
+
+public class BasicProxyMapTest extends TestCase {
+ private BasicProxyMap map;
+
+ public void testPut() {
+ Object key1 = new Object();
+ Object value1 = new Object();
+ Object key2 = new Object();
+ Object value2 = new Object();
+
+ // test expected start conditions
+ assertEquals(0,map.size());
+ assertTrue(map.isEmpty());
+ assertNull(map.get(key1));
+ assertFalse(map.containsKey(key1));
+ assertFalse(map.containsValue(value1));
+
+ // test put
+ assertNull(map.put(key1, value1));
+ assertEquals(1,map.size());
+ assertFalse(map.isEmpty());
+ assertSame(value1, map.put(key1,value2));
+ assertSame(value2, map.put(key1,value1));
+ assertNull(map.put(key2, value2));
+ assertEquals(2,map.size());
+ }
+
+ public void testContains() {
+ Object key1 = new Object();
+ Object value1 = new Object();
+ Object key2 = new Object();
+ Object value2 = new Object();
+
+ map.put(key1,value1);
+ map.put(key2, value2);
+
+ assertTrue(map.containsKey(key1));
+ assertTrue(map.containsValue(value1));
+ assertTrue(map.containsKey(key2));
+ assertTrue(map.containsValue(value2));
+ assertFalse(map.containsKey(new Object()));
+ assertFalse(map.containsValue(new Object()));
+ }
+
+ public void testGet() {
+ Object key1 = new Object();
+ Object value1 = new Object();
+ Object key2 = new Object();
+ Object value2 = new Object();
+
+ map.put(key1,value1);
+ map.put(key2, value2);
+
+ assertSame(value1,map.get(key1));
+ assertSame(value2,map.get(key2));
+ assertNull(map.get(new Object()));
+ }
+
+ public void testRemove() {
+ Object key1 = new Object();
+ Object value1 = new Object();
+ Object key2 = new Object();
+ Object value2 = new Object();
+
+ map.put(key1,value1);
+ map.put(key2, value2);
+
+ assertSame(value1,map.remove(key1));
+ assertEquals(1,map.size());
+ assertFalse(map.isEmpty());
+ assertFalse(map.containsKey(key1));
+ assertFalse(map.containsValue(value1));
+ assertNull(map.get(key1));
+ assertNull(map.put(key1, value1));
+ }
+
+ public void testClear() {
+ Object key1 = new Object();
+ Object value1 = new Object();
+ Object key2 = new Object();
+ Object value2 = new Object();
+
+ map.put(key1,value1);
+ map.put(key2, value2);
+ map.clear();
+
+ assertEquals(0,map.size());
+ assertTrue(map.isEmpty());
+ assertNull(map.get(key1));
+ assertFalse(map.containsKey(key1));
+ assertFalse(map.containsValue(value1));
+ }
+
+ public void testIdentitySemantic() {
+ Object value1 = new Object();
+ Object value2 = new Object();
+
+ class IdentityTest {
+ public int hashCode() { return 1; }
+ public boolean equals(Object o) { return true; }
+ }
+ class IdentityTest2 {
+ public int hashCode() { return 1; }
+ public boolean equals(Object o) { return false; }
+ }
+
+ Object key1 = new IdentityTest();
+ Object key2 = new IdentityTest();
+
+ assertNull(map.put(key1, value1));
+ assertNull(map.get(key2));
+ assertNull(map.put(key2, value2));
+ assertSame(value1, map.get(key1));
+ assertSame(value2, map.get(key2));
+ map.clear();
+
+ key1 = new IdentityTest2();
+ key2 = new IdentityTest2();
+
+ assertNull(map.put(key1, value1));
+ assertNull(map.get(key2));
+ assertNull(map.put(key2, value2));
+ assertSame(value1, map.get(key1));
+ assertSame(value2, map.get(key2));
+ }
+
+ public void testWeakSemantic() {
+ int MAX = 256;
+ Object [] keys = new Object[MAX];
+ Object [] values = new Object[MAX];
+ for (int i=0;i<MAX;i++) {
+ keys[i] = new Object(); values[i] = new Object();
+ assertNull(map.put(keys[i],values[i]));
+ }
+ for (int i=0;i<MAX;i++) {
+ assertEquals(values[i], map.get(keys[i]));
+ }
+
+ // now add a number of entries that will be available for GCing
+ for (int i=0;i<MAX;i++) {
+ assertNull(map.put(new Object(), new Object()));
+ }
+ System.gc();
+ try {Thread.sleep(1000);} catch (InterruptedException ie) {}
+ assertEquals(MAX,map.size());
+ for (int i=0;i<MAX;i++) {
+ assertSame(values[i], map.get(keys[i]));
+ }
+ }
+
+ protected void setUp() throws Exception {
+ map = new BasicProxyMap();
+ }
+}