View Javadoc

1   
2   /*   Open Source Java Caching Service
3   *    Copyright (C) 2002 Frank Karlstrøm
4   *    This library is free software; you can redistribute it and/or
5   *    modify it under the terms of the GNU Lesser General Public
6   *    License as published by the Free Software Foundation; either
7   *    version 2.1 of the License, or (at your option) any later version.
8   *
9   *    This library is distributed in the hope that it will be useful,
10  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  *    Lesser General Public License for more details.
13  *
14  *    You should have received a copy of the GNU Lesser General Public
15  *    License along with this library; if not, write to the Free Software
16  *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17  *
18  *    The author can be contacted by email: fjankk@users.sourceforge.net
19  */
20  package org.fjank.jcache;
21  
22  import java.lang.ref.ReferenceQueue;
23  import java.util.Iterator;
24  import javax.util.jcache.Attributes;
25  import javax.util.jcache.CacheAttributes;
26  import javax.util.jcache.CacheException;
27  import javax.util.jcache.CacheNotAvailableException;
28  import javax.util.jcache.NullObjectException;
29  
30  
31  
32  
33  /**
34   * A Runnable class for Sweeping the cache for expired objects. Either they are
35   * serialized to disk, or they are removed from the cache depending on the
36   * attributes of the object.
37   *
38   * @author Frank Karlstrøm
39   */
40  public class CacheSweeper implements Runnable {
41      /** the singleton sweeper */
42      private static CacheSweeper _singleton;
43  	/** a boolean indication wether this sweeper is active or not. */
44      //2004-09-FB
45      private boolean active = false;
46  
47      /**
48       * Creates new CacheSweeper
49       */
50      private CacheSweeper() {
51      }
52  
53      /**
54       * sweeps the cache and removes invalid objects, waits for  cleanInterval
55       * seconds, then sweeps again etc.
56       */
57      public void run() {
58          CacheImpl cache = CacheImpl.getCache(true);
59          CacheAttributes attribs = cache.getAttributes();
60  
61          while (active) {
62              try {
63              	Thread.sleep(attribs.getCleanInterval() * 1000);
64              } catch (InterruptedException e) {
65                  //normal
66              }
67              sweepCache();
68          }
69      }
70  
71      /**
72       * checks the size of the objects in the cache,  and if the memory limit
73       * is nearing its capacity,  somoe objects are saved to disk. The
74       * algorithm for choosing these is in the method writeToDisk(). Then the
75       * ReferenceQueue is checked, and all objects in this queue have been
76       * swept by the GC. if they have expired, they are deleted from the cache.
77       * If not, they are retained to the next cachesweep.
78       */
79      private void sweepCache() {
80          CacheImpl cache = null;
81          try {
82              cache = CacheImpl.getCache(true);
83  
84              int maxSize = cache.getAttributes().getMemoryCacheSize();
85              long maxBytes = maxSize * 1024 * 1024;
86              if (getMemoryCacheSize() >= maxBytes) {
87                  writeToDisk();
88              }
89              /**sweeps the entire cache, and de-references the expired objects.
90               * This will add them to the appropiate ReferenceQueue
91               * when their referenceCount is zero. If no one uses the objects,
92               * this will happen immediantely.
93               * We also attempt to stimulate the gc to run. (Not neccessary perhaps?)
94               *
95               */
96  
97  			CacheRegion reg = cache.getRegion();
98  			sweepGroup(reg);
99  			Iterator iter = cache.userRegionNames();
100 			while(iter.hasNext()) {
101 				Object key = iter.next();
102 				CacheRegion userReg = cache.getRegion(key);
103 				sweepGroup(userReg);
104 			}
105 			for(int i=0; i<3; i++) {
106 				System.runFinalization();
107 				System.gc();
108 				try {
109 					Thread.sleep(500);
110 				} catch (InterruptedException e1) {;}
111 			}
112 
113 			/**now its up to the referencequeue to remove the
114 			 * appropiate obects.
115 			 */
116 			CacheObject cacheObj = null;
117             ReferenceQueue q = cache.getReferenceQueue();
118     		while ((cacheObj = (CacheObject) q.poll()) != null) {
119 				cacheObj.resetRefCount();
120 				tryRemoval(cacheObj);
121 			}
122 
123 		} catch (CacheException e) {
124         	e.printStackTrace();
125             stopSweeper();
126         } catch (java.lang.IncompatibleClassChangeError e) {
127             //the cache has probably been reloaded/redeployed.
128             //restart ourselves.
129             if (cache != null) {
130                 cache.close();
131                 try {
132                     cache.open();
133                 } catch (CacheNotAvailableException ee) {
134                     stopSweeper();
135                 }
136             }
137         }
138     }
139 	/**
140 	 * Will sweep a group in search for an object which has expired.
141 	 * any groups encountered will use this method as a recursive method
142 	 * to further search for expired objects.
143 	 * @todo there's no failsafe for groups containing themselves.
144 	 * @param group the group to sweep.
145 	 * @throws NullObjectException if a nullobject is encountered.
146 	 */
147     private void sweepGroup(CacheGroup group) throws NullObjectException {
148     	Iterator iter = group.weakReferenceObjects.keySet().iterator();
149     	while(iter.hasNext()) {
150     		Object key = iter.next();
151     		Object obj = group.weakReferenceObjects.get(key);
152     		if(obj instanceof CacheObject) {
153     			if(hasExpired((CacheObject) obj)) {
154     			    group.removeObjectReference(key);
155     			}
156     		}else if(obj instanceof CacheGroup) {
157     			sweepGroup((CacheGroup) obj);
158     		}else {
159     			System.out.println("An unknown object ("+obj.getClass().getName()+") was discovered in the cache.");
160     			break;
161     		}
162     	}
163     }
164 
165     /**
166      * the actual remove routine.  if the object has been alive longer than the
167      * timeToLive , its invalidated.
168      * @todo Some of the code here is duplicated in tryRemoveHardReference.
169      * @todo return value is never used.
170      * @param cacheObj the CacheObject to try to remove.
171      * @return an boolean indication wether the object was removed or not.
172      */
173 
174     private boolean tryRemoval(final CacheObject cacheObj) throws NullObjectException {
175         if (hasExpired(cacheObj)) {
176             cacheObj.invalidate();
177             return true;
178         }
179         return false;
180     }
181 	private boolean hasExpired(CacheObject obj) throws NullObjectException {
182 		Attributes attributes = obj.getAttributes();
183 		if(attributes==null) throw new NullObjectException("A null attributes was detected.");
184 		/*-1 is the default for attributes, and the default is
185 		 * non invalidation, so if the ttl is -1, the objects remains in the cache.
186 		 */
187 
188 		if(attributes.getTimeToLive()==-1) {
189 			return false;
190 		}
191 		if(attributes.getLoader()!=null) return false;
192 		long now = System.currentTimeMillis();
193 		long timealive = (now - attributes.getCreateTime()) / 1000;
194 
195 		//only ttl is support for now. the rest of the params can be implemented later.
196 		if (timealive >= attributes.getTimeToLive()) {
197 			return true;
198 		}
199 		return false;
200 	}
201     /**
202      * Finds all objects wich are not in use, and determines wich one of them
203      * is the best to flush to disk. LFU is a caching strategy that stands for
204      * "least frequently used". In this strategy, elements are evicted based
205      * when they were added, when they were last used, and how many times they
206      * have been used.  Other strategies may be implemented later. Gets the
207      * diskFile, fins an available position in the file, converts the
208      * CacheObject to a CacheObjectDisk, and writes it to the disk file.
209      *
210      *@todo NOT IMPLEMENTED.
211      */
212     private void writeToDisk() {
213     }
214 
215     /**
216      * Gets the current size of all objects in the cache. Return the size of
217      * the actuall object, not regarding the size of the attributes, or the
218      * wrapper objects.
219      *
220      * @return long the current size of all objects in the cache.
221      *
222      *@todo NOT IMPLEMENTED.
223      */
224     private long getMemoryCacheSize() {
225         return 0;
226     }
227 
228 	/**
229 	 * activates this sweeper.
230 	 */
231 	synchronized void startSweeper() {
232 		//2004/09-FB
233 		if (!_singleton.active){
234 			active=true;
235 			CacheThreadFactory fact = CacheThreadFactory.getInstance();
236 			fact.setName("Fjanks FKache - CacheSweeper");
237 			fact.setDaemon(true);
238 			fact.newThread(this).start();
239 			
240 		}
241 	}
242 
243     /**
244      * deactivates this sweeper.
245      */
246 	//2004/09-FB
247 	synchronized void stopSweeper() {
248         active = false;
249 		
250     }
251 
252 	/**
253 	 * create this sweeper instance.
254 	 */
255     public static synchronized CacheSweeper getInstance() {
256         if (_singleton == null) {
257             _singleton = new CacheSweeper();
258             //2004/09-FB
259             //_singleton.startSweeper();
260 			
261         }
262         return _singleton;
263     }
264 
265 	//2004/09-FB
266 	/**
267 	 * remove this sweeper instance.
268 	 */
269 	static synchronized void removeInstance() {
270 		if (_singleton != null) {
271 			 if (_singleton.active){
272 				_singleton.stopSweeper();
273 			 }
274 			_singleton = null;
275 			
276 		}
277 	}
278 }