[hibernate-commits] Hibernate SVN: r20856 - in core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate: engine and 3 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Mon Nov 8 03:39:23 EST 2010


Author: stliu
Date: 2010-11-08 03:39:22 -0500 (Mon, 08 Nov 2010)
New Revision: 20856

Added:
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/LRUMap.java
Modified:
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/cfg/Environment.java
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/SessionFactoryImplementor.java
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/query/QueryPlanCache.java
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/impl/SessionFactoryImpl.java
   core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SoftLimitMRUCache.java
Log:
JBPAPP-5392 Backport HHH-5300 to EAP to fix memory leak in QueryPlanCache

Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/cfg/Environment.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/cfg/Environment.java	2010-11-03 09:15:19 UTC (rev 20855)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/cfg/Environment.java	2010-11-08 08:39:22 UTC (rev 20856)
@@ -466,6 +466,14 @@
      * The jacc context id of the deployment
      */
     public static final String JACC_CONTEXTID = "hibernate.jacc_context_id";
+    /**
+     * The maximum number of strong references maintained by {@link org.hibernate.util.SoftLimitMRUCache}. Default is 128.
+     */
+    public static final String QUERY_PLAN_CACHE_MAX_STRONG_REFERENCES = "hibernate.query.plan_cache_max_strong_references";
+    /**
+     * The maximum number of soft references maintained by {@link org.hibernate.util.SoftLimitMRUCache}. Default is 2048.
+     */
+    public static final String QUERY_PLAN_CACHE_MAX_SOFT_REFERENCES = "hibernate.query.plan_cache_max_soft_references";
 
 	public static final String BYTECODE_PROVIDER = "hibernate.bytecode.provider";
 

Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/SessionFactoryImplementor.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/SessionFactoryImplementor.java	2010-11-03 09:15:19 UTC (rev 20855)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/SessionFactoryImplementor.java	2010-11-08 08:39:22 UTC (rev 20856)
@@ -2,6 +2,7 @@
 package org.hibernate.engine;
 
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 import java.sql.Connection;
 
@@ -37,8 +38,13 @@
  * @author Gavin King
  */
 public interface SessionFactoryImplementor extends Mapping, SessionFactory {
-
 	/**
+	 * Get a copy of the Properties used to configure this session factory.
+	 *
+	 * @return The properties.
+	 */
+	public Properties getProperties();
+	/**
 	 * Get the persister for the named entity
 	 */
 	public EntityPersister getEntityPersister(String entityName) throws MappingException;

Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/query/QueryPlanCache.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/query/QueryPlanCache.java	2010-11-03 09:15:19 UTC (rev 20855)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/engine/query/QueryPlanCache.java	2010-11-08 08:39:22 UTC (rev 20856)
@@ -1,9 +1,33 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Inc.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
 package org.hibernate.engine.query;
 
-import org.hibernate.util.ArrayHelper;
 import org.hibernate.util.CollectionHelper;
+import org.hibernate.util.PropertiesHelper;
 import org.hibernate.util.SimpleMRUCache;
 import org.hibernate.util.SoftLimitMRUCache;
+import org.hibernate.cfg.Environment;
 import org.hibernate.engine.SessionFactoryImplementor;
 import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
 import org.hibernate.impl.FilterImpl;
@@ -16,7 +40,6 @@
 import java.util.Collection;
 import java.util.Map;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.HashSet;
@@ -25,6 +48,9 @@
 /**
  * Acts as a cache for compiled query plans, as well as query-parameter metadata.
  *
+ * @see Environment#QUERY_PLAN_CACHE_MAX_STRONG_REFERENCES
+ * @see Environment#QUERY_PLAN_CACHE_MAX_SOFT_REFERENCES
+ *
  * @author <a href="mailto:steve at hibernate.org">Steve Ebersole </a>
  */
 public class QueryPlanCache implements Serializable {
@@ -34,29 +60,49 @@
 	private SessionFactoryImplementor factory;
 
 	public QueryPlanCache(SessionFactoryImplementor factory) {
+		int maxStrongReferenceCount = PropertiesHelper.getInt(
+				Environment.QUERY_PLAN_CACHE_MAX_STRONG_REFERENCES,
+				factory.getProperties(),
+				SoftLimitMRUCache.DEFAULT_STRONG_REF_COUNT
+		);
+		int maxSoftReferenceCount = PropertiesHelper.getInt(
+				Environment.QUERY_PLAN_CACHE_MAX_SOFT_REFERENCES,
+				factory.getProperties(),
+				SoftLimitMRUCache.DEFAULT_SOFT_REF_COUNT
+		);
 		this.factory = factory;
+		this.sqlParamMetadataCache = new SimpleMRUCache( maxStrongReferenceCount );
+		this.planCache = new SoftLimitMRUCache( maxStrongReferenceCount, maxSoftReferenceCount );
+
 	}
+	/**
+	 * simple cache of param metadata based on query string.  Ideally, the original "user-supplied query"
+	 * string should be used to obtain this metadata (i.e., not the para-list-expanded query string) to avoid
+	 * unnecessary cache entries.
+	 * <p>
+	 * Used solely for caching param metadata for native-sql queries, see {@link #getSQLParameterMetadata} for a
+	 * discussion as to why...
+	 */
+	private final SimpleMRUCache sqlParamMetadataCache;
 
-	// simple cache of param metadata based on query string.  Ideally, the
-	// original "user-supplied query" string should be used to retreive this
-	// metadata (i.e., not the para-list-expanded query string) to avoid
-	// unnecessary cache entries.
-	// Used solely for caching param metadata for native-sql queries, see
-	// getSQLParameterMetadata() for a discussion as to why...
-	private final SimpleMRUCache sqlParamMetadataCache = new SimpleMRUCache();
+	/**
+	 * the cache of the actual plans...
+	 */
+	private final SoftLimitMRUCache planCache;
 
-	// the cache of the actual plans...
-	private final SoftLimitMRUCache planCache = new SoftLimitMRUCache( 128 );
-
-
+	/**
+	 * Obtain the parameter metadata for given native-sql query.
+	 * <p/>
+	 * for native-sql queries, the param metadata is determined outside any relation to a query plan, because
+	 * query plan creation and/or retrieval for a native-sql query depends on all of the return types having been
+	 * set, which might not be the case up-front when param metadata would be most useful
+	 *
+	 * @param query The query
+	 * @return The parameter metadata
+	 */
 	public ParameterMetadata getSQLParameterMetadata(String query) {
 		ParameterMetadata metadata = ( ParameterMetadata ) sqlParamMetadataCache.get( query );
 		if ( metadata == null ) {
-			// for native-sql queries, the param metadata is determined outside
-			// any relation to a query plan, because query plan creation and/or
-			// retreival for a native-sql query depends on all of the return
-			// types having been set, which might not be the case up-front when
-			// param metadata would be most useful
 			metadata = buildNativeSQLParameterMetadata( query );
 			sqlParamMetadataCache.put( query, metadata );
 		}
@@ -137,7 +183,7 @@
 		}
 
 		Iterator itr = recognizer.getNamedParameterDescriptionMap().entrySet().iterator();
-		Map namedParamDescriptorMap = new HashMap();
+		Map<String,NamedParameterDescriptor> namedParamDescriptorMap = new HashMap<String,NamedParameterDescriptor>();
 		while( itr.hasNext() ) {
 			final Map.Entry entry = ( Map.Entry ) itr.next();
 			final String name = ( String ) entry.getKey();
@@ -155,7 +201,7 @@
 	private static class HQLQueryPlanKey implements Serializable {
 		private final String query;
 		private final boolean shallow;
-		private final Set filterKeys;
+		private final Set<DynamicFilterKey> filterKeys;
 		private final int hashCode;
 
 		public HQLQueryPlanKey(String query, boolean shallow, Map enabledFilters) {
@@ -163,16 +209,15 @@
 			this.shallow = shallow;
 
 			if ( enabledFilters == null || enabledFilters.isEmpty() ) {
-				filterKeys = Collections.EMPTY_SET;
+				filterKeys = Collections.emptySet();
 			}
 			else {
-				Set tmp = new HashSet(
+				Set<DynamicFilterKey> tmp = new HashSet<DynamicFilterKey>(
 						CollectionHelper.determineProperSizing( enabledFilters ),
 						CollectionHelper.LOAD_FACTOR
 				);
-				Iterator itr = enabledFilters.values().iterator();
-				while ( itr.hasNext() ) {
-					tmp.add( new DynamicFilterKey( ( FilterImpl ) itr.next() ) );
+				for ( Object o : enabledFilters.values() ) {
+					tmp.add( new DynamicFilterKey( (FilterImpl) o ) );
 				}
 				this.filterKeys = Collections.unmodifiableSet( tmp );
 			}
@@ -205,30 +250,31 @@
 	}
 	private static class DynamicFilterKey implements Serializable {
 		private final String filterName;
-		private final Map parameterMetadata;
+		private final Map<String,Integer> parameterMetadata;
 		private final int hashCode;
 
 		private DynamicFilterKey(FilterImpl filter) {
 			this.filterName = filter.getName();
 			if ( filter.getParameters().isEmpty() ) {
-				parameterMetadata = Collections.EMPTY_MAP;
+				parameterMetadata = Collections.emptyMap();
 			}
 			else {
-				parameterMetadata = new HashMap(
+				parameterMetadata = new HashMap<String,Integer>(
 						CollectionHelper.determineProperSizing( filter.getParameters() ),
 						CollectionHelper.LOAD_FACTOR
 				);
-				Iterator itr = filter.getParameters().entrySet().iterator();
-				while ( itr.hasNext() ) {
-					final Integer valueCount;
-					final Map.Entry entry = ( Map.Entry ) itr.next();
+				for ( Object o : filter.getParameters().entrySet() ) {
+					final Map.Entry entry = (Map.Entry) o;
+					final String key = (String) entry.getKey();
+				
+				final Integer valueCount;
 					if ( Collection.class.isInstance( entry.getValue() ) ) {
 						valueCount = new Integer( ( (Collection) entry.getValue() ).size() );
 					}
 					else {
 						valueCount = new Integer(1);
 					}
-					parameterMetadata.put( entry.getKey(), valueCount );
+					parameterMetadata.put( key, valueCount );
 				}
 			}
 
@@ -260,7 +306,7 @@
 		private final String query;
 		private final String collectionRole;
 		private final boolean shallow;
-		private final Set filterNames;
+		private final Set<String> filterNames;
 		private final int hashCode;
 
 		public FilterQueryPlanKey(String query, String collectionRole, boolean shallow, Map enabledFilters) {
@@ -269,10 +315,10 @@
 			this.shallow = shallow;
 
 			if ( enabledFilters == null || enabledFilters.isEmpty() ) {
-				filterNames = Collections.EMPTY_SET;
+				filterNames = Collections.emptySet();
 			}
 			else {
-				Set tmp = new HashSet();
+				Set<String> tmp = new HashSet<String>();
 				tmp.addAll( enabledFilters.keySet() );
 				this.filterNames = Collections.unmodifiableSet( tmp );
 			}

Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/impl/SessionFactoryImpl.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/impl/SessionFactoryImpl.java	2010-11-03 09:15:19 UTC (rev 20855)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/impl/SessionFactoryImpl.java	2010-11-08 08:39:22 UTC (rev 20856)
@@ -142,7 +142,7 @@
 	private final transient EntityNotFoundDelegate entityNotFoundDelegate;
 	private final transient SQLFunctionRegistry sqlFunctionRegistry;
 	
-	private final QueryPlanCache queryPlanCache = new QueryPlanCache( this );
+	private final QueryPlanCache queryPlanCache;
 
 	private transient boolean isClosed = false;
 	
@@ -181,7 +181,7 @@
 
 		// Caches
 		settings.getCacheProvider().start( properties );
-
+		this.queryPlanCache = new QueryPlanCache( this );
 		//Generators:
 
 		identifierGenerators = new HashMap();
@@ -974,7 +974,9 @@
 	public BatcherFactory getBatcherFactory() {
 		return settings.getBatcherFactory();
 	}
-
+	public Properties getProperties() {
+		return properties;
+	}
 	public IdentifierGenerator getIdentifierGenerator(String rootEntityName) {
 		return (IdentifierGenerator) identifierGenerators.get(rootEntityName);
 	}

Added: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/LRUMap.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/LRUMap.java	                        (rev 0)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/LRUMap.java	2010-11-08 08:39:22 UTC (rev 20856)
@@ -0,0 +1,50 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Inc.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
+package org.hibernate.util;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * A simple LRU cache that implements the <code>Map</code> interface. Instances
+ * are not thread-safe and should be synchronized externally, for instance by
+ * using {@link java.util.Collections#synchronizedMap}.
+ * 
+ * @author Manuel Dominguez Sarmiento
+ */
+public class LRUMap extends LinkedHashMap implements Serializable {
+	private static final long serialVersionUID = -5522608033020688048L;
+
+	private final int maxEntries;
+
+	public LRUMap(int maxEntries) {
+		super( maxEntries, .75f, true );
+		this.maxEntries = maxEntries;
+	}
+
+	protected boolean removeEldestEntry(Map.Entry eldest) {
+		return ( size() > maxEntries );
+	}
+}
\ No newline at end of file

Modified: core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SoftLimitMRUCache.java
===================================================================
--- core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SoftLimitMRUCache.java	2010-11-03 09:15:19 UTC (rev 20855)
+++ core/branches/Branch_3_2_4_SP1_CP/src/org/hibernate/util/SoftLimitMRUCache.java	2010-11-08 08:39:22 UTC (rev 20856)
@@ -1,13 +1,35 @@
+/*
+ * Hibernate, Relational Persistence for Idiomatic Java
+ *
+ * Copyright (c) 2010, Red Hat Inc. or third-party contributors as
+ * indicated by the @author tags or express copyright attribution
+ * statements applied by the authors.  All third-party contributions are
+ * distributed under license by Red Hat Inc.
+ *
+ * This copyrighted material is made available to anyone wishing to use, modify,
+ * copy, or redistribute it subject to the terms and conditions of the GNU
+ * Lesser General Public License, as published by the Free Software Foundation.
+ *
+ * This program 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 Lesser General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this distribution; if not, write to:
+ * Free Software Foundation, Inc.
+ * 51 Franklin Street, Fifth Floor
+ * Boston, MA  02110-1301  USA
+ */
 package org.hibernate.util;
 
-import org.apache.commons.collections.ReferenceMap;
-import org.apache.commons.collections.LRUMap;
-
 import java.io.Serializable;
 import java.io.IOException;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
 
 /**
- * Cache following a "Most Recently Used" (MRY) algorithm for maintaining a
+ * Cache following a "Most Recently Used" (MRU) algorithm for maintaining a
  * bounded in-memory size; the "Least Recently Used" (LRU) entry is the first
  * available for removal from the cache.
  * <p/>
@@ -15,60 +37,168 @@
  * meaning that all cache entries are kept within a completely
  * {@link java.lang.ref.SoftReference}-based map with the most recently utilized
  * entries additionally kept in a hard-reference manner to prevent those cache
- * entries soft references from becoming enqueued by the garbage collector.
- * Thus the actual size of this cache impl can actually grow beyond the stated
- * max size bound as long as GC is not actively seeking soft references for
+ * entries soft references from becoming enqueued by the garbage collector. Thus
+ * the actual size of this cache impl can actually grow beyond the stated max
+ * size bound as long as GC is not actively seeking soft references for
  * enqueuement.
+ * <p/>
+ * The soft-size is bounded and configurable. This allows controlling memory
+ * usage which can grow out of control under some circumstances, especially when
+ * very large heaps are in use. Although memory usage per se should not be a
+ * problem with soft references, which are cleared when necessary, this can
+ * trigger extremely slow stop-the-world GC pauses when nearing full heap usage,
+ * even with CMS concurrent GC (i.e. concurrent mode failure). This is most
+ * evident when ad-hoc HQL queries are produced by the application, leading to
+ * poor soft-cache hit ratios. This can also occur with heavy use of SQL IN
+ * clauses, which will generate multiples SQL queries (even if parameterized),
+ * one for each collection/array size passed to the IN clause. Many slightly
+ * different queries will eventually fill the heap and trigger a full GC to
+ * reclaim space, leading to unacceptable pauses in some cases.
+ * <p/>
+ * <strong>Note:</strong> This class is serializable, however all entries are
+ * discarded on serialization.
  *
+ * @see org.hibernate.cfg.Environment#QUERY_PLAN_CACHE_MAX_STRONG_REFERENCES
+ * @see org.hibernate.cfg.Environment#QUERY_PLAN_CACHE_MAX_SOFT_REFERENCES
+ *
  * @author Steve Ebersole
+ * @author Manuel Dominguez Sarmiento
  */
 public class SoftLimitMRUCache implements Serializable {
-
+	/**
+	 * The default strong reference count.
+	 */
 	public static final int DEFAULT_STRONG_REF_COUNT = 128;
 
-	private final int strongReferenceCount;
+	/**
+	 * The default soft reference count.
+	 */
+	public static final int DEFAULT_SOFT_REF_COUNT = 2048;
 
-	// actual cache of the entries.  soft references are used for both the keys and the
-	// values here since the values pertaining to the MRU entries are kept in a
-	// seperate hard reference cache (to avoid their enqueuement/garbage-collection).
-	private transient ReferenceMap softReferenceCache = new ReferenceMap( ReferenceMap.SOFT, ReferenceMap.SOFT );
-	// the MRU cache used to keep hard references to the most recently used query plans;
-	// note : LRU here is a bit of a misnomer, it indicates that LRU entries are removed, the
-	// actual kept entries are the MRU entries
-	private transient LRUMap strongReferenceCache;
+	private final int strongRefCount;
+	private final int softRefCount;
 
+	private transient LRUMap strongRefCache;
+	private transient LRUMap softRefCache;
+	private transient ReferenceQueue referenceQueue;
+
+	/**
+	 * Constructs a cache with the default settings.
+	 *
+	 * @see #DEFAULT_STRONG_REF_COUNT
+	 * @see #DEFAULT_SOFT_REF_COUNT
+	 */
 	public SoftLimitMRUCache() {
-		this( DEFAULT_STRONG_REF_COUNT );
+		this( DEFAULT_STRONG_REF_COUNT, DEFAULT_SOFT_REF_COUNT );
 	}
 
-	public SoftLimitMRUCache(int strongRefCount) {
-		this.strongReferenceCount = strongRefCount;
+	/**
+	 * Constructs a cache with the specified settings.
+	 *
+	 * @param strongRefCount the strong reference count.
+	 * @param softRefCount the soft reference count.
+	 *
+	 * @throws IllegalArgumentException if either of the arguments is less than one, or if the strong
+	 * reference count is higher than the soft reference count.
+	 */
+	public SoftLimitMRUCache(int strongRefCount, int softRefCount) {
+		if ( strongRefCount < 1 || softRefCount < 1 ) {
+			throw new IllegalArgumentException( "Reference counts must be greater than zero" );
+		}
+		if ( strongRefCount > softRefCount ) {
+			throw new IllegalArgumentException( "Strong reference count cannot exceed soft reference count" );
+		}
+
+		this.strongRefCount = strongRefCount;
+		this.softRefCount = softRefCount;
 		init();
 	}
 
+	/**
+	 * Gets an object from the cache.
+	 *
+	 * @param key the cache key.
+	 *
+	 * @return the stored value, or <code>null</code> if no entry exists.
+	 */
 	public synchronized Object get(Object key) {
-		Object result = softReferenceCache.get( key );
-		if ( result != null ) {
-			strongReferenceCache.put( key, result );
+		if ( key == null ) {
+			throw new NullPointerException( "Key to get cannot be null" );
 		}
-		return result;
+
+		clearObsoleteReferences();
+
+		SoftReference ref = (SoftReference) softRefCache.get( key );
+		if ( ref != null ) {
+			Object refValue = ref.get();
+			if ( refValue != null ) {
+				// This ensures recently used entries are strongly-reachable
+				strongRefCache.put( key, refValue );
+				return refValue;
+			}
+		}
+
+		return null;
 	}
 
+	/**
+	 * Puts a value in the cache.
+	 *
+	 * @param key the key.
+	 * @param value the value.
+	 *
+	 * @return the previous value stored in the cache, if any.
+	 */
 	public synchronized Object put(Object key, Object value) {
-		softReferenceCache.put( key, value );
-		return strongReferenceCache.put( key, value );
+		if ( key == null || value == null ) {
+			throw new NullPointerException(
+					getClass().getName() + "does not support null key [" + key + "] or value [" + value + "]"
+			);
+		}
+
+		clearObsoleteReferences();
+
+		strongRefCache.put( key, value );
+		SoftReference ref = (SoftReference) softRefCache.put(
+				key,
+				new KeyedSoftReference( key, value, referenceQueue )
+		);
+
+		return ( ref != null ) ? ref.get() : null;
 	}
 
+	/**
+	 * Gets the strong reference cache size.
+	 *
+	 * @return the strong reference cache size.
+	 */
 	public synchronized int size() {
-		return strongReferenceCache.size();
+		clearObsoleteReferences();
+		return strongRefCache.size();
 	}
 
+	/**
+	 * Gets the soft reference cache size.
+	 *
+	 * @return the soft reference cache size.
+	 */
 	public synchronized int softSize() {
-		return softReferenceCache.size();
+		clearObsoleteReferences();
+		return softRefCache.size();
 	}
 
+	/**
+	 * Clears the cache.
+	 */
+	public synchronized void clear() {
+		strongRefCache.clear();
+		softRefCache.clear();
+	}
+
 	private void init() {
-		strongReferenceCache = new LRUMap( strongReferenceCount );
+		this.strongRefCache = new LRUMap( strongRefCount );
+		this.softRefCache = new LRUMap( softRefCount );
+		this.referenceQueue = new ReferenceQueue();
 	}
 
 	private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
@@ -76,8 +206,26 @@
 		init();
 	}
 
-	public synchronized void clear() {
-		strongReferenceCache.clear();
-		softReferenceCache.clear();
+	private void clearObsoleteReferences() {
+		// Clear entries for soft references removed by garbage collector
+		KeyedSoftReference obsoleteRef;
+		while ( ( obsoleteRef = (KeyedSoftReference) referenceQueue.poll() ) != null ) {
+			Object key = obsoleteRef.getKey();
+			softRefCache.remove( key );
+		}
 	}
+
+	private static class KeyedSoftReference extends SoftReference {
+		private final Object key;
+
+		@SuppressWarnings({ "unchecked" })
+		private KeyedSoftReference(Object key, Object value, ReferenceQueue q) {
+			super( value, q );
+			this.key = key;
+		}
+
+		private Object getKey() {
+			return key;
+		}
+	}
 }



More information about the hibernate-commits mailing list