Ehcache has two stores:
The MemoryStore is always enabled. It is not directly manipulated, but is a component of every cache.
All Elements are suitable for placement in the MemoryStore.
It has the following characteristics:
Thread safe for use by multiple concurrent threads.
Tested for memory leaks. See MemoryCacheTest#testMemoryLeak. This test passes for ehcache but exploits a number of memory leaks in JCS. JCS will give an OutOfMemory error with a default 64M in 10 seconds.
LinkedHashMap The MemoryStore for JDK1.4 and JDK 5 it is backed by an extended LinkedHashMap. This provides a combined linked list and a hash map, and is ideally suited for caching. Using this standard Java class simplifies the implementation of the memory cache. It directly supports obtaining the least recently used element.
For JDK1.2 and JDK1.3, the LRUMap from Apache Commons is used. It provides similar features to LinkedHashMap.
The implementation is determined dynamically at runtime. LinkedHashMap is preferred if found in the classpath.
The memory store, being all in memory, is the fastest caching option.
All caches specify their maximum in-memory size, in terms of the number of elements, at configuration time.
When an element is added to a cache and it goes beyond its maximum memory size, an existing element is either deleted, if overflowToDisk is false, or evaluated for spooling to disk, if overflowToDisk is true. In the latter case, a check for expiry is carried out. If it is expired it is deleted; if not it is spooled. The eviction of an item from the memory store is based on the MemoryStoreEvictionPolicy setting specified in the configuration file.
memoryStoreEvictionPolicy is an optional attribute in ehcache.xml introduced since 1.2. Legal values are LRU (default), LFU and FIFO.
LRU, LFU and FIFO eviction policies are supported. LRU is the default, consistent with all earlier releases of ehcache.
The eldest element, is the Least Recently Used (LRU). The last used timestamp is updated when an element is put into the cache or an element is retrieved from the cache with a get call.
For each get call on the element the number of hits is updated. When a put call is made for a new element (and assuming that the max limit is reached for the memory store) the element with least number of hits, the Less Frequently Used element, is evicted.
Elements are evicted in the same order as they come in. When a put call is made for a new element (and assuming that the max limit is reached for the memory store) the element that was placed first (First-In) in the store is the candidate for eviction (First-Out).
For all the eviction policies there are also putQuiet and getQuiet methods which do not update the last used timestamp.
When there is a get or a getQuiet on an element, it is checked for expiry. If expired, it is removed and null is returned.
Note that at any point in time there will usually be some expired elements in the cache. Memory sizing of an application must always take into account the maximum size of each cache. There is a convenience method which can provide an estimate of the size in bytes of the MemoryStore. See calculateInMemorySize(). It returns the serialized size of the cache. Do not use this method in production. It is very slow. It is only meant to provide a rough estimate.
The alternative would have been to have an expiry thread. This is a trade-off between lower memory use and short locking periods and cpu utilisation. The design is in favour of the latter. For those concerned with memory use, simply reduce the maxElementsInMemory.
The DiskStore provides a disk spooling facility.
Only Elements which are Serializable can be placed in the DiskStore. Any non serializable Elements which attempt to overflow to the DiskStore will be removed instead, and a WARNING level log message emitted.
It has the following characteristics:
The disk store creates one file per cache called "cache name.data".
If the DiskStore is configured to be persistent, a "cache name.index" file is also created.
Files are created in the directory specified by the diskStore configuration element. The default configuration is "java.io.tmpdir", which causes files to be created in the system's temporary directory.
Following is a list of Java system properties which are supported as values for diskStore:
Apart from these, any directory can be specified using syntax appropriate to the operating system. e.g. for Unix "/home/application/cache".
One thread per cache is used to remove expired elements. The optional attribute diskExpiryThreadIntervalSeconds sets the interval between runs of the expiry thread. Warning: setting this to a low value is not recommended. It can cause excessive DiskStore locking and high cpu utilisation. The default value is 120 seconds.
Only Serializable objects can be stored in a DiskStore. A NotSerializableException will be thrown if the object is not serializable.
DiskStores are thread safe.
DiskStore persistence is controlled by the diskPersistent configuration element. If false or omitted, DiskStores will not persist between CacheManager restarts. The data file for each cache will be deleted, if it exists, both on shutdown and startup. No data from a previous instance CacheManager is available.
If diskPersistent is true, the data file, and an index file, are saved. Cache Elements are available to a new CacheManager. This CacheManager may be in the same VM instance, or a new one.
The data file is updated continuously during operation of the Disk Store. New elements are spooled to disk, and deleted when expired. The index file is only written when dispose is called on the DiskStore. This happens when the CacheManager is shut down, a Cache is disposed, or the VM is being shut down. It is recommended that the CacheManager shutdown() method be used. See Virtual Machine Shutdown Considerations for guidance on how to safely shut the Virtual Machine down.
When a DiskStore is persisted, the following steps take place:
On startup the following steps take place:
These actions favour safety over persistence. Ehcache is a cache, not a database. If a file gets dirty, all data is deleted. Once started there is further checking for corruption. When a get is done, if the Element cannot be successfully derserialized, it is deleted, and null is returned. These measures prevent corrupt and inconsistent data being returned.
Expiring an element frees its space on the file. This space is available for reuse by new elements. The element is also removed from the in-memory index of elements.
Spool requests are placed in-memory and then asynchronously written to disk. There is one thread per cache. An in-memory index of elements on disk is maintained to quickly resolve whether a key exists on disk, and if so to seek it and read it.
Writes to and from the disk use ObjectInputStream and the Java serialization mechanism. This is not required for the MemoryStore. As a result the DiskStore can never be as fast as the MemoryStore.
Serialization speed is affected by the size of the objects being serialized and their type. It has been found in the ElementTest test that:
Byte arrays are 20 times faster to serialize. Make use of byte arrays to increase DiskStore performance.
One option to speed up disk stores is to use a RAM file system. On some operating systems there are a plethora of file systems to choose from. For example, the Disk Cache has been successfully used with Linux' RAMFS file system. This file system simply consists of memory. Linux presents it as a file system. The Disk Cache treats it like a normal disk - it is just way faster. With this type of file system, object serialization becomes the limiting factor to performance.