View Javadoc

1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2001-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: GarbageCollectionService.java,v 1.2 2005/08/30 05:09:45 tanderson Exp $
44   */
45  package org.exolab.jms.gc;
46  
47  import java.util.LinkedList;
48  
49  import org.apache.commons.logging.Log;
50  import org.apache.commons.logging.LogFactory;
51  
52  import org.exolab.jms.config.Configuration;
53  import org.exolab.jms.config.GarbageCollectionConfiguration;
54  import org.exolab.jms.events.BasicEventManager;
55  import org.exolab.jms.events.Event;
56  import org.exolab.jms.events.EventHandler;
57  import org.exolab.jms.events.IllegalEventDefinedException;
58  import org.exolab.jms.events.EventManager;
59  import org.exolab.jms.service.Service;
60  import org.exolab.jms.service.ServiceException;
61  
62  
63  /***
64   * The garbage collection service is responsible for managing all transient
65   * garbage collection for OpenJMS, which includes messages, destinations,
66   * endpoints etc. It does not deal with persistent data, which is handled
67   * through the database service. Other services or managers can register
68   * themselves with GarbageCollectionService if they implement the {@link
69   * GarbageCollectable} interface.
70   * <p/>
71   * Gargabe collection will be initiated when the amount of free memory falls
72   * below a low water mark, which is calculated as a percentage of total memory.
73   * By default garbage collection will run when free memory falls below 20% of
74   * total memory, this can be changed through the configuration file.
75   * <p/>
76   * The service will check the memory usage every 30 seconds by default. but this
77   * can also be modified through the configuration file.
78   * <p/>
79   * In addition the garbage collection service can also be configured to execute
80   * at regular intervals regardless the amount of residual free memory. This
81   * option can be employed to ease the burden of performing wholesale garbage
82   * collection when memory falls below the low water mark threshold. The default
83   * value for this is 300 seconds. Setting this value to 0 will disable this
84   * capability.
85   * <p/>
86   * This service makes use of the {@link BasicEventManager} to register events
87   * for garbage collection.
88   *
89   * @author <a href="mailto:jima@intalio.com">Jim Alateras</a>
90   * @version $Revision: 1.2 $ $Date: 2005/08/30 05:09:45 $
91   */
92  public class GarbageCollectionService
93          extends Service
94          implements EventHandler {
95  
96      /***
97       * This is the value of the transient garbage collection event that is used
98       * to register with the {@link BasicEventManager}. When this event is
99       * received the memory utilization is checked to determine whether we need
100      * to perform some garbage collection.
101      */
102     private final static int CHECK_FREE_MEMORY_EVENT = 1;
103 
104     /***
105      * This event is used to unconditionally trigger garbage collection.
106      */
107     private final static int GARBAGE_COLLECT_EVENT = 2;
108 
109     /***
110      * The default low water threshold value before GC is initiated. This is
111      * specified as a percentage with valid values ranging from 10-50.
112      */
113     private int _gcLowWaterThreshold = 20;
114 
115     /***
116      * The default interval, in seconds, that memory is checked for the low
117      * water threshold. The default is 30 seconds.
118      */
119     private int _memoryCheckInterval = 30 * 1000;
120 
121     /***
122      * The default interval, in seconds, between successive executions of the
123      * garbage collector. This will execute regardless the amount of free memory
124      * left in the VM.
125      */
126     private int _gcInterval = 300 * 1000;
127 
128     /***
129      * This is the priority of that the GC thread uses to collect garbage.
130      * Changing it effects how aggressive GC is performed. The default value is
131      * 5.
132      */
133     private int _gcThreadPriority = 5;
134 
135     /***
136      * This is used to serialize access to the _collectingGarbage flag
137      */
138     private final Object _gcGuard = new Object();
139 
140     /***
141      * This flag indicates whether garabage collection is in progress
142      */
143     private boolean _collectingGarbage = false;
144 
145     /***
146      * Maintains a list of all GarbageCollectable instances
147      */
148     private LinkedList _gcList = new LinkedList();
149 
150     /***
151      * The event manager.
152      */
153     private final EventManager _eventMgr;
154 
155     /***
156      * The logger.
157      */
158     private static final Log _log =
159             LogFactory.getLog(GarbageCollectionService.class);
160 
161 
162     /***
163      * Create an instance of a garbage collection service. It uses the
164      * configuration manager to extract the service parameters.
165      *
166      * @param config   the configuration to use
167      * @param eventMgr the event manager
168      */
169     public GarbageCollectionService(Configuration config,
170                                     EventManager eventMgr) {
171         super("GarbageCollectionService");
172         if (config == null) {
173             throw new IllegalArgumentException("Argument 'config' is null");
174         }
175         if (eventMgr == null) {
176             throw new IllegalArgumentException("Argument 'eventMgr' is null");
177         }
178 
179         GarbageCollectionConfiguration gcConfig =
180                 config.getGarbageCollectionConfiguration();
181 
182         // read the value and ensure that it is within
183         // the specified limits
184         int low = gcConfig.getLowWaterThreshold();
185         if (low < 10) {
186             low = 10;
187         }
188 
189         if (low > 50) {
190             low = 50;
191         }
192         _gcLowWaterThreshold = low;
193 
194         // read the memory check interval and fix it if it falls
195         // outside the constraints
196         int mem_interval = gcConfig.getMemoryCheckInterval();
197         if ((mem_interval > 0) &&
198                 (mem_interval < 5)) {
199             mem_interval = 5;
200         }
201         _memoryCheckInterval = mem_interval * 1000;
202 
203         // read the gc interval, which is optional
204         int gc_interval = gcConfig.getGarbageCollectionInterval();
205         if (gc_interval <= 0) {
206             gc_interval = 0;
207         }
208         _gcInterval = gc_interval * 1000;
209 
210         // read the gc thread priority
211         int gc_priority = gcConfig.getGarbageCollectionThreadPriority();
212         if (gc_priority < Thread.MIN_PRIORITY) {
213             gc_priority = Thread.MIN_PRIORITY;
214         }
215 
216         if (gc_priority > Thread.MAX_PRIORITY) {
217             gc_priority = Thread.MAX_PRIORITY;
218         }
219         _gcThreadPriority = gc_priority;
220 
221         _eventMgr = eventMgr;
222     }
223 
224     /***
225      * Check whether the low water threshold has been reached.
226      *
227      * @return boolean - true if it has; false otherwise
228      */
229     public boolean belowLowWaterThreshold() {
230         boolean result = false;
231         long threshold = (long) ((Runtime.getRuntime().totalMemory() / 100) *
232                 _gcLowWaterThreshold);
233         long free = Runtime.getRuntime().freeMemory();
234 
235         if (_log.isDebugEnabled()) {
236             _log.debug("GC Threshold=" + threshold + " Free=" + free);
237         }
238         if (threshold > free) {
239             result = true;
240         }
241 
242         return result;
243     }
244 
245     /***
246      * Register an entity that wishes to participate in the garbage collection
247      * process. This entity will be added to the list of other registered
248      * entities and will be called when GC is triggered.
249      *
250      * @param entry the entry to add to list
251      */
252     public void register(GarbageCollectable entry) {
253         if (entry != null) {
254             synchronized (_gcList) {
255                 _gcList.add(entry);
256             }
257         }
258     }
259 
260     /***
261      * Unregister the specified entry from the list of garbge collectable
262      * entities
263      *
264      * @param entry - entry to remove
265      */
266     public void unregister(GarbageCollectable entry) {
267         if (entry != null) {
268             synchronized (_gcList) {
269                 _gcList.remove(entry);
270             }
271         }
272     }
273 
274     public void doStart()
275             throws ServiceException {
276 
277         // register an event with the event manager
278         if (_memoryCheckInterval > 0) {
279             _log.info("Registering Garbage Collection every " +
280                       _memoryCheckInterval + " for memory.");
281             registerEvent(CHECK_FREE_MEMORY_EVENT, _memoryCheckInterval);
282         }
283 
284         // optionally start garbage collection
285         if (_gcInterval > 0) {
286             _log.info("Registering Garbage Collection every " +
287                       _gcInterval + " for other resources.");
288             registerEvent(GARBAGE_COLLECT_EVENT, _gcInterval);
289         }
290     }
291 
292     // implementation of EventHandler.handleEvent
293     public void handleEvent(int event, Object callback, long time) {
294         boolean valid_event = false;
295 
296         try {
297             if (event == CHECK_FREE_MEMORY_EVENT) {
298                 valid_event = true;
299                 try {
300                     // collect garbage only below threshold
301                     if (belowLowWaterThreshold()) {
302                         _log.info("GC Collecting Garbage Free Heap below "
303                                   + _gcLowWaterThreshold);
304                         collectGarbage(true);
305                     }
306                 } catch (Exception exception) {
307                     _log.error("Error in GC Service [CHECK_FREE_MEMORY_EVENT]",
308                                exception);
309                 }
310             } else if (event == GARBAGE_COLLECT_EVENT) {
311                 valid_event = true;
312                 try {
313                     // collect garbage now
314                     collectGarbage(false);
315                 } catch (Exception exception) {
316                     _log.error("Error in GC Service [GARBAGE_COLLECT_EVENT]",
317                                exception);
318                 }
319             }
320         } finally {
321             if (valid_event) {
322                 try {
323                     registerEvent(event, ((Long) callback).longValue());
324                 } catch (Exception exception) {
325                     _log.error("Error in GC Service", exception);
326                 }
327             }
328         }
329     }
330 
331     /***
332      * Iterate through the list of registered {@link GarbageCollectable}s and
333      * call collectGarbage on all of them.
334      *
335      * @param aggressive - true ofr aggressive garbage collection
336      */
337     private void collectGarbage(boolean aggressive) {
338         synchronized (_gcGuard) {
339             if (_collectingGarbage) {
340                 // if we are in the middle of collecting garbage then
341                 // we can ignore this request safely.
342                 return;
343             } else {
344                 _collectingGarbage = true;
345             }
346         }
347 
348         // if we get this far then we are the only thread that will
349         // trigger garbage collection. First we must set the priority
350         // of this thread
351         int oldPriority = Thread.currentThread().getPriority();
352         try {
353             Thread.currentThread().setPriority(_gcThreadPriority);
354             Object[] list = _gcList.toArray();
355             for (int index = 0; index < list.length; index++) {
356                 try {
357                     GarbageCollectable collectable =
358                             (GarbageCollectable) list[index];
359                     collectable.collectGarbage(aggressive);
360                 } catch (Exception exception) {
361                     _log.error("Error while collecting garbage", exception);
362                 }
363             }
364         } finally {
365             Thread.currentThread().setPriority(oldPriority);
366         }
367 
368         // we have finished collecting garbage
369         synchronized (_gcGuard) {
370             _collectingGarbage = false;
371         }
372     }
373 
374     /***
375      * Register the specified event with the corresponding time with the {@link
376      * BasicEventManager}. It will throw an exception if it cannot contact the
377      * event manager or register the event.
378      *
379      * @param event the event to register
380      * @param time  the associated time
381      * @throws GarbageCollectionServiceException
382      *
383      */
384     private void registerEvent(int event, long time)
385             throws GarbageCollectionServiceException {
386         try {
387             _eventMgr.registerEventRelative(
388                     new Event(event, this, new Long(time)), time);
389         } catch (IllegalEventDefinedException exception) {
390             // rethrow as a more relevant exception
391             throw new GarbageCollectionServiceException(
392                     "Failed to register event " + exception);
393         }
394     }
395 
396 }