001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright
003: * notice. All rights reserved.
004: */
005: package com.tc.object.cache;
006:
007: import com.tc.lang.TCThreadGroup;
008: import com.tc.logging.TCLogger;
009: import com.tc.logging.TCLogging;
010: import com.tc.runtime.MemoryEventType;
011: import com.tc.runtime.MemoryEventsListener;
012: import com.tc.runtime.MemoryUsage;
013: import com.tc.runtime.TCMemoryManagerImpl;
014: import com.tc.util.Assert;
015: import com.tc.util.State;
016:
017: import java.util.List;
018:
019: public class CacheManager implements MemoryEventsListener {
020:
021: private static final TCLogger logger = TCLogging
022: .getLogger(CacheManager.class);
023:
024: private static final State INIT = new State("INIT");
025: private static final State PROCESSING = new State("PROCESSING");
026: private static final State COMPLETE = new State("COMPLETE");
027:
028: private final Evictable evictable;
029: private final CacheConfig config;
030: private final TCMemoryManagerImpl memoryManager;
031:
032: private int calculatedCacheSize = 0;
033: private CacheStatistics lastStat = null;
034:
035: public CacheManager(Evictable evictable, CacheConfig config,
036: TCThreadGroup threadGroup) {
037: this .evictable = evictable;
038: this .config = config;
039: this .memoryManager = new TCMemoryManagerImpl(config
040: .getUsedThreshold(), config.getUsedCriticalThreshold(),
041: config.getSleepInterval(), config.getLeastCount(),
042: config.isOnlyOldGenMonitored(), threadGroup);
043: this .memoryManager.registerForMemoryEvents(this );
044: if (config.getObjectCountCriticalThreshold() > 0) {
045: logger
046: .warn("Cache Object Count Critical threshold is set to "
047: + config.getObjectCountCriticalThreshold()
048: + ". It is not recommended that this value is set. Setting a wrong vlaue could totally destroy performance.");
049: }
050: }
051:
052: public void memoryUsed(MemoryEventType type, MemoryUsage usage) {
053: CacheStatistics cp = new CacheStatistics(type, usage);
054: evictable.evictCache(cp);
055: cp.validate();
056: addLastStat(cp);
057: }
058:
059: // currently we only maintain 1 last stat
060: private void addLastStat(CacheStatistics cp) {
061: this .lastStat = cp;
062: }
063:
064: private final class CacheStatistics implements CacheStats {
065: private final MemoryEventType type;
066: private final MemoryUsage usage;
067:
068: private int countBefore;
069: private int countAfter;
070: private int evicted;
071: private boolean objectsGCed = false;
072: private int toEvict;
073: private long startTime;
074: private State state = INIT;
075:
076: public CacheStatistics(MemoryEventType type, MemoryUsage usage) {
077: this .type = type;
078: this .usage = usage;
079: }
080:
081: public void validate() {
082: if (state == PROCESSING) {
083: // This might be ignored by the memory manager thread. TODO:: exit VM !!!
084: throw new AssertionError(
085: this
086: + " : Object Evicted is not called. This indicates a bug in the software !");
087: }
088: }
089:
090: public int getObjectCountToEvict(int currentCount) {
091: startTime = System.currentTimeMillis();
092: countBefore = currentCount;
093: adjustCachedObjectCount(currentCount);
094: toEvict = computeObjects2Evict(currentCount);
095: if (toEvict < 0 || toEvict > currentCount) {
096: //
097: throw new AssertionError(
098: "Computed Object to evict is out of range : toEvict = "
099: + toEvict + " currentCount = "
100: + currentCount + " " + this );
101: }
102: if (toEvict > 0) {
103: state = PROCESSING;
104: }
105: if (config.isLoggingEnabled()) {
106: logger.info("Asking to evict " + toEvict
107: + " current size = " + currentCount
108: + " calculated cache size = "
109: + calculatedCacheSize + " heap used = "
110: + usage.getUsedPercentage() + " % gc count = "
111: + usage.getCollectionCount());
112: }
113: return this .toEvict;
114: }
115:
116: //
117: // We recalibrate the calculatedCacheSize based on the current known details if one of the following is true.
118: // 0) Usage goes below threshold or
119: // 1) This is the first threshold crossing alarm or
120: // 2) A GC has taken place since the last time (we either base in on the collection count which is accurate in 1.5
121: // or in 1.4 we check to see if the usedMemory has gone down which is an indication of gc (not foolprove though)
122: //
123: private void adjustCachedObjectCount(int currentCount) {
124: if (type == MemoryEventType.BELOW_THRESHOLD
125: || lastStat == null
126: || lastStat.usage.getCollectionCount() < usage
127: .getCollectionCount()
128: || (usage.getCollectionCount() < 0 && lastStat.usage
129: .getUsedMemory() > usage.getUsedMemory())) {
130: double used = usage.getUsedPercentage();
131: double threshold = config.getUsedThreshold();
132: Assert
133: .assertTrue((type == MemoryEventType.BELOW_THRESHOLD && threshold >= used)
134: || threshold <= used);
135: if (used > 0)
136: calculatedCacheSize = (int) (currentCount * (threshold / used));
137: }
138: }
139:
140: public void objectEvicted(int evictedCount, int currentCount,
141: List targetObjects4GC) {
142: this .evicted = evictedCount;
143: this .countAfter = currentCount;
144: state = COMPLETE;
145: // TODO:: add reference queue
146: if (config.isLoggingEnabled()) {
147: logger.info("Evicted " + evictedCount
148: + " current Size = " + currentCount
149: + " new objects created = "
150: + getNewObjectsCount() + " time taken = "
151: + (System.currentTimeMillis() - startTime)
152: + " ms");
153: }
154: }
155:
156: private int getNewObjectsCount() {
157: return countAfter - (countBefore - evicted);
158: }
159:
160: // TODO:: This need to be more intellegent. It should also check if a GC actually happened after eviction. Use
161: // Reference Queue
162: private int computeObjects2Evict(int currentCount) {
163: if (type == MemoryEventType.BELOW_THRESHOLD
164: || calculatedCacheSize > currentCount) {
165: return 0;
166: }
167: int overshoot = 0;
168: if (config.getObjectCountCriticalThreshold() > 0
169: && currentCount > config
170: .getObjectCountCriticalThreshold()) {
171: // Give higher precidence to Object Count Critical Threshold than calculate cache size.
172: overshoot = currentCount
173: - config.getObjectCountCriticalThreshold();
174: } else {
175: overshoot = currentCount - calculatedCacheSize;
176: }
177: if (overshoot <= 0) {
178: return 0;
179: }
180: int objects2Evict = overshoot
181: + ((calculatedCacheSize * config
182: .getPercentageToEvict()) / 100);
183: return objects2Evict;
184: }
185:
186: public String toString() {
187: return "CacheStats[ type = " + type + ",\n\t usage = "
188: + usage + ",\n\t countBefore = " + countBefore
189: + ", toEvict = " + toEvict + ", evicted = "
190: + evicted + ", countAfter = " + countAfter
191: + ", objectsGCed = " + objectsGCed
192: + ",\n\t state = " + state + "]";
193: }
194: }
195:
196: }
|