2
* Copyright 2003-2009 Terracotta, Inc.
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
* you may not use this file except in compliance with the License.
6
* You may obtain a copy of the License at
8
* http://www.apache.org/licenses/LICENSE-2.0
10
* Unless required by applicable law or agreed to in writing, software
11
* distributed under the License is distributed on an "AS IS" BASIS,
12
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
* See the License for the specific language governing permissions and
14
* limitations under the License.
17
package net.sf.ehcache.store;
19
import net.sf.ehcache.CacheException;
20
import net.sf.ehcache.Ehcache;
21
import net.sf.ehcache.Element;
22
import net.sf.ehcache.Status;
24
import java.util.Iterator;
26
import java.util.logging.Level;
27
import java.util.logging.Logger;
31
* An implementation of a LruMemoryStore.
33
* This uses {@link java.util.LinkedHashMap} as its backing map. It uses the {@link java.util.LinkedHashMap} LRU
34
* feature. LRU for this implementation means least recently accessed.
36
* @author <a href="mailto:gluck@thoughtworks.com">Greg Luck</a>
39
public class LruMemoryStore implements Store {
41
private static final Logger LOG = Logger.getLogger(LruMemoryStore.class.getName());
44
* The cache this store is associated with.
46
protected Ehcache cache;
49
* Map where items are stored by key.
54
* The DiskStore associated with this MemoryStore.
56
protected final Store diskStore;
61
protected Status status;
64
* The maximum size of the store
66
protected int maximumSize;
70
* Constructor for the LruMemoryStore object
71
* The backing {@link java.util.LinkedHashMap} is created with LRU by access order.
73
public LruMemoryStore(Ehcache cache, Store diskStore) {
74
status = Status.STATUS_UNINITIALISED;
75
this.maximumSize = cache.getCacheConfiguration().getMaxElementsInMemory();
77
this.diskStore = diskStore;
78
map = new SpoolingLinkedHashMap();
79
status = Status.STATUS_ALIVE;
84
* Puts an item in the cache. Note that this automatically results in
85
* {@link net.sf.ehcache.store.LruMemoryStore.SpoolingLinkedHashMap#removeEldestEntry} being called.
87
* @param element the element to add
89
public final synchronized void put(Element element) throws CacheException {
90
if (element != null) {
91
map.put(element.getObjectKey(), element);
97
* Allow specialised actions over adding the element to the map.
101
protected void doPut(Element element) throws CacheException {
106
* Gets an item from the cache.
108
* The last access time in {@link net.sf.ehcache.Element} is updated.
110
* @param key the cache key
111
* @return the element, or null if there was no match for the key
113
public final synchronized Element get(Object key) {
114
Element element = (Element) map.get(key);
116
if (element != null) {
117
element.updateAccessStatistics();
123
* Gets an item from the cache, without updating statistics.
125
* @param key the cache key
126
* @return the element, or null if there was no match for the key
128
public final synchronized Element getQuiet(Object key) {
129
Element cacheElement = (Element) map.get(key);
131
// if (cacheElement != null) {
132
//cacheElement.updateAccessStatistics(); Don't update statistics
139
* Removes an Element from the store.
141
* @param key the key of the Element, usually a String
142
* @return the Element if one was found, else null
144
public final synchronized Element remove(Object key) {
146
// remove single item.
147
Element element = (Element) map.remove(key);
148
if (element != null) {
156
* Remove all of the elements from the store.
158
public final synchronized void removeAll() throws CacheException {
163
* Clears any data structures and places it back to its state when it was first created.
165
protected final void clear() {
170
* Prepares for shutdown.
172
public final synchronized void dispose() {
173
if (status.equals(Status.STATUS_SHUTDOWN)) {
176
status = Status.STATUS_SHUTDOWN;
179
//release reference to cache
185
* Flush to disk only if the cache is diskPersistent.
187
public final void flush() {
188
if (cache.getCacheConfiguration().isDiskPersistent()) {
189
if (LOG.isLoggable(Level.FINE)) {
190
LOG.log(Level.FINE, cache.getName() + " is persistent. Spooling " + map.size() + " elements to the disk store.");
195
//should be emptied if clearOnFlush is true
196
if (cache.getCacheConfiguration().isClearOnFlush()) {
203
* Spools all elements to disk, in preparation for shutdown.
205
* This revised implementation is a little slower but avoids using increased memory during the method.
207
protected final void spoolAllToDisk() {
208
boolean clearOnFlush = cache.getCacheConfiguration().isClearOnFlush();
209
Object[] keys = getKeyArray();
210
for (Object key : keys) {
211
Element element = (Element) map.get(key);
212
if (element != null) {
213
if (!element.isSerializable()) {
214
if (LOG.isLoggable(Level.FINE)) {
215
LOG.log(Level.FINE, "Object with key " + element.getObjectKey()
216
+ " is not Serializable and is not being overflowed to disk.");
219
spoolToDisk(element);
220
//Don't notify listeners. They are not being removed from the cache, only a store
221
//Leave it in the memory store for performance if do not want to clear on flush
231
* Puts the element in the DiskStore.
232
* Should only be called if isOverflowToDisk is true
234
* Relies on being called from a synchronized method
236
* @param element The Element
238
protected void spoolToDisk(Element element) {
239
diskStore.put(element);
240
if (LOG.isLoggable(Level.FINE)) {
241
LOG.log(Level.FINE, cache.getName() + "Cache: spool to disk done for: " + element.getObjectKey());
246
* Gets the status of the MemoryStore.
248
public final Status getStatus() {
253
* Gets an Array of the keys for all elements in the memory cache.
255
* Does not check for expired entries
257
* @return An Object[]
259
public final synchronized Object[] getKeyArray() {
260
return map.keySet().toArray();
264
* Returns the current cache size.
266
* @return The size value
268
public final int getSize() {
274
* An unsynchronized check to see if a key is in the Store. No check is made to see if the Element is expired.
276
* @param key The Element key
277
* @return true if found. If this method return false, it means that an Element with the given key is definitely not in the MemoryStore.
278
* If it returns true, there is an Element there. An attempt to get it may return null if the Element has expired.
280
public final boolean containsKey(Object key) {
281
return map.containsKey(key);
286
* Measures the size of the memory store by measuring the serialized size of all elements.
287
* If the objects are not Serializable they count as 0.
289
* Warning: This method can be very expensive to run. Allow approximately 1 second
290
* per 1MB of entries. Running this method could create liveness problems
291
* because the object lock is held for a long period
293
* @return the size, in bytes
295
public final synchronized long getSizeInBytes() throws CacheException {
296
long sizeInBytes = 0;
297
for (Iterator iterator = map.values().iterator(); iterator.hasNext();) {
298
Element element = (Element) iterator.next();
299
if (element != null) {
300
sizeInBytes += element.getSerializedSize();
308
* Evict the <code>Element</code>.
310
* Evict means that the <code>Element</code> is:
312
* <li>if, the store is diskPersistent, the <code>Element</code> is spooled to the DiskStore
313
* <li>if not, the <code>Element</code> is removed.
316
* @param element the <code>Element</code> to be evicted.
318
protected final void evict(Element element) throws CacheException {
319
boolean spooled = false;
320
if (cache.getCacheConfiguration().isOverflowToDisk()) {
321
if (!element.isSerializable()) {
322
if (LOG.isLoggable(Level.FINE)) {
323
LOG.log(Level.FINE, new StringBuffer("Object with key ").append(element.getObjectKey())
324
.append(" is not Serializable and cannot be overflowed to disk").toString());
327
spoolToDisk(element);
333
cache.getCacheEventNotificationService().notifyElementEvicted(element, false);
338
* Before eviction elements are checked.
342
protected final void notifyExpiry(Element element) {
343
cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
348
* An algorithm to tell if the MemoryStore is at or beyond its carrying capacity.
350
protected final boolean isFull() {
351
return map.size() > maximumSize;
355
* Expire all elsments.
357
* This is a default implementation which does nothing. Expiry on demand is only
358
* implemented for disk stores.
360
public void expireElements() {
361
//empty implementation
365
* Memory stores are never backed up and always return false
367
public boolean bufferFull() {
373
* Package local access to the map for testing
375
Map getBackingMap() {
380
* An extension of LinkedHashMap which overrides {@link #removeEldestEntry}
381
* to persist cache entries to the auxiliary cache before they are removed.
383
* This implementation also provides LRU by access order.
385
public final class SpoolingLinkedHashMap extends java.util.LinkedHashMap {
386
private static final int INITIAL_CAPACITY = 100;
387
private static final float GROWTH_FACTOR = .75F;
390
* Default constructor.
391
* Will create an initial capacity of 100, a loading of .75 and
392
* LRU by access order.
394
public SpoolingLinkedHashMap() {
395
super(INITIAL_CAPACITY, GROWTH_FACTOR, true);
399
* Returns <tt>true</tt> if this map should remove its eldest entry.
400
* This method is invoked by <tt>put</tt> and <tt>putAll</tt> after
401
* inserting a new entry into the map. It provides the implementer
402
* with the opportunity to remove the eldest entry each time a new one
403
* is added. This is useful if the map represents a cache: it allows
404
* the map to reduce memory consumption by deleting stale entries.
406
* Will return true if:
408
* <li> the element has expired
409
* <li> the cache size is greater than the in-memory actual.
410
* In this case we spool to disk before returning.
413
* @param eldest The least recently inserted entry in the map, or if
414
* this is an access-ordered map, the least recently accessed
415
* entry. This is the entry that will be removed it this
416
* method returns <tt>true</tt>. If the map was empty prior
417
* to the <tt>put</tt> or <tt>putAll</tt> invocation resulting
418
* in this invocation, this will be the entry that was just
419
* inserted; in other words, if the map contains a single
420
* entry, the eldest entry is also the newest.
421
* @return true if the eldest entry should be removed
422
* from the map; <tt>false</t> if it should be retained.
424
protected final boolean removeEldestEntry(Map.Entry eldest) {
425
Element element = (Element) eldest.getValue();
426
return removeLeastRecentlyUsedElement(element);
430
* Relies on being called from a synchronized method
433
* @return true if the LRU element should be removed
435
private boolean removeLeastRecentlyUsedElement(Element element) throws CacheException {
436
//check for expiry and remove before going to the trouble of spooling it
437
if (element.isExpired()) {
438
notifyExpiry(element);
454
* @return the current eviction policy. This may not be the configured policy, if it has been
456
* @see #setEvictionPolicy(Policy)
458
public Policy getEvictionPolicy() {
459
return new LruPolicy();
463
* Sets the eviction policy strategy. The Store will use a policy at startup. The store may allow changing
464
* the eviction policy strategy dynamically. Otherwise implementations will throw an exception if this method
467
* @param policy the new policy
469
public void setEvictionPolicy(Policy policy) {
470
throw new UnsupportedOperationException("This store is LRU only. It does not support changing the eviction" +