net.sf.ehcache.store.MemoryStore Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
/**
* Copyright Terracotta, Inc.
*
* 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 net.sf.ehcache.store;
import net.sf.ehcache.CacheEntry;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.CacheOperationOutcomes.EvictionOutcome;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.sf.ehcache.Status;
import net.sf.ehcache.concurrent.CacheLockProvider;
import net.sf.ehcache.concurrent.ReadWriteLockSync;
import net.sf.ehcache.concurrent.Sync;
import net.sf.ehcache.config.CacheConfiguration;
import net.sf.ehcache.config.CacheConfigurationListener;
import net.sf.ehcache.config.PinningConfiguration;
import net.sf.ehcache.config.SizeOfPolicyConfiguration;
import net.sf.ehcache.event.RegisteredEventListeners;
import net.sf.ehcache.pool.Pool;
import net.sf.ehcache.pool.PoolAccessor;
import net.sf.ehcache.pool.PoolParticipant;
import net.sf.ehcache.pool.Size;
import net.sf.ehcache.pool.SizeOfEngine;
import net.sf.ehcache.pool.SizeOfEngineLoader;
import net.sf.ehcache.pool.impl.UnboundedPool;
import net.sf.ehcache.search.Attribute;
import net.sf.ehcache.search.attribute.AttributeExtractor;
import net.sf.ehcache.search.impl.SearchManager;
import net.sf.ehcache.store.StoreOperationOutcomes.GetOutcome;
import net.sf.ehcache.store.StoreOperationOutcomes.PutOutcome;
import net.sf.ehcache.store.StoreOperationOutcomes.RemoveOutcome;
import net.sf.ehcache.store.chm.SelectableConcurrentHashMap;
import net.sf.ehcache.store.disk.StoreUpdateException;
import net.sf.ehcache.writer.CacheWriterManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terracotta.statistics.OperationStatistic;
import org.terracotta.statistics.Statistic;
import org.terracotta.statistics.StatisticsManager;
import org.terracotta.statistics.derived.EventRateSimpleMovingAverage;
import org.terracotta.statistics.derived.OperationResultFilter;
import org.terracotta.statistics.observer.OperationObserver;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static net.sf.ehcache.statistics.StatisticBuilder.operation;
/**
* A Store implementation suitable for fast, concurrent in memory stores. The policy is determined by that
* configured in the cache.
*
* @author Terracotta
* @version $Id: MemoryStore.java 9094 2014-06-26 19:30:52Z alexsnaps $
* @version $Id: MemoryStore.java 9094 2014-06-26 19:30:52Z alexsnaps $
*/
public class MemoryStore extends AbstractStore implements CacheConfigurationListener, Store {
/**
* This is the default from {@link java.util.concurrent.ConcurrentHashMap}. It should never be used, because we size
* the map to the max size of the store.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* Set optimisation for 100 concurrent threads.
*/
private static final int CONCURRENCY_LEVEL = 100;
private static final int MAX_EVICTION_RATIO = 5;
private static final Logger LOG = LoggerFactory.getLogger(MemoryStore.class.getName());
/**
* Eviction outcome observer
*/
protected final OperationObserver evictionObserver = operation(EvictionOutcome.class).named("eviction").of(this).build();
/**
* The cache this store is associated with.
*/
private final Ehcache cache;
/**
* Map where items are stored by key.
*/
private final SelectableConcurrentHashMap map;
private final PoolAccessor poolAccessor;
private final OperationObserver getObserver = operation(GetOutcome.class).named("get").of(this).tag("local-heap").build();
private final OperationObserver putObserver = operation(PutOutcome.class).named("put").of(this).tag("local-heap").build();
private final OperationObserver removeObserver = operation(RemoveOutcome.class).named("remove").of(this).tag("local-heap").build();
private final boolean storePinned;
/**
* The maximum size of the store (0 == no limit)
*/
private volatile int maximumSize;
/**
* status.
*/
private volatile Status status;
/**
* The eviction policy to use
*/
private volatile Policy policy;
/**
* The pool accessor
*/
private volatile CacheLockProvider lockProvider;
/**
* Constructs things that all MemoryStores have in common.
*
* @param cache the cache
* @param pool the pool tracking the on-heap usage
* @param searchManager the search manager
*/
protected MemoryStore(Ehcache cache, Pool pool, BackingFactory factory, final SearchManager searchManager) {
super(searchManager, cache.getName());
status = Status.STATUS_UNINITIALISED;
this.cache = cache;
this.maximumSize = (int) cache.getCacheConfiguration().getMaxEntriesLocalHeap();
this.policy = determineEvictionPolicy(cache);
if (pool instanceof UnboundedPool) {
this.poolAccessor = pool.createPoolAccessor(null, null);
} else {
this.poolAccessor = pool.createPoolAccessor(new Participant(),
SizeOfPolicyConfiguration.resolveMaxDepth(cache),
SizeOfPolicyConfiguration.resolveBehavior(cache).equals(SizeOfPolicyConfiguration.MaxDepthExceededBehavior.ABORT));
}
this.storePinned = determineStorePinned(cache.getCacheConfiguration());
int maximumCapacity = isClockEviction() && !storePinned ? maximumSize : 0;
RegisteredEventListeners eventListener = cache.getCacheEventNotificationService();
if (Boolean.getBoolean(MemoryStore.class.getName() + ".presize")) {
// create the CHM with initialCapacity sufficient to hold maximumSize
final float loadFactor = maximumSize == 1 ? 1 : DEFAULT_LOAD_FACTOR;
int initialCapacity = getInitialCapacityForLoadFactor(maximumSize, loadFactor);
this.map = factory.newBackingMap(poolAccessor, initialCapacity,
loadFactor, CONCURRENCY_LEVEL, maximumCapacity, eventListener);
} else {
this.map = factory.newBackingMap(poolAccessor, CONCURRENCY_LEVEL, maximumCapacity, eventListener);
}
this.status = Status.STATUS_ALIVE;
if (LOG.isDebugEnabled()) {
LOG.debug("Initialized " + this.getClass().getName() + " for " + cache.getName());
}
}
private boolean determineStorePinned(CacheConfiguration cacheConfiguration) {
PinningConfiguration pinningConfiguration = cacheConfiguration.getPinningConfiguration();
if (pinningConfiguration == null) {
return false;
}
switch (pinningConfiguration.getStore()) {
case LOCALMEMORY:
return false;
case INCACHE:
return !cacheConfiguration.isOverflowToOffHeap() && !cacheConfiguration.isOverflowToDisk();
default:
throw new IllegalArgumentException();
}
}
/**
* Calculates the initialCapacity for a desired maximumSize goal and loadFactor.
*
* @param maximumSizeGoal the desired maximum size goal
* @param loadFactor the load factor
* @return the calculated initialCapacity. Returns 0 if the parameter maximumSizeGoal is less than or equal
* to 0
*/
protected static int getInitialCapacityForLoadFactor(int maximumSizeGoal, float loadFactor) {
double actualMaximum = Math.ceil(maximumSizeGoal / loadFactor);
return Math.max(0, actualMaximum >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) actualMaximum);
}
/**
* A factory method to create a MemoryStore.
*
* @param cache the cache
* @param pool the pool tracking the on-heap usage
* @return an instance of a NotifyingMemoryStore, configured with the appropriate eviction policy
*/
public static Store create(final Ehcache cache, Pool pool) {
CacheConfiguration cacheConfiguration = cache.getCacheConfiguration();
final BruteForceSearchManager searchManager = new BruteForceSearchManager(cache);
MemoryStore memoryStore = new MemoryStore(cache, pool, new BasicBackingFactory(), searchManager);
cacheConfiguration.addConfigurationListener(memoryStore);
searchManager.setBruteForceSource(createBruteForceSource(memoryStore, cache.getCacheConfiguration()));
return memoryStore;
}
/**
* Factory method to wrap the MemoryStore into a BruteForceSource, accounting for transactional and copy
* configuration
*
* @param memoryStore the underlying store acting as source
* @param cacheConfiguration the cache configuration
* @return a BruteForceSource connected to underlying MemoryStore and matching configuration
*/
protected static BruteForceSource createBruteForceSource(MemoryStore memoryStore, CacheConfiguration cacheConfiguration) {
BruteForceSource source = new MemoryStoreBruteForceSource(memoryStore, cacheConfiguration.getSearchable());
CopyStrategyHandler copyStrategyHandler = new CopyStrategyHandler(cacheConfiguration.isCopyOnRead(),
cacheConfiguration.isCopyOnWrite(),
cacheConfiguration.getCopyStrategy(), cacheConfiguration.getClassLoader());
if (cacheConfiguration.getTransactionalMode().isTransactional()) {
source = new TransactionalBruteForceSource(source, copyStrategyHandler);
} else if (cacheConfiguration.isCopyOnRead() || cacheConfiguration.isCopyOnWrite()) {
source = new CopyingBruteForceSource(source, copyStrategyHandler);
}
return source;
}
/**
* Puts an item in the store. Note that this automatically results in an eviction if the store is full.
*
* @param element the element to add
*/
public boolean put(final Element element) throws CacheException {
if (element == null) {
return false;
}
if (searchManager != null) {
searchManager.put(cache.getName(), -1, element, null, attributeExtractors, cache.getCacheConfiguration().getDynamicExtractor());
}
putObserver.begin();
long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), storePinned);
if (delta > -1) {
Element old = map.put(element.getObjectKey(), element, delta);
checkCapacity(element);
if (old == null) {
putObserver.end(PutOutcome.ADDED);
return true;
} else {
putObserver.end(PutOutcome.UPDATED);
return false;
}
} else {
notifyDirectEviction(element);
putObserver.end(PutOutcome.ADDED);
return true;
}
}
/**
* {@inheritDoc}
*/
public boolean putWithWriter(Element element, CacheWriterManager writerManager) throws CacheException {
if (searchManager != null) {
searchManager.put(cache.getName(), -1, element, null, attributeExtractors, cache.getCacheConfiguration().getDynamicExtractor());
}
long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), storePinned);
if (delta > -1) {
final ReentrantReadWriteLock lock = map.lockFor(element.getObjectKey());
lock.writeLock().lock();
try {
Element old = map.put(element.getObjectKey(), element, delta);
if (writerManager != null) {
try {
writerManager.put(element);
} catch (RuntimeException e) {
throw new StoreUpdateException(e, old != null);
}
}
checkCapacity(element);
return old == null;
} finally {
lock.writeLock().unlock();
}
} else {
notifyDirectEviction(element);
return true;
}
}
/**
* Gets an item from the cache.
*
* The last access time in {@link net.sf.ehcache.Element} is updated.
*
* @param key the key of the Element
* @return the element, or null if there was no match for the key
*/
public final Element get(final Object key) {
getObserver.begin();
if (key == null) {
getObserver.end(GetOutcome.MISS);
return null;
} else {
final Element e = map.get(key);
if (e == null) {
getObserver.end(GetOutcome.MISS);
return null;
} else {
getObserver.end(GetOutcome.HIT);
return e;
}
}
}
/**
* Gets an item from the cache, without updating statistics.
*
* @param key the cache key
* @return the element, or null if there was no match for the key
*/
public final Element getQuiet(Object key) {
return map.get(key);
}
/**
* Removes an Element from the store.
*
* @param key the key of the Element, usually a String
* @return the Element if one was found, else null
*/
public Element remove(final Object key) {
if (key == null) {
return null;
}
removeObserver.begin();
try {
return map.remove(key);
} finally {
removeObserver.end(RemoveOutcome.SUCCESS);
}
}
/**
* {@inheritDoc}
*/
public final Element removeWithWriter(Object key, CacheWriterManager writerManager) throws CacheException {
if (key == null) {
return null;
}
// remove single item.
Element element;
final ReentrantReadWriteLock.WriteLock writeLock = map.lockFor(key).writeLock();
writeLock.lock();
try {
element = map.remove(key);
if (writerManager != null) {
writerManager.remove(new CacheEntry(key, element));
}
} finally {
writeLock.unlock();
}
if (element == null && LOG.isDebugEnabled()) {
LOG.debug(cache.getName() + "Cache: Cannot remove entry as key " + key + " was not found");
}
return element;
}
/**
* Memory stores are never backed up and always return false
*/
public final boolean bufferFull() {
return false;
}
/**
* Expire all elements.
*
* This is a default implementation which does nothing. Expiration on demand is only implemented for disk stores.
*/
public void expireElements() {
for (Object key : keySet()) {
final Element element = expireElement(key);
if (element != null) {
cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
}
}
}
/**
* Evicts the element for the given key, if it exists and is expired
* @param key the key
* @return the evicted element, if any. Otherwise null
*/
protected Element expireElement(final Object key) {
Element value = get(key);
return value != null && value.isExpired() && map.remove(key, value) ? value : null;
}
/**
* Chooses the Policy from the cache configuration
* @param cache the cache
* @return the chosen eviction policy
*/
static Policy determineEvictionPolicy(Ehcache cache) {
MemoryStoreEvictionPolicy policySelection = cache.getCacheConfiguration().getMemoryStoreEvictionPolicy();
if (policySelection.equals(MemoryStoreEvictionPolicy.LRU)) {
return new LruPolicy();
} else if (policySelection.equals(MemoryStoreEvictionPolicy.FIFO)) {
return new FifoPolicy();
} else if (policySelection.equals(MemoryStoreEvictionPolicy.LFU)) {
return new LfuPolicy();
} else if (policySelection.equals(MemoryStoreEvictionPolicy.CLOCK)) {
return null;
}
throw new IllegalArgumentException(policySelection + " isn't a valid eviction policy");
}
/**
* Remove all of the elements from the store.
*/
public final void removeAll() throws CacheException {
for (Object key : map.keySet()) {
remove(key);
}
}
/**
* Prepares for shutdown.
*/
public synchronized void dispose() {
if (status.equals(Status.STATUS_SHUTDOWN)) {
return;
}
status = Status.STATUS_SHUTDOWN;
flush();
poolAccessor.unlink();
}
/**
* Flush to disk only if the cache is diskPersistent.
*/
public void flush() {
if (cache.getCacheConfiguration().isClearOnFlush()) {
removeAll();
}
}
/**
* Gets an Array of the keys for all elements in the memory cache.
*
* Does not check for expired entries
*
* @return An List
*/
public final List> getKeys() {
return new ArrayList