package com.quickserverlab.quickcached.cache.impl.softreferencemap; import java.io.*; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; import com.quickserverlab.quickcached.QuickCached; import com.quickserverlab.quickcached.cache.impl.BaseCacheImpl; /** * A SoftReference based ConcurrentHashMap cache. * * Code based on example from http://www.javaspecialists.eu/archive/Issue015.html * * @author Akshathkumar Shetty */ public class SoftReferenceMapImpl extends BaseCacheImpl { private static final Logger logger = Logger.getLogger(SoftReferenceMapImpl.class.getName()); /** * The number of "hard" references to hold internally. */ private static int hardSize = 1000; /** * The internal HashMap that will hold the SoftReference. */ private Map map = new ConcurrentHashMap(); private Map mapTtl = new ConcurrentHashMap(); /** * The FIFO list of hard references, order of last access. */ private List hardCache = Collections.synchronizedList(new LinkedList()); /** * Reference queue for cleared SoftReference objects. */ private final ReferenceQueue queue = new ReferenceQueue(); private int tunerSleeptime = 120;//in sec private final Object lock = new Object(); protected volatile long evicted; protected volatile long expired; private Thread purgeThread = null; public SoftReferenceMapImpl() { startPurgeThread(); } private void startPurgeThread() { purgeThread = new Thread("SoftReferenceMapImpl-PurgeThread") { public void run() { long timespent = 0; long timeToSleep = 0; long stime = 0; long etime = 0; while (true) { timeToSleep = tunerSleeptime * 1000 - timespent; if (timeToSleep > 0) { try { Thread.sleep(timeToSleep); } catch (InterruptedException ex) { Logger.getLogger(SoftReferenceMapImpl.class.getName()).log( Level.FINE, "Interrupted "+ex); break; } } stime = System.currentTimeMillis(); try { purgeOperation(); } catch (Exception ex) { Logger.getLogger(SoftReferenceMapImpl.class.getName()).log( Level.SEVERE, null, ex); } processQueue(); etime = System.currentTimeMillis(); timespent = etime - stime; } } }; purgeThread.setDaemon(true); purgeThread.start(); } public void purgeOperation() { try { Iterator iterator = mapTtl.keySet().iterator(); String key = null; Date expTime; Date currentTime = new Date(); while (iterator.hasNext()) { key = (String) iterator.next(); expTime = (Date) mapTtl.get(key); if(expTime==null) { continue; } if (expTime.before(currentTime)) { mapTtl.remove(key); map.remove(key); while(true) { if(hardCache.remove(key)==false) { break; } } expired++; } Thread.yield(); } } catch (Exception e) { logger.log(Level.WARNING, "Error: " + e, e); } } public void saveStats(Map stats) { if(stats==null) stats = new LinkedHashMap(); super.saveStats(stats); //evicted - Number of valid items removed from cache to free memory for new items stats.put("evicted", "" + evicted); //expired - Number of items that expired stats.put("expired", ""+expired); } public String getName() { return "SoftReferenceMapImpl"; } public long getSize() { processQueue(); // throw out garbage collected values first return map.size(); } public void setToCache(String key, Object value, int objectSize, int expInSec) throws Exception { map.put(key, new SoftValue(value, key, queue)); if (expInSec != 0) { mapTtl.put(key, new Date(System.currentTimeMillis() + expInSec * 1000)); } else { mapTtl.remove(key);//just in case } } public void updateToCache(String key, Object value, int objectSize) throws Exception { //no action required here for ref based cache } public void updateToCache(String key, Object value, int objectSize, int expInSec) throws Exception { //no action required here for ref based cache if (expInSec != 0) { mapTtl.put(key, new Date(System.currentTimeMillis() + expInSec * 1000)); } else { mapTtl.remove(key);//just in case } } public Object getFromCache(String key) throws Exception { Object result = null; // We get the SoftReference represented by that key SoftReference softRef = (SoftReference) map.get(key); if (softRef != null) { // From the SoftReference we get the value, which can be // null if it was not in the map, or it was removed in // the processQueue() method defined below result = softRef.get(); if (result == null) { // If the value has been garbage collected, remove the // entry from the HashMap. map.remove(key); mapTtl.remove(key); } else { // We now add this object to the beginning of the hard // reference queue. One reference can occur more than // once, because lookups of the FIFO queue are slow, so // we don't want to search through it each time to remove // duplicates. hardCache.add(0, result);//addFirst try { synchronized(lock) { int size = hardCache.size(); if (size > getHardSize()) { // Remove the last entry if list longer than HARD_SIZE hardCache.remove(size-1);//removeLast(); } } } catch(IndexOutOfBoundsException e) { logger.log(Level.SEVERE, "IndexOutOfBoundsException: "+e); } } } if(result==null && QuickCached.DEBUG) { logger.log(Level.FINE, "no value in db for key: {0}", key); } return result; } public boolean deleteFromCache(String key) throws Exception { mapTtl.remove(key); Object obj = map.remove(key); return obj != null; } public void flushCache() throws Exception { hardCache.clear(); mapTtl.clear(); map.clear(); } public static int getHardSize() { return hardSize; } public static void setHardSize(int aHardSize) { hardSize = aHardSize; } /** * We define our own subclass of SoftReference which contains not only the * value but also the key to make it easier to find the entry in the HashMap * after it's been garbage collected. */ private static class SoftValue extends SoftReference { private final Object key; // always make data member final /** * Did you know that an outer class can access private data members and * methods of an inner class? I didn't know that! I thought it was only * the inner class who could access the outer class's private * information. An outer class can also access private members of an * inner class inside its inner class. */ private SoftValue(Object v, Object key, ReferenceQueue q) { super(v, q); this.key = key; } } /** * Here we go through the ReferenceQueue and remove garbage collected * SoftValue objects from the HashMap by looking them up using the * SoftValue.key data member. */ private void processQueue() { SoftValue sv = null; try { while ((sv = (SoftValue) queue.poll()) != null) { map.remove(sv.key); // we can access private data! mapTtl.remove(sv.key); // we can access private data! while(true) { if(hardCache.remove(sv.key)==false) { break; } } evicted++; Thread.yield(); } } catch (Throwable e) { logger.log(Level.WARNING, "Error: "+e, e); } } private String fileName = "./"+getName()+"_"+QuickCached.getPort()+".dat"; public boolean saveToDisk() { System.out.print("Saving state to disk.. "); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(fileName)); writeObject(oos); oos.flush(); return true; } catch (Exception e) { logger.log(Level.WARNING, "Error: {0}", e); } finally { if(oos!=null) { try { oos.close(); } catch (IOException ex) { logger.log(Level.WARNING, "Error: "+ex, ex); } } System.out.println("Done"); } return false; } public boolean readFromDisk() { File file = new File(fileName); if(file.canRead()==false) return false; System.out.print("Reading state from disk.. "); ObjectInputStream ois = null; try { ois = new ObjectInputStream(new FileInputStream(fileName)); readObject(ois); return true; } catch (Exception e) { logger.log(Level.WARNING, "Error: {0}", e); } finally { if(ois!=null) { try { ois.close(); } catch (IOException ex) { logger.log(Level.WARNING, "Error: "+ex, ex); } } file.delete(); System.out.println("Done"); } return false; } private void writeObject(ObjectOutputStream out) throws IOException { purgeThread.interrupt(); processQueue(); HashMap<Object, Object> wcache = new HashMap<Object, Object>(); Iterator<String> it = map.keySet().iterator(); Object key = null; Object value = null; while(it.hasNext()) { key = it.next(); SoftReference ref = (SoftReference) map.get(key); if(ref == null) continue; value = ref.get(); if(value!=null) { wcache.put(key, value); } else { mapTtl.remove(key); while(true) { if(hardCache.remove(key)==false) { break; } } } } out.writeObject(wcache); out.writeObject(mapTtl); out.writeObject(hardCache); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { HashMap<Object, Object> wcache = (HashMap<Object, Object>) in.readObject(); Iterator it = wcache.keySet().iterator(); Object key = null; Object value = null; while(it.hasNext()) { key = it.next(); value = wcache.get(key); map.put(key, new SoftValue(value, key, queue)); } mapTtl = (Map) in.readObject(); hardCache = (List) in.readObject(); } }