001: /*
002: * All content copyright (c) 2003-2006 Terracotta, Inc., except as may otherwise be noted in a separate copyright notice. All rights reserved.
003: */
004: package com.tc.runtime;
005:
006: import com.tc.exception.TCRuntimeException;
007: import com.tc.lang.TCThreadGroup;
008: import com.tc.logging.TCLogger;
009: import com.tc.logging.TCLogging;
010: import com.tc.util.DebugUtil;
011: import com.tc.util.runtime.Os;
012:
013: import java.util.ArrayList;
014: import java.util.Iterator;
015: import java.util.List;
016:
017: public class TCMemoryManagerImpl implements TCMemoryManager {
018:
019: private static final TCLogger logger = TCLogging
020: .getLogger(TCMemoryManagerImpl.class);
021:
022: private final List listeners = new ArrayList();
023:
024: private final int threshold;
025: private final int criticalThreshold;
026: private final int leastCount;
027: private final long sleepInterval;
028: private final boolean monitorOldGenOnly;
029:
030: private MemoryMonitor monitor;
031:
032: private final TCThreadGroup threadGroup;
033:
034: public TCMemoryManagerImpl(int usedThreshold,
035: int usedCriticalThreshold, long sleepInterval,
036: int leastCount, boolean monitorOldGenOnly,
037: TCThreadGroup threadGroup) {
038: this .threadGroup = threadGroup;
039: verifyInput(usedThreshold, usedCriticalThreshold,
040: sleepInterval, leastCount);
041: this .monitorOldGenOnly = monitorOldGenOnly;
042: this .leastCount = leastCount;
043: this .threshold = usedThreshold;
044: this .criticalThreshold = usedCriticalThreshold;
045: this .sleepInterval = sleepInterval;
046: }
047:
048: private void verifyInput(int usedT, int usedCT, long sleep, int lc) {
049: if (usedT <= 0 || usedT >= 100) {
050: //
051: throw new AssertionError(
052: "Used Threshold should be > 0 && < 100 : " + usedT
053: + " Outside range");
054: }
055: if (usedCT <= 0 || usedCT >= 100) {
056: //
057: throw new AssertionError(
058: "Critical Used Threshold should be > 0 && < 100 : "
059: + usedCT + " Outside range");
060: }
061: if (usedT > usedCT) {
062: //
063: throw new AssertionError(
064: "Used Threshold should be <= Critical Used Threshold : "
065: + usedT + " <= " + usedCT);
066: }
067: if (sleep <= 0) {
068: //
069: throw new AssertionError(
070: "Sleep Interval cannot be <= 0 : sleep Interval = "
071: + sleep);
072: }
073: if (lc <= 0 || lc >= 100) {
074: //
075: throw new AssertionError(
076: "Least Count should be > 0 && < 100 : " + lc
077: + " Outside range");
078: }
079: }
080:
081: public synchronized void registerForMemoryEvents(
082: MemoryEventsListener listener) {
083: listeners.add(listener);
084: startMonitorIfNecessary();
085: }
086:
087: public synchronized void unregisterForMemoryEvents(
088: MemoryEventsListener listener) {
089: listeners.remove(listener);
090: stopMonitorIfNecessary();
091: }
092:
093: private void stopMonitorIfNecessary() {
094: if (listeners.size() == 0 && monitor != null) {
095: monitor.stop();
096: monitor = null;
097: }
098: }
099:
100: private void startMonitorIfNecessary() {
101: if (listeners.size() > 0 && monitor == null) {
102: this .monitor = new MemoryMonitor(TCRuntime
103: .getJVMMemoryManager(), this .sleepInterval,
104: this .monitorOldGenOnly);
105: Thread t = new Thread(this .threadGroup, this .monitor);
106: t.setDaemon(true);
107: if (Os.isSolaris()) {
108: t.setPriority(Thread.MAX_PRIORITY);
109: t.setName("TC Memory Monitor(High Priority)");
110: } else {
111: t.setName("TC Memory Monitor");
112: }
113: t.start();
114: }
115: }
116:
117: private synchronized void fireMemoryEvent(MemoryEventType type,
118: MemoryUsage mu) {
119: for (Iterator i = listeners.iterator(); i.hasNext();) {
120: MemoryEventsListener listener = (MemoryEventsListener) i
121: .next();
122: listener.memoryUsed(type, mu);
123: }
124: }
125:
126: public class MemoryMonitor implements Runnable {
127:
128: private final JVMMemoryManager manager;
129: private final boolean oldGen;
130: private volatile boolean run = true;
131: private int lastUsed;
132: private MemoryUsage lastReported;
133: private long sleepTime;
134: private MemoryEventType currentState;
135:
136: public MemoryMonitor(JVMMemoryManager manager,
137: long sleepInterval, boolean monitorOldGenOnly) {
138: this .manager = manager;
139: this .sleepTime = sleepInterval;
140: this .oldGen = monitorOldGenOnly
141: && manager.isMemoryPoolMonitoringSupported();
142: this .currentState = MemoryEventType.BELOW_THRESHOLD;
143: }
144:
145: public void stop() {
146: run = false;
147: }
148:
149: public void run() {
150: logger.debug("Starting Memory Monitor - sleep interval - "
151: + sleepTime);
152: final boolean _oldGen = oldGen;
153: while (run) {
154: try {
155: Thread.sleep(sleepTime);
156: MemoryUsage mu = (_oldGen ? manager
157: .getOldGenUsage() : manager
158: .getMemoryUsage());
159: if (DebugUtil.DEBUG) {
160: logger.info("Memory Usage is : " + mu);
161: }
162: reportUsage(mu);
163: adjust(mu);
164: } catch (Throwable t) {
165: logger.error(t);
166: throw new TCRuntimeException(t);
167: }
168: }
169: logger.debug("Stopping Memory Monitor - sleep interval - "
170: + sleepTime);
171: }
172:
173: private void adjust(MemoryUsage mu) {
174: int usedPercentage = mu.getUsedPercentage();
175: try {
176: if (lastUsed != 0 && lastUsed < usedPercentage) {
177: int diff = usedPercentage - lastUsed;
178: long l_sleep = this .sleepTime;
179: if (diff > leastCount * 1.5 && l_sleep > 1) {
180: // decrease sleep time
181: this .sleepTime = Math.max(1, l_sleep
182: * leastCount / diff);
183: logger.info("Sleep time changed to : "
184: + this .sleepTime);
185: } else if (diff < leastCount * 0.5
186: && l_sleep < sleepInterval) {
187: // increase sleep time
188: this .sleepTime = Math.min(sleepInterval,
189: l_sleep * leastCount / diff);
190: logger.info("Sleep time changed to : "
191: + this .sleepTime);
192: }
193: }
194: } finally {
195: lastUsed = usedPercentage;
196: }
197: }
198:
199: private void reportUsage(MemoryUsage mu) {
200: int usedPercentage = mu.getUsedPercentage();
201: if (usedPercentage < threshold) {
202: if (currentState != MemoryEventType.BELOW_THRESHOLD) {
203: // Send only 1 BELOW_THRESHOLD event
204: fire(MemoryEventType.BELOW_THRESHOLD, mu);
205: }
206: } else if (usedPercentage >= criticalThreshold) {
207: if (!oldGen
208: || currentState != MemoryEventType.ABOVE_CRITICAL_THRESHOLD
209: || isLeastCountReached(usedPercentage)
210: || isGCCompleted(mu)) {
211: // Send an event every time if we are monitoring the entire heap or else if we are monitoring only old gen
212: // then send an event only if greater than least count or if we just reached ABOVE_CRITICAL_THRESHOLD or if a gc took place
213: fire(MemoryEventType.ABOVE_CRITICAL_THRESHOLD, mu);
214: }
215: } else if (currentState != MemoryEventType.ABOVE_THRESHOLD
216: || isLeastCountReached(usedPercentage)) {
217: fire(MemoryEventType.ABOVE_THRESHOLD, mu);
218: }
219: }
220:
221: private boolean isGCCompleted(MemoryUsage mu) {
222: return lastReported.getCollectionCount() < mu
223: .getCollectionCount();
224: }
225:
226: private boolean isLeastCountReached(int usedPercentage) {
227: return (Math.abs(usedPercentage
228: - lastReported.getUsedPercentage()) >= leastCount);
229: }
230:
231: private void fire(MemoryEventType type, MemoryUsage mu) {
232: fireMemoryEvent(type, mu);
233: currentState = type;
234: lastReported = mu;
235: }
236: }
237:
238: }
|