Author: epbernard
Date: 2010-08-24 08:50:58 -0400 (Tue, 24 Aug 2010)
New Revision: 20241
Added:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/util/SoftLimitMRUCache.java
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/CachingWrapperFilter.java
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java
Log:
HSEARCH-580 No longer depend on Core's SoftLimitMRUCache implem
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/CachingWrapperFilter.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/CachingWrapperFilter.java 2010-08-24
10:47:18 UTC (rev 20240)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/CachingWrapperFilter.java 2010-08-24
12:50:58 UTC (rev 20241)
@@ -32,7 +32,7 @@
import org.slf4j.Logger;
import org.hibernate.search.util.LoggerFactory;
-import org.hibernate.util.SoftLimitMRUCache;
+import org.hibernate.search.util.SoftLimitMRUCache;
/**
* A slightly different version of Lucene's original
<code>CachingWrapperFilter</code> which
@@ -58,6 +58,12 @@
private final Filter filter;
/**
+ * Under memory pressure the JVM will release all Soft references,
+ * so pushing it too high will invalidate all eventually useful other caches.
+ */
+ private static final int HARD_TO_SOFT_RATIO = 15;
+
+ /**
* @param filter Filter to cache results of
*/
public CachingWrapperFilter(Filter filter) {
@@ -69,8 +75,9 @@
*/
public CachingWrapperFilter(Filter filter, int size) {
this.filter = filter;
- log.debug( "Initialising SoftLimitMRUCache with hard ref size of {}", size
);
- this.cache = new SoftLimitMRUCache( size );
+ final int softRefSize = size * HARD_TO_SOFT_RATIO;
+ log.debug( "Initialising SoftLimitMRUCache with hard ref size of {} and a soft ref
of {}", size, softRefSize );
+ this.cache = new SoftLimitMRUCache( size, softRefSize );
}
@Override
Modified:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java 2010-08-24
10:47:18 UTC (rev 20240)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/filter/MRUFilterCachingStrategy.java 2010-08-24
12:50:58 UTC (rev 20241)
@@ -29,7 +29,7 @@
import org.apache.lucene.search.Filter;
import org.hibernate.search.Environment;
import org.hibernate.search.backend.configuration.ConfigurationParseHelper;
-import org.hibernate.util.SoftLimitMRUCache;
+import org.hibernate.search.util.SoftLimitMRUCache;
/**
* Keep the most recently used Filters in the cache
@@ -43,9 +43,15 @@
private SoftLimitMRUCache cache;
private static final String SIZE = Environment.FILTER_CACHING_STRATEGY +
".size";
+ /**
+ * Under memory pressure the JVM will release all Soft references,
+ * so pushing it too high will invalidate all eventually useful other caches.
+ */
+ private static final int HARD_TO_SOFT_RATIO = 15;
+
public void initialize(Properties properties) {
int size = ConfigurationParseHelper.getIntValue( properties, SIZE, DEFAULT_SIZE );
- cache = new SoftLimitMRUCache( size );
+ cache = new SoftLimitMRUCache( size, size * HARD_TO_SOFT_RATIO );
}
public Filter getCachedFilter(FilterKey key) {
Added:
search/trunk/hibernate-search/src/main/java/org/hibernate/search/util/SoftLimitMRUCache.java
===================================================================
---
search/trunk/hibernate-search/src/main/java/org/hibernate/search/util/SoftLimitMRUCache.java
(rev 0)
+++
search/trunk/hibernate-search/src/main/java/org/hibernate/search/util/SoftLimitMRUCache.java 2010-08-24
12:50:58 UTC (rev 20241)
@@ -0,0 +1,236 @@
+/*
+ * 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.search.util;
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.SoftReference;
+
+import org.hibernate.util.LRUMap;
+
+/**
+ * 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/>
+ * This implementation uses a "soft limit" to the in-memory size of the cache,
+ * 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
+ * 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
+ *
+ * Copied from Hibernate Core
+ */
+public class SoftLimitMRUCache implements Serializable {
+ /**
+ * The default strong reference count.
+ */
+ public static final int DEFAULT_STRONG_REF_COUNT = 128;
+
+ /**
+ * The default soft reference count.
+ */
+ public static final int DEFAULT_SOFT_REF_COUNT = 2048;
+
+ 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, DEFAULT_SOFT_REF_COUNT );
+ }
+
+ /**
+ * 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) {
+ if ( key == null ) {
+ throw new NullPointerException( "Key to get cannot be null" );
+ }
+
+ 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) {
+ 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() {
+ clearObsoleteReferences();
+ return strongRefCache.size();
+ }
+
+ /**
+ * Gets the soft reference cache size.
+ *
+ * @return the soft reference cache size.
+ */
+ public synchronized int softSize() {
+ clearObsoleteReferences();
+ return softRefCache.size();
+ }
+
+ /**
+ * Clears the cache.
+ */
+ public synchronized void clear() {
+ strongRefCache.clear();
+ softRefCache.clear();
+ }
+
+ private void init() {
+ this.strongRefCache = new LRUMap( strongRefCount );
+ this.softRefCache = new LRUMap( softRefCount );
+ this.referenceQueue = new ReferenceQueue();
+ }
+
+ private void readObject(java.io.ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ in.defaultReadObject();
+ init();
+ }
+
+ 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;
+ }
+ }
+}
+