[hibernate-commits] Hibernate SVN: r20860 - in core/branches/Branch_3_3_2_GA_CP: core/src/main/java/org/hibernate/engine and 4 other directories.

hibernate-commits at lists.jboss.org hibernate-commits at lists.jboss.org
Mon Nov 8 05:14:49 EST 2010


Author: stliu
Date: 2010-11-08 05:14:48 -0500 (Mon, 08 Nov 2010)
New Revision: 20860

Added:
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/LRUMap.java
Modified:
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/cfg/Environment.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/query/QueryPlanCache.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java
   core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/SoftLimitMRUCache.java
   core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java
Log:
JBPAPP-5394 Backport HHH-5300 to EAP to fix memory leak in QueryPlanCache

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/cfg/Environment.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/cfg/Environment.java	2010-11-08 09:28:03 UTC (rev 20859)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/cfg/Environment.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -509,7 +509,17 @@
      * 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";
 
 	public static final String JPAQL_STRICT_COMPLIANCE= "hibernate.query.jpaql_strict_compliance";

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java	2010-11-08 09:28:03 UTC (rev 20859)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/SessionFactoryImplementor.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -26,6 +26,7 @@
 
 import java.sql.Connection;
 import java.util.Map;
+import java.util.Properties;
 import java.util.Set;
 
 import javax.transaction.TransactionManager;
@@ -219,5 +220,7 @@
 	public EntityNotFoundDelegate getEntityNotFoundDelegate();
 
 	public SQLFunctionRegistry getSqlFunctionRegistry();
+
+	public Properties getProperties();
 		
 }

Modified: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/query/QueryPlanCache.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/query/QueryPlanCache.java	2010-11-08 09:28:03 UTC (rev 20859)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/engine/query/QueryPlanCache.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -1,10 +1,10 @@
 /*
  * Hibernate, Relational Persistence for Idiomatic Java
  *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * 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 Middleware LLC.
+ * 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
@@ -20,33 +20,38 @@
  * Free Software Foundation, Inc.
  * 51 Franklin Street, Fifth Floor
  * Boston, MA  02110-1301  USA
- *
  */
 package org.hibernate.engine.query;
 
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import org.hibernate.MappingException;
-import org.hibernate.QueryException;
+import org.hibernate.util.PropertiesHelper;
+import org.hibernate.util.SimpleMRUCache;
+import org.hibernate.util.SoftLimitMRUCache;
+import org.hibernate.util.CollectionHelper;
+import org.hibernate.cfg.Environment;
 import org.hibernate.engine.SessionFactoryImplementor;
 import org.hibernate.engine.query.sql.NativeSQLQuerySpecification;
+import org.hibernate.QueryException;
+import org.hibernate.MappingException;
 import org.hibernate.impl.FilterImpl;
-import org.hibernate.util.CollectionHelper;
-import org.hibernate.util.SimpleMRUCache;
-import org.hibernate.util.SoftLimitMRUCache;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.io.Serializable;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Set;
+import java.util.HashSet;
+import java.util.Collections;
+import java.util.Collection;
+
 /**
  * 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 Steve Ebersole
  */
 public class QueryPlanCache implements Serializable {
@@ -56,29 +61,51 @@
 	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 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();
+	/**
+	 * 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;
 
-	// the cache of the actual plans...
-	private final SoftLimitMRUCache planCache = new SoftLimitMRUCache( 128 );
+	/**
+	 * the cache of the actual plans...
+	 */
+	private final SoftLimitMRUCache planCache;
 
 
+	/**
+	 * 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 );
 		}
@@ -148,6 +175,7 @@
 		return plan;
 	}
 
+	@SuppressWarnings({ "UnnecessaryUnboxing" })
 	private ParameterMetadata buildNativeSQLParameterMetadata(String sqlString) {
 		ParamLocationRecognizer recognizer = ParamLocationRecognizer.parseLocations( sqlString );
 
@@ -159,7 +187,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();
@@ -177,7 +205,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) {
@@ -185,16 +213,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 );
 			}
@@ -228,30 +255,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;
 
+		@SuppressWarnings({ "UnnecessaryBoxing" })
 		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() ) {
+				for ( Object o : filter.getParameters().entrySet() ) {
+					final Map.Entry entry = (Map.Entry) o;
+					final String key = (String) entry.getKey();
 					final Integer valueCount;
-					final Map.Entry entry = ( Map.Entry ) itr.next();
 					if ( Collection.class.isInstance( entry.getValue() ) ) {
 						valueCount = new Integer( ( (Collection) entry.getValue() ).size() );
 					}
 					else {
-						valueCount = new Integer(1);
+						valueCount = new Integer( 1 );
 					}
-					parameterMetadata.put( entry.getKey(), valueCount );
+					parameterMetadata.put( key, valueCount );
 				}
 			}
 
@@ -284,19 +312,20 @@
 		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;
 
+		@SuppressWarnings({ "unchecked" })
 		public FilterQueryPlanKey(String query, String collectionRole, boolean shallow, Map enabledFilters) {
 			this.query = query;
 			this.collectionRole = collectionRole;
 			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_3_2_GA_CP/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java	2010-11-08 09:28:03 UTC (rev 20859)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/impl/SessionFactoryImpl.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -178,7 +178,7 @@
 	private final transient SessionFactoryObserver observer;
 	private final transient HashMap entityNameResolvers = new HashMap();
 
-	private final QueryPlanCache queryPlanCache = new QueryPlanCache( this );
+	private final QueryPlanCache queryPlanCache;
 
 	private transient boolean isClosed = false;
 
@@ -218,7 +218,7 @@
 
 		// Caches
 		settings.getRegionFactory().start( settings, properties );
-
+		this.queryPlanCache = new QueryPlanCache( this );
 		//Generators:
 
 		identifierGenerators = new HashMap();
@@ -1153,4 +1153,8 @@
 	public SQLFunctionRegistry getSqlFunctionRegistry() {
 		return sqlFunctionRegistry;
 	}
+
+	public Properties getProperties() {
+		return properties;
+	}
 }

Added: core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/LRUMap.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/LRUMap.java	                        (rev 0)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/LRUMap.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -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_3_2_GA_CP/core/src/main/java/org/hibernate/util/SoftLimitMRUCache.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/SoftLimitMRUCache.java	2010-11-08 09:28:03 UTC (rev 20859)
+++ core/branches/Branch_3_3_2_GA_CP/core/src/main/java/org/hibernate/util/SoftLimitMRUCache.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -1,10 +1,10 @@
 /*
  * Hibernate, Relational Persistence for Idiomatic Java
  *
- * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
+ * 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 Middleware LLC.
+ * 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
@@ -20,18 +20,16 @@
  * Free Software Foundation, Inc.
  * 51 Franklin Street, Fifth Floor
  * Boston, MA  02110-1301  USA
- *
  */
 package org.hibernate.util;
 
+import java.io.Serializable;
 import java.io.IOException;
-import java.io.Serializable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
 
-import org.apache.commons.collections.map.LRUMap;
-import org.apache.commons.collections.map.ReferenceMap;
-
 /**
- * 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/>
@@ -39,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
-	// separate 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 {
@@ -100,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;
+		}
+	}
 }

Modified: core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java
===================================================================
--- core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java	2010-11-08 09:28:03 UTC (rev 20859)
+++ core/branches/Branch_3_3_2_GA_CP/testsuite/src/test/java/org/hibernate/test/queryplan/NativeSQLQueryPlanEqualsTest.java	2010-11-08 10:14:48 UTC (rev 20860)
@@ -47,7 +47,7 @@
 	}
 
 	public void testNativeSQLQuerySpecEquals() {
-		QueryPlanCache cache = new QueryPlanCache(null);
+		QueryPlanCache cache = new QueryPlanCache(sfi());
 		NativeSQLQuerySpecification firstSpec = createSpec();
 
 		NativeSQLQuerySpecification secondSpec = createSpec();



More information about the hibernate-commits mailing list