001package org.apache.commons.jcs.engine.memory.shrinking; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import java.util.Set; 023 024import org.apache.commons.jcs.engine.behavior.ICacheElement; 025import org.apache.commons.jcs.engine.behavior.IElementAttributes; 026import org.apache.commons.jcs.engine.control.CompositeCache; 027import org.apache.commons.jcs.engine.control.event.behavior.ElementEventType; 028import org.apache.commons.jcs.engine.memory.behavior.IMemoryCache; 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031 032/** 033 * A background memory shrinker. Memory problems and concurrent modification exception caused by 034 * acting directly on an iterator of the underlying memory cache should have been solved. 035 * @version $Id: ShrinkerThread.java 1719092 2015-12-10 15:07:30Z tv $ 036 */ 037public class ShrinkerThread<K, V> 038 implements Runnable 039{ 040 /** The logger */ 041 private static final Log log = LogFactory.getLog( ShrinkerThread.class ); 042 043 /** The CompositeCache instance which this shrinker is watching */ 044 private final CompositeCache<K, V> cache; 045 046 /** Maximum memory idle time for the whole cache */ 047 private final long maxMemoryIdleTime; 048 049 /** Maximum number of items to spool per run. Default is -1, or no limit. */ 050 private final int maxSpoolPerRun; 051 052 /** Should we limit the number spooled per run. If so, the maxSpoolPerRun will be used. */ 053 private boolean spoolLimit = false; 054 055 /** 056 * Constructor for the ShrinkerThread object. 057 * <p> 058 * @param cache The MemoryCache which the new shrinker should watch. 059 */ 060 public ShrinkerThread( CompositeCache<K, V> cache ) 061 { 062 super(); 063 064 this.cache = cache; 065 066 long maxMemoryIdleTimeSeconds = cache.getCacheAttributes().getMaxMemoryIdleTimeSeconds(); 067 068 if ( maxMemoryIdleTimeSeconds < 0 ) 069 { 070 this.maxMemoryIdleTime = -1; 071 } 072 else 073 { 074 this.maxMemoryIdleTime = maxMemoryIdleTimeSeconds * 1000; 075 } 076 077 this.maxSpoolPerRun = cache.getCacheAttributes().getMaxSpoolPerRun(); 078 if ( this.maxSpoolPerRun != -1 ) 079 { 080 this.spoolLimit = true; 081 } 082 083 } 084 085 /** 086 * Main processing method for the ShrinkerThread object 087 */ 088 @Override 089 public void run() 090 { 091 shrink(); 092 } 093 094 /** 095 * This method is called when the thread wakes up. First the method obtains an array of keys for 096 * the cache region. It iterates through the keys and tries to get the item from the cache 097 * without affecting the last access or position of the item. The item is checked for 098 * expiration, the expiration check has 3 parts: 099 * <ol> 100 * <li>Has the cacheattributes.MaxMemoryIdleTimeSeconds defined for the region been exceeded? If 101 * so, the item should be move to disk.</li> <li>Has the item exceeded MaxLifeSeconds defined in 102 * the element attributes? If so, remove it.</li> <li>Has the item exceeded IdleTime defined in 103 * the element attributes? If so, remove it. If there are event listeners registered for the 104 * cache element, they will be called.</li> 105 * </ol> 106 * TODO Change element event handling to use the queue, then move the queue to the region and 107 * access via the Cache. 108 */ 109 protected void shrink() 110 { 111 if ( log.isDebugEnabled() ) 112 { 113 log.debug( "Shrinking memory cache for: " + this.cache.getCacheName() ); 114 } 115 116 IMemoryCache<K, V> memCache = cache.getMemoryCache(); 117 118 try 119 { 120 Set<K> keys = memCache.getKeySet(); 121 int size = keys.size(); 122 if ( log.isDebugEnabled() ) 123 { 124 log.debug( "Keys size: " + size ); 125 } 126 127 ICacheElement<K, V> cacheElement; 128 IElementAttributes attributes; 129 130 int spoolCount = 0; 131 132 for (K key : keys) 133 { 134 cacheElement = memCache.getQuiet( key ); 135 136 if ( cacheElement == null ) 137 { 138 continue; 139 } 140 141 attributes = cacheElement.getElementAttributes(); 142 143 boolean remove = false; 144 145 long now = System.currentTimeMillis(); 146 147 // If the element is not eternal, check if it should be 148 // removed and remove it if so. 149 if ( !cacheElement.getElementAttributes().getIsEternal() ) 150 { 151 remove = cache.isExpired( cacheElement, now, 152 ElementEventType.EXCEEDED_MAXLIFE_BACKGROUND, 153 ElementEventType.EXCEEDED_IDLETIME_BACKGROUND ); 154 155 if ( remove ) 156 { 157 memCache.remove( cacheElement.getKey() ); 158 } 159 } 160 161 // If the item is not removed, check is it has been idle 162 // long enough to be spooled. 163 164 if ( !remove && maxMemoryIdleTime != -1 ) 165 { 166 if ( !spoolLimit || spoolCount < this.maxSpoolPerRun ) 167 { 168 final long lastAccessTime = attributes.getLastAccessTime(); 169 170 if ( lastAccessTime + maxMemoryIdleTime < now ) 171 { 172 if ( log.isDebugEnabled() ) 173 { 174 log.debug( "Exceeded memory idle time: " + cacheElement.getKey() ); 175 } 176 177 // Shouldn't we ensure that the element is 178 // spooled before removing it from memory? 179 // No the disk caches have a purgatory. If it fails 180 // to spool that does not affect the 181 // responsibilities of the memory cache. 182 183 spoolCount++; 184 185 memCache.remove( cacheElement.getKey() ); 186 187 memCache.waterfal( cacheElement ); 188 189 key = null; 190 cacheElement = null; 191 } 192 } 193 else 194 { 195 if ( log.isDebugEnabled() ) 196 { 197 log.debug( "spoolCount = '" + spoolCount + "'; " + "maxSpoolPerRun = '" + maxSpoolPerRun 198 + "'" ); 199 } 200 201 // stop processing if limit has been reached. 202 if ( spoolLimit && spoolCount >= this.maxSpoolPerRun ) 203 { 204 return; 205 } 206 } 207 } 208 } 209 } 210 catch ( Throwable t ) 211 { 212 log.info( "Unexpected trouble in shrink cycle", t ); 213 214 // concurrent modifications should no longer be a problem 215 // It is up to the IMemoryCache to return an array of keys 216 217 // stop for now 218 return; 219 } 220 } 221}