1
16
17 package net.sf.ehcache.store;
18
19 import net.sf.ehcache.CacheEntry;
20 import net.sf.ehcache.CacheException;
21 import net.sf.ehcache.Ehcache;
22 import net.sf.ehcache.Element;
23 import net.sf.ehcache.Status;
24 import net.sf.ehcache.concurrent.CacheLockProvider;
25 import net.sf.ehcache.concurrent.ReadWriteLockSync;
26 import net.sf.ehcache.concurrent.Sync;
27 import net.sf.ehcache.config.CacheConfiguration;
28 import net.sf.ehcache.config.CacheConfigurationListener;
29 import net.sf.ehcache.config.PinningConfiguration;
30 import net.sf.ehcache.config.SizeOfPolicyConfiguration;
31 import net.sf.ehcache.event.RegisteredEventListeners;
32 import net.sf.ehcache.pool.Pool;
33 import net.sf.ehcache.pool.PoolAccessor;
34 import net.sf.ehcache.pool.PoolableStore;
35 import net.sf.ehcache.pool.Size;
36 import net.sf.ehcache.pool.impl.DefaultSizeOfEngine;
37 import net.sf.ehcache.store.chm.SelectableConcurrentHashMap;
38 import net.sf.ehcache.store.disk.StoreUpdateException;
39 import net.sf.ehcache.util.ratestatistics.AtomicRateStatistic;
40 import net.sf.ehcache.util.ratestatistics.RateStatistic;
41 import net.sf.ehcache.writer.CacheWriterManager;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44
45 import java.util.ArrayList;
46 import java.util.Collection;
47 import java.util.List;
48 import java.util.Set;
49 import java.util.concurrent.TimeUnit;
50 import java.util.concurrent.locks.Lock;
51
52
59 public class MemoryStore extends AbstractStore implements TierableStore, PoolableStore, CacheConfigurationListener {
60
61
65 static final float DEFAULT_LOAD_FACTOR = 0.75f;
66
67
70 private static final int CONCURRENCY_LEVEL = 100;
71
72 private static final int MAX_EVICTION_RATIO = 5;
73
74 private static final Logger LOG = LoggerFactory.getLogger(MemoryStore.class.getName());
75
76 private final boolean alwaysPutOnHeap;
77
78
81 private final Ehcache cache;
82
83
86 private final SelectableConcurrentHashMap map;
87 private final PoolAccessor poolAccessor;
88
89 private final RateStatistic hitRate = new AtomicRateStatistic(1000, TimeUnit.MILLISECONDS);
90 private final RateStatistic missRate = new AtomicRateStatistic(1000, TimeUnit.MILLISECONDS);
91
92 private final boolean storePinned;
93 private final boolean elementPinningEnabled;
94
95
98 private volatile int maximumSize;
99
100
103 private volatile Status status;
104
105
108 private volatile Policy policy;
109
110
113
114 private volatile CacheLockProvider lockProvider;
115
116
123 protected MemoryStore(Ehcache cache, Pool pool, boolean notify, BackingFactory factory) {
124 status = Status.STATUS_UNINITIALISED;
125 this.cache = cache;
126 this.maximumSize = (int) cache.getCacheConfiguration().getMaxEntriesLocalHeap();
127 this.policy = determineEvictionPolicy(cache);
128
129 this.poolAccessor = pool.createPoolAccessor(this,
130 SizeOfPolicyConfiguration.resolveMaxDepth(cache),
131 SizeOfPolicyConfiguration.resolveBehavior(cache).equals(SizeOfPolicyConfiguration.MaxDepthExceededBehavior.ABORT));
132
133 this.alwaysPutOnHeap = getAdvancedBooleanConfigProperty("alwaysPutOnHeap", cache.getCacheConfiguration().getName(), false);
134 this.storePinned = determineStorePinned(cache.getCacheConfiguration());
135
136 this.elementPinningEnabled = !cache.getCacheConfiguration().isOverflowToOffHeap();
137 int maximumCapacity = isClockEviction() && !storePinned ? maximumSize : 0;
138 RegisteredEventListeners eventListener = notify ? cache.getCacheEventNotificationService() : null;
139 if (Boolean.getBoolean(MemoryStore.class.getName() + ".presize")) {
140
141 final float loadFactor = maximumSize == 1 ? 1 : DEFAULT_LOAD_FACTOR;
142 int initialCapacity = getInitialCapacityForLoadFactor(maximumSize, loadFactor);
143 this.map = factory.newBackingMap(poolAccessor, elementPinningEnabled, initialCapacity,
144 loadFactor, CONCURRENCY_LEVEL, maximumCapacity, eventListener);
145 } else {
146 this.map = factory.newBackingMap(poolAccessor, elementPinningEnabled, CONCURRENCY_LEVEL, maximumCapacity, eventListener);
147 }
148
149 this.status = Status.STATUS_ALIVE;
150
151 if (LOG.isDebugEnabled()) {
152 LOG.debug("Initialized " + this.getClass().getName() + " for " + cache.getName());
153 }
154 }
155
156 private boolean determineStorePinned(CacheConfiguration cacheConfiguration) {
157 PinningConfiguration pinningConfiguration = cacheConfiguration.getPinningConfiguration();
158 if (pinningConfiguration == null) {
159 return false;
160 }
161
162 switch (pinningConfiguration.getStore()) {
163 case LOCALHEAP:
164 return true;
165
166 case LOCALMEMORY:
167 return !cacheConfiguration.isOverflowToOffHeap();
168
169 case INCACHE:
170 return !cacheConfiguration.isOverflowToOffHeap() && !cacheConfiguration.isOverflowToDisk();
171
172 default:
173 throw new IllegalArgumentException();
174 }
175 }
176
177
185 protected static int getInitialCapacityForLoadFactor(int maximumSizeGoal, float loadFactor) {
186 double actualMaximum = Math.ceil(maximumSizeGoal / loadFactor);
187 return Math.max(0, actualMaximum >= Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) actualMaximum);
188 }
189
190
197 public static MemoryStore create(final Ehcache cache, Pool pool) {
198 MemoryStore memoryStore = new MemoryStore(cache, pool, false, new BasicBackingFactory());
199 cache.getCacheConfiguration().addConfigurationListener(memoryStore);
200 return memoryStore;
201 }
202
203
206 public void unpinAll() {
207 if (elementPinningEnabled) {
208 map.unpinAll();
209 }
210 }
211
212
215 public void setPinned(Object key, boolean pinned) {
216 if (elementPinningEnabled) {
217 map.setPinned(key, pinned);
218 }
219 }
220
221
224 public boolean isPinned(Object key) {
225 return elementPinningEnabled && map.isPinned(key);
226 }
227
228 private boolean isPinningEnabled(Element element) {
229 return storePinned || isPinned(element.getObjectKey());
230 }
231
232
235 public void fill(Element element) {
236 if (alwaysPutOnHeap || isPinningEnabled(element) || remove(element.getObjectKey()) != null || canPutWithoutEvicting(element)) {
237 put(element);
238 }
239 }
240
241
244 public boolean removeIfNotPinned(final Object key) {
245 return !storePinned && !isPinned(key) && remove(key) != null;
246 }
247
248
253 public boolean put(final Element element) throws CacheException {
254 if (element == null) {
255 return false;
256 }
257
258 long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
259 if (delta > -1) {
260 Element old = map.put(element.getObjectKey(), element, delta);
261 checkCapacity(element);
262 return old == null;
263 } else {
264 notifyDirectEviction(element);
265 return true;
266 }
267 }
268
269
272 public boolean putWithWriter(Element element, CacheWriterManager writerManager) throws CacheException {
273 long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
274 if (delta > -1) {
275 Element old = map.put(element.getObjectKey(), element, delta);
276 if (writerManager != null) {
277 try {
278 writerManager.put(element);
279 } catch (RuntimeException e) {
280 throw new StoreUpdateException(e, old != null);
281 }
282 }
283 checkCapacity(element);
284 return old == null;
285 } else {
286 notifyDirectEviction(element);
287 return true;
288 }
289 }
290
291
299 public final Element get(final Object key) {
300 if (key == null) {
301 return null;
302 } else {
303 final Element e = map.get(key);
304 if (e == null) {
305 missRate.event();
306 } else {
307 hitRate.event();
308 }
309 return e;
310 }
311 }
312
313
319 public final Element getQuiet(Object key) {
320 return get(key);
321 }
322
323
329 public Element remove(final Object key) {
330 if (key == null) {
331 return null;
332 }
333
334 return map.remove(key);
335 }
336
337
340 public void removeNoReturn(final Object key) {
341 remove(key);
342 }
343
344
347 public boolean isTierPinned() {
348 return storePinned;
349 }
350
351
354 public Set getPresentPinnedKeys() {
355 return map.pinnedKeySet();
356 }
357
358
361 public boolean isPersistent() {
362 return false;
363 }
364
365
368 public final Element removeWithWriter(Object key, CacheWriterManager writerManager) throws CacheException {
369 if (key == null) {
370 return null;
371 }
372
373
374 Element element = map.remove(key);
375 if (writerManager != null) {
376 writerManager.remove(new CacheEntry(key, element));
377 }
378 if (element == null && LOG.isDebugEnabled()) {
379 LOG.debug(cache.getName() + "Cache: Cannot remove entry as key " + key + " was not found");
380 }
381 return element;
382 }
383
384
387 public final boolean bufferFull() {
388 return false;
389 }
390
391
396 public void expireElements() {
397 for (Object key : map.keySet()) {
398 expireElement(key);
399 }
400 }
401
402
407 protected Element expireElement(final Object key) {
408 Element value = get(key);
409 return value != null && value.isExpired() && map.remove(key, value) ? value : null;
410 }
411
412
417 private static Policy determineEvictionPolicy(Ehcache cache) {
418 MemoryStoreEvictionPolicy policySelection = cache.getCacheConfiguration().getMemoryStoreEvictionPolicy();
419
420 if (policySelection.equals(MemoryStoreEvictionPolicy.LRU)) {
421 return new LruPolicy();
422 } else if (policySelection.equals(MemoryStoreEvictionPolicy.FIFO)) {
423 return new FifoPolicy();
424 } else if (policySelection.equals(MemoryStoreEvictionPolicy.LFU)) {
425 return new LfuPolicy();
426 } else if (policySelection.equals(MemoryStoreEvictionPolicy.CLOCK)) {
427 return null;
428 }
429
430 throw new IllegalArgumentException(policySelection + " isn't a valid eviction policy");
431 }
432
433
436 public final void removeAll() throws CacheException {
437 for (Object key : map.keySet()) {
438 remove(key);
439 }
440 }
441
442
445 public synchronized void dispose() {
446 if (status.equals(Status.STATUS_SHUTDOWN)) {
447 return;
448 }
449 status = Status.STATUS_SHUTDOWN;
450 flush();
451 poolAccessor.unlink();
452 }
453
454
457 public void flush() {
458 if (cache.getCacheConfiguration().isClearOnFlush()) {
459 removeAll();
460 }
461 }
462
463
470 public final List<?> getKeys() {
471 return new ArrayList<Object>(map.keySet());
472 }
473
474
478 protected Set<?> keySet() {
479 return map.keySet();
480 }
481
482
487 public final int getSize() {
488 return map.size();
489 }
490
491
496 public final int getTerracottaClusteredSize() {
497 return 0;
498 }
499
500
508 public final boolean containsKey(final Object key) {
509 return map.containsKey(key);
510 }
511
512
517 private void notifyExpiry(final Element element) {
518 cache.getCacheEventNotificationService().notifyElementExpiry(element, false);
519 }
520
521
526 protected void notifyDirectEviction(final Element element) {
527 }
528
529
534 public final boolean isFull() {
535 return maximumSize > 0 && map.quickSize() >= maximumSize;
536 }
537
538
544 public final boolean canPutWithoutEvicting(Element element) {
545 if (element == null) {
546 return true;
547 }
548
549 return !isFull() && poolAccessor.canAddWithoutEvicting(element.getObjectKey(), element.getObjectValue(), map.storedObject(element));
550 }
551
552
557 private void checkCapacity(final Element elementJustAdded) {
558 if (maximumSize > 0 && !isClockEviction()) {
559 int evict = Math.min(map.quickSize() - maximumSize, MAX_EVICTION_RATIO);
560 for (int i = 0; i < evict; i++) {
561 removeElementChosenByEvictionPolicy(elementJustAdded);
562 }
563 }
564 }
565
566
572 private boolean removeElementChosenByEvictionPolicy(final Element elementJustAdded) {
573
574 if (policy == null) {
575 return map.evict();
576 }
577
578 Element element = findEvictionCandidate(elementJustAdded);
579 if (element == null) {
580 LOG.debug("Eviction selection miss. Selected element is null");
581 return false;
582 }
583
584
585 if (element.isExpired()) {
586 remove(element.getObjectKey());
587 notifyExpiry(element);
588 return true;
589 }
590
591 if (isPinningEnabled(element)) {
592 return false;
593 }
594
595 return evict(element);
596 }
597
598
604 private Element findEvictionCandidate(final Element elementJustAdded) {
605 Object objectKey = elementJustAdded != null ? elementJustAdded.getObjectKey() : null;
606 Element[] elements = sampleElements(objectKey);
607
608 return policy.selectedBasedOnPolicy(elements, elementJustAdded);
609 }
610
611
619 private Element[] sampleElements(Object keyHint) {
620 int size = AbstractPolicy.calculateSampleSize(map.quickSize());
621 return map.getRandomValues(size, keyHint);
622 }
623
624
627 public Object getInternalContext() {
628 if (lockProvider != null) {
629 return lockProvider;
630 } else {
631 lockProvider = new LockProvider();
632 return lockProvider;
633 }
634 }
635
636
639 public final Status getStatus() {
640 return status;
641 }
642
643
646 public void timeToIdleChanged(long oldTti, long newTti) {
647
648 }
649
650
653 public void timeToLiveChanged(long oldTtl, long newTtl) {
654
655 }
656
657
660 public void diskCapacityChanged(int oldCapacity, int newCapacity) {
661
662 }
663
664
667 public void loggingChanged(boolean oldValue, boolean newValue) {
668
669 }
670
671
674 public void memoryCapacityChanged(int oldCapacity, int newCapacity) {
675 maximumSize = newCapacity;
676 if (isClockEviction() && !storePinned) {
677 map.setMaxSize(maximumSize);
678 }
679 }
680
681 private boolean isClockEviction() {
682 return policy == null;
683 }
684
685
688 public void registered(CacheConfiguration config) {
689
690 }
691
692
695 public void deregistered(CacheConfiguration config) {
696
697 }
698
699
702 public void maxBytesLocalHeapChanged(final long oldValue, final long newValue) {
703 this.poolAccessor.setMaxSize(newValue);
704 }
705
706
709 public void maxBytesLocalDiskChanged(final long oldValue, final long newValue) {
710
711 }
712
713
716 public boolean containsKeyInMemory(Object key) {
717 return containsKey(key);
718 }
719
720
723 public boolean containsKeyOffHeap(Object key) {
724 return false;
725 }
726
727
730 public boolean containsKeyOnDisk(Object key) {
731 return false;
732 }
733
734
737 public Policy getInMemoryEvictionPolicy() {
738 return policy;
739 }
740
741
744 public int getInMemorySize() {
745 return getSize();
746 }
747
748
751 public long getInMemorySizeInBytes() {
752 if (poolAccessor.getSize() < 0) {
753 DefaultSizeOfEngine defaultSizeOfEngine = new DefaultSizeOfEngine(
754 SizeOfPolicyConfiguration.resolveMaxDepth(cache),
755 SizeOfPolicyConfiguration.resolveBehavior(cache).equals(SizeOfPolicyConfiguration.MaxDepthExceededBehavior.ABORT)
756 );
757 long sizeInBytes = 0;
758 for (Object o : map.values()) {
759 Element element = (Element)o;
760 if (element != null) {
761 Size size = defaultSizeOfEngine.sizeOf(element.getObjectKey(), element, map.storedObject(element));
762 sizeInBytes += size.getCalculated();
763 }
764 }
765 return sizeInBytes;
766 }
767 return poolAccessor.getSize();
768 }
769
770
773 public int getOffHeapSize() {
774 return 0;
775 }
776
777
780 public long getOffHeapSizeInBytes() {
781 return 0;
782 }
783
784
787 public int getOnDiskSize() {
788 return 0;
789 }
790
791
794 public long getOnDiskSizeInBytes() {
795 return 0;
796 }
797
798
801 @Override
802 public boolean hasAbortedSizeOf() {
803 return poolAccessor.hasAbortedSizeOf();
804 }
805
806
809 public void setInMemoryEvictionPolicy(Policy policy) {
810 this.policy = policy;
811 }
812
813
816 public Element putIfAbsent(Element element) throws NullPointerException {
817 if (element == null) {
818 return null;
819 }
820
821 long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
822 if (delta > -1) {
823 Element old = map.putIfAbsent(element.getObjectKey(), element, delta);
824 if (old == null) {
825 checkCapacity(element);
826 } else {
827 poolAccessor.delete(delta);
828 }
829 return old;
830 } else {
831 notifyDirectEviction(element);
832 return null;
833 }
834 }
835
836
841 protected boolean evict(final Element element) {
842 final Element remove = remove(element.getObjectKey());
843 RegisteredEventListeners cacheEventNotificationService = cache.getCacheEventNotificationService();
844 final FrontEndCacheTier frontEndCacheTier = cacheEventNotificationService.getFrontEndCacheTier();
845 if (remove != null && frontEndCacheTier != null && frontEndCacheTier.notifyEvictionFromCache(remove.getKey())) {
846 cacheEventNotificationService.notifyElementEvicted(remove, false);
847 }
848 return remove != null;
849 }
850
851
854 public Element removeElement(Element element, ElementValueComparator comparator) throws NullPointerException {
855 if (element == null || element.getObjectKey() == null) {
856 return null;
857 }
858
859 Object key = element.getObjectKey();
860
861 Lock lock = getWriteLock(key);
862 lock.lock();
863 try {
864 Element toRemove = map.get(key);
865 if (comparator.equals(element, toRemove)) {
866 map.remove(key);
867 return toRemove;
868 } else {
869 return null;
870 }
871 } finally {
872 lock.unlock();
873 }
874 }
875
876
879 public boolean replace(Element old, Element element, ElementValueComparator comparator) throws NullPointerException,
880 IllegalArgumentException {
881 if (element == null || element.getObjectKey() == null) {
882 return false;
883 }
884
885 Object key = element.getObjectKey();
886
887 long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
888 if (delta > -1) {
889 Lock lock = getWriteLock(key);
890 lock.lock();
891 try {
892 Element toRemove = map.get(key);
893 if (comparator.equals(old, toRemove)) {
894 map.put(key, element, delta);
895 return true;
896 } else {
897 poolAccessor.delete(delta);
898 return false;
899 }
900 } finally {
901 lock.unlock();
902 }
903 } else {
904 notifyDirectEviction(element);
905 return false;
906 }
907 }
908
909
912 public Element replace(Element element) throws NullPointerException {
913 if (element == null || element.getObjectKey() == null) {
914 return null;
915 }
916
917 Object key = element.getObjectKey();
918
919 long delta = poolAccessor.add(element.getObjectKey(), element.getObjectValue(), map.storedObject(element), isPinningEnabled(element));
920 if (delta > -1) {
921 Lock lock = getWriteLock(key);
922 lock.lock();
923 try {
924 Element toRemove = map.get(key);
925 if (toRemove != null) {
926 map.put(key, element, delta);
927 return toRemove;
928 } else {
929 poolAccessor.delete(delta);
930 return null;
931 }
932 } finally {
933 lock.unlock();
934 }
935 } else {
936 notifyDirectEviction(element);
937 return null;
938 }
939 }
940
941
944 public Object getMBean() {
945 return null;
946 }
947
948
951 public boolean evictFromOnHeap(int count, long size) {
952 if (storePinned) {
953 return false;
954 }
955
956 for (int i = 0; i < count; i++) {
957 boolean removed = removeElementChosenByEvictionPolicy(null);
958 if (!removed) {
959 return false;
960 }
961 }
962 return true;
963 }
964
965
968 public boolean evictFromOnDisk(int count, long size) {
969 return false;
970 }
971
972
975 public float getApproximateDiskHitRate() {
976 return 0;
977 }
978
979
982 public float getApproximateDiskMissRate() {
983 return 0;
984 }
985
986
989 public long getApproximateDiskCountSize() {
990 return 0;
991 }
992
993
996 public long getApproximateDiskByteSize() {
997 return 0;
998 }
999
1000
1003 public float getApproximateHeapHitRate() {
1004 return hitRate.getRate();
1005 }
1006
1007
1010 public float getApproximateHeapMissRate() {
1011 return missRate.getRate();
1012 }
1013
1014
1017 public long getApproximateHeapCountSize() {
1018 return map.quickSize();
1019 }
1020
1021
1024 public long getApproximateHeapByteSize() {
1025 return poolAccessor.getSize();
1026 }
1027
1028 private Lock getWriteLock(Object key) {
1029 return map.lockFor(key).writeLock();
1030 }
1031
1032
1037 public Collection<Element> elementSet() {
1038 return map.values();
1039 }
1040
1041
1044 private class LockProvider implements CacheLockProvider {
1045
1046
1049 public Sync getSyncForKey(Object key) {
1050 return new ReadWriteLockSync(map.lockFor(key));
1051 }
1052 }
1053
1054 private static boolean getAdvancedBooleanConfigProperty(String property, String cacheName, boolean defaultValue) {
1055 String globalPropertyKey = "net.sf.ehcache.store.config." + property;
1056 String cachePropertyKey = "net.sf.ehcache.store." + cacheName + ".config." + property;
1057 return Boolean.parseBoolean(System.getProperty(cachePropertyKey, System.getProperty(globalPropertyKey, Boolean.toString(defaultValue))));
1058 }
1059
1060 @Override
1061 public void recalculateSize(Object key) {
1062 if (key == null) {
1063 return;
1064 }
1065 map.recalculateSize(key);
1066 }
1067
1068
1071 protected interface BackingFactory {
1072
1084 @Deprecated
1085 SelectableConcurrentHashMap newBackingMap(PoolAccessor<?> poolAccessor, boolean elementPinning, int initialCapacity,
1086 float loadFactor, int concurrency, int maximumCapacity, RegisteredEventListeners eventListener);
1087
1088
1097 SelectableConcurrentHashMap newBackingMap(PoolAccessor<?> poolAccessor, boolean elementPinning, int concurrency,
1098 int maximumCapacity, RegisteredEventListeners eventListener);
1099 }
1100
1101
1104 static class BasicBackingFactory implements BackingFactory {
1105
1106 @Override
1107 public SelectableConcurrentHashMap newBackingMap(PoolAccessor<?> poolAccessor, boolean elementPinning,
1108 int concurrency, int maximumCapacity, RegisteredEventListeners eventListener) {
1109 return new SelectableConcurrentHashMap(poolAccessor, elementPinning, concurrency, maximumCapacity, eventListener);
1110 }
1111
1112 @Override
1113 public SelectableConcurrentHashMap newBackingMap(PoolAccessor poolAccessor, boolean elementPinning, int initialCapacity,
1114 float loadFactor, int concurrency, int maximumCapacity, RegisteredEventListeners eventListener) {
1115 return new SelectableConcurrentHashMap(poolAccessor, elementPinning, initialCapacity,
1116 loadFactor, concurrency, maximumCapacity, eventListener);
1117 }
1118 }
1119 }
1120
1121