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-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: GarbageCollectionService.java,v 1.14 2003/08/17 01:32:23 tanderson Exp $
44   *
45   * Date         Author  Changes
46   * 08/29/2001   jima    Created
47   */
48  package org.exolab.jms.gc;
49  
50  import java.util.LinkedList;
51  
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  
55  import org.exolab.core.foundation.HandleIfc;
56  import org.exolab.core.service.BasicService;
57  import org.exolab.core.service.ServiceException;
58  import org.exolab.core.service.ServiceState;
59  import org.exolab.jms.config.Configuration;
60  import org.exolab.jms.config.ConfigurationManager;
61  import org.exolab.jms.config.GarbageCollectionConfiguration;
62  import org.exolab.jms.events.BasicEventManager;
63  import org.exolab.jms.events.Event;
64  import org.exolab.jms.events.EventHandler;
65  import org.exolab.jms.events.IllegalEventDefinedException;
66  
67  
68  /***
69   * The garbage collection service is responsible for managing all transient
70   * garbage collection for OpenJMS, which includes messages, destinations,
71   * endpoints etc. It does not deal with persistent data, which is handled
72   * through the database service. Other services or managers can register
73   * themselves with GarbageCollectionService if they implement the
74   * {@link GarbageCollectable} interface.
75   * <p>
76   * Gargabe collection will be initiated when the amount of free memory falls
77   * below a low water mark, which is calculated as a percentage of total memory.
78   * By default garbage collection will run when free memory falls below 20%
79   * of total memory, this can be changed through the configuration file.
80   * <p>
81   * The service will check the memory usage every 30 seconds by default. but
82   * this can also be modified through the configuration file.
83   * <p>
84   * In addition the garbage collection service can also be configured to
85   * execute at regular intervals regardless the amount of residual free memory.
86   * This option can be employed to ease the burden of performing wholesale
87   * garbage collection when memory falls below the low water mark threshold. The
88   * default value for this is 300 seconds. Setting this value to 0 will disable
89   * this capability.
90   * <p>
91   * This service makes use of the {@link BasicEventManager} to register events 
92   * for garbage collection.
93   *
94   * @version     $Revision: 1.14 $ $Date: 2003/08/17 01:32:23 $
95   * @author      <a href="mailto:jima@intalio.com">Jim Alateras</a>
96   */
97  public class GarbageCollectionService
98      extends BasicService
99      implements EventHandler {
100 
101     /***
102      * The name of the service
103      */
104     private final static String GC_SERVICE_NAME = "GCCollectionService";
105 
106     /***
107      * This is the value of the trnasient garbage collection event that
108      * is used to register with the {@link BasicEventManager}. When this event
109      * is received the memory utilization is checked to determine whether
110      * we need to perform some garbage collection.
111      */
112     private final static int CHECK_FREE_MEMORY_EVENT = 1;
113 
114     /***
115      * This event is used to unconditionally trigger garbage collection.
116      */
117     private final static int GARBAGE_COLLECT_EVENT = 2;
118 
119     /***
120      * Maintains a singleton instance of the gc service
121      */
122     private static GarbageCollectionService _instance = null;
123 
124     /***
125      * Used to synchronize the creation of the transaction manager
126      */
127     private static final Object _creator = new Object();
128 
129     /***
130      * The default low water threshold value before GC is initiated.
131      * This is specified as a percentage with valid values ranging from
132      * 10-50.
133      */
134     private int _gcLowWaterThreshold = 20;
135 
136     /***
137      * The default interval, in seconds, that memory is checked for
138      * the low water threshold. The default is 30 seconds.
139      */
140     private int _memoryCheckInterval = 30 * 1000;
141 
142     /***
143      * The default interval, in seconds, between successive executions
144      * of the garbage collector. This will execute regardless the amount
145      * of free memory left in the VM.
146      */
147     private int _gcInterval = 300 * 1000;
148 
149     /***
150      * This is the priority of that the GC thread uses to collect garbage.
151      * Changing it effects how aggressive GC is performed. The default value
152      * is 5.
153      */
154     private int _gcThreadPriority = 5;
155 
156     /***
157      * This is used to serialize access to the _collectingGarbage flag
158      */
159     private final Object _gcGuard = new Object();
160 
161     /***
162      * This flag indicates whether garabage collection is in progress
163      */
164     private boolean _collectingGarbage = false;
165 
166     /***
167      * Maintains a list of all GarbageCollectable instances
168      */
169     private LinkedList _gcList = new LinkedList();
170 
171     /***
172      * The logger
173      */
174     private static final Log _log =
175         LogFactory.getLog(GarbageCollectionService.class);
176 
177 
178     /***
179      * Return the singleton instance of the GarbageCollectionService
180      *
181      * @return GarbageCollectionService
182      * @throws GarbageCollectionServiceException
183      */
184     public static GarbageCollectionService instance()
185         throws GarbageCollectionServiceException {
186         if (_instance == null) {
187             synchronized (_creator) {
188                 // we need to check again if multiple threads
189                 // have blocked on the creation of the singleton
190                 if (_instance == null) {
191                     _instance = new GarbageCollectionService();
192                 }
193             }
194         }
195 
196         return _instance;
197     }
198 
199     /***
200      * Create an instance of a garbage collection service. It uses the
201      * configuration manager to extract the service parameters.
202      * <p>
203      * It will throw a GarbageCollectionServiceException, if it cannot
204      * construct the service
205      *
206      * @throws GarbageCollectionServiceException
207      */
208     GarbageCollectionService()
209         throws GarbageCollectionServiceException {
210         super(GC_SERVICE_NAME);
211 
212         // access the configuration file.
213         Configuration config = ConfigurationManager.getConfig();
214         GarbageCollectionConfiguration gc_config =
215             config.getGarbageCollectionConfiguration();
216 
217         // read the value and ensure that it is within
218         // the specified limits
219         int low = gc_config.getLowWaterThreshold();
220         if (low < 10) {
221             low = 10;
222         }
223 
224         if (low > 50) {
225             low = 50;
226         }
227         _gcLowWaterThreshold = low;
228 
229         // read the memory check interval and fix it if it falls
230         // outside the constraints
231         int mem_interval = gc_config.getMemoryCheckInterval();
232         if ((mem_interval > 0) &&
233             (mem_interval < 5)) {
234             mem_interval = 5;
235         }
236         _memoryCheckInterval = mem_interval * 1000;
237 
238         // read the gc interval, which is optional
239         int gc_interval = gc_config.getGarbageCollectionInterval();
240         if (gc_interval <= 0) {
241             gc_interval = 0;
242         }
243         _gcInterval = gc_interval * 1000;
244 
245         // read the gc thread priority
246         int gc_priority = gc_config.getGarbageCollectionThreadPriority();
247         if (gc_priority < Thread.MIN_PRIORITY) {
248             gc_priority = Thread.MIN_PRIORITY;
249         }
250 
251         if (gc_priority > Thread.MAX_PRIORITY) {
252             gc_priority = Thread.MAX_PRIORITY;
253         }
254         _gcThreadPriority = gc_priority;
255     }
256 
257     /***
258      * Check whether the low water threshold has been reached.
259      *
260      * @return boolean - true if it has; false otherwise
261      */
262     public boolean belowLowWaterThreshold() {
263         boolean result = false;
264         long threshold = (long) ((Runtime.getRuntime().totalMemory() / 100) *
265             _gcLowWaterThreshold);
266         long free = Runtime.getRuntime().freeMemory();
267 
268         if (_log.isDebugEnabled()) {
269             _log.debug("GC Threshold=" + threshold + " Free=" + free);
270         }
271         if (threshold > free) {
272             result = true;
273         }
274 
275         return result;
276     }
277 
278     /***
279      * Register an entity that wishes to participate in the garbage collection
280      * process. This entity will be added to the list of other registered
281      * entities and will be called when GC is triggered.
282      *
283      * @param entry - entry to add to list
284      */
285     public void register(GarbageCollectable entry) {
286         if (entry != null) {
287             synchronized (_gcList) {
288                 _gcList.add(entry);
289             }
290         }
291     }
292 
293     /***
294      * Unregister the specified entry from the list of garbge collectable
295      * entities
296      *
297      * @param entry - entry to remove
298      */
299     public void unregister(GarbageCollectable entry) {
300         if (entry != null) {
301             synchronized (_gcList) {
302                 _gcList.remove(entry);
303             }
304         }
305     }
306 
307     // override ServiceManager.run
308     public void run() {
309         // do nothing
310     }
311 
312     // override ServiceManager.start
313     public void start()
314         throws ServiceException {
315 
316         // register an event with the event manager
317         if (_memoryCheckInterval > 0) {
318             _log.info("Registering Garbage Collection every " +
319                 _memoryCheckInterval + " for memory.");
320             registerEvent(CHECK_FREE_MEMORY_EVENT, _memoryCheckInterval);
321         }
322 
323         // optionally start garbage collection
324         if (_gcInterval > 0) {
325             _log.info("Registering Garbage Collection every " +
326                 _gcInterval + " for other resources.");
327             registerEvent(GARBAGE_COLLECT_EVENT, _gcInterval);
328         }
329 
330         this.setState(ServiceState.RUNNING);
331     }
332 
333     // override ServiceManager.stop
334     public void stop()
335         throws ServiceException {
336 
337         this.setState(ServiceState.STOPPED);
338     }
339 
340     // implementation of EventHandler.getHandle
341     public HandleIfc getHandle() {
342         return null;
343     }
344 
345     // implementation of EventHandler.handleEvent
346     public void handleEvent(int event, Object callback, long time) {
347         boolean valid_event = false;
348 
349         try {
350             if (event == CHECK_FREE_MEMORY_EVENT) {
351                 valid_event = true;
352                 try {
353                     // collect garbage only below threshold
354                     if (belowLowWaterThreshold()) {
355                         _log.info("GC Collecting Garbage Free Heap below "
356                             + _gcLowWaterThreshold);
357                         collectGarbage(true);
358                     }
359                 } catch (Exception exception) {
360                     _log.error("Error in GC Service [CHECK_FREE_MEMORY_EVENT]",
361                         exception);
362                 }
363             } else if (event == GARBAGE_COLLECT_EVENT) {
364                 valid_event = true;
365                 try {
366                     // collect garbage now
367                     collectGarbage(false);
368                 } catch (Exception exception) {
369                     _log.error("Error in GC Service [GARBAGE_COLLECT_EVENT]",
370                         exception);
371                 }
372             }
373         } finally {
374             if (valid_event) {
375                 try {
376                     registerEvent(event, ((Long) callback).longValue());
377                 } catch (Exception exception) {
378                     _log.error("Error in GC Service", exception);
379                 }
380             }
381         }
382     }
383 
384     /***
385      * Iterate through the list of registered {@link GarbageCollectables}
386      * and call collectGarbage on all of them.
387      *
388      * @param aggressive - true ofr aggressive garbage collection
389      */
390     private void collectGarbage(boolean aggressive) {
391         synchronized (_gcGuard) {
392             if (_collectingGarbage) {
393                 // if we are in the middle of collecting garbage then
394                 // we can ignore this request safely.
395                 return;
396             } else {
397                 _collectingGarbage = true;
398             }
399         }
400 
401         // if we get this far then we are the only thread that will
402         // trigger garbage collection. First we must set the priority
403         // of this thread
404         int oldPriority = Thread.currentThread().getPriority();
405         try {
406             Thread.currentThread().setPriority(_gcThreadPriority);
407             Object[] list = _gcList.toArray();
408             for (int index = 0; index < list.length; index++) {
409                 try {
410                     GarbageCollectable collectable =
411                         (GarbageCollectable) list[index];
412                     collectable.collectGarbage(aggressive);
413                 } catch (Exception exception) {
414                     _log.error("Error while collecting garbage", exception);
415                 }
416             }
417         } finally {
418             Thread.currentThread().setPriority(oldPriority);
419         }
420 
421         // we have finished collecting garbage
422         synchronized (_gcGuard) {
423             _collectingGarbage = false;
424         }
425     }
426 
427     /***
428      * Register the specified event with the corresponding time with the
429      * {@link BasicEventManager}. It will throw an exception if it cannot
430      * contact the event manager or register the event.
431      *
432      * @param event - the event to register
433      * @param time - the associated time
434      * @throws GarbageCollectionServiceException
435      */
436     private void registerEvent(int event, long time)
437         throws GarbageCollectionServiceException {
438         try {
439             BasicEventManager.instance().registerEventRelative(
440                 new Event(event, this, new Long(time)), time);
441         } catch (IllegalEventDefinedException exception) {
442             // rethrow as a more relevant exception
443             throw new GarbageCollectionServiceException(
444                 "Failed to registerEvent " + exception);
445         }
446     }
447 
448 } //-- GarbageCollectionService