/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.cocoon.components.store; import org.apache.avalon.framework.activity.Startable; import org.apache.avalon.framework.configuration.Configurable; import org.apache.avalon.framework.configuration.Configuration; import org.apache.avalon.framework.configuration.ConfigurationException; import org.apache.avalon.framework.logger.AbstractLogEnabled; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.thread.ThreadSafe; import java.util.ArrayList; import java.util.Iterator; /** * This class is a implentation of a StoreJanitor. Store classes * can register to the StoreJanitor. When memory is too low, * the StoreJanitor frees the registered caches until memory is normal. * * @deprecated Use the Avalon Excalibur Store instead. * * @author <a href="mailto:cs@ffzj0ia9.bank.dresdner.net">Christian Schmitt</a> * @author <a href="mailto:g-froehlich@gmx.de">Gerhard Froehlich</a> * @author <a href="mailto:proyal@managingpartners.com">Peter Royal</a> * @version CVS $Id$ */ public class StoreJanitorImpl extends AbstractLogEnabled implements StoreJanitor, Configurable, ThreadSafe, Runnable, Startable { private int freememory = -1; private int heapsize = -1; private int cleanupthreadinterval = -1; private int priority = -1; private Runtime jvm; private ArrayList storelist; private int index = -1; private static boolean doRun = false; private double fraction; /** * Initialize the StoreJanitorImpl. * A few options can be used : * <UL> * <LI>freememory = how many bytes shall be always free in the jvm</LI> * <LI>heapsize = max. size of jvm memory consumption</LI> * <LI>cleanupthreadinterval = how often (sec) shall run the cleanup thread</LI> * <LI>threadpriority = priority of the thread (1-10). (Default: 10)</LI> * </UL> * * @param conf the Configuration of the application * @exception ConfigurationException */ public void configure(Configuration conf) throws ConfigurationException { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Configure StoreJanitorImpl"); } this.setJVM(Runtime.getRuntime()); Parameters params = Parameters.fromConfiguration(conf); this.setFreememory(params.getParameterAsInteger("freememory",1000000)); this.setHeapsize(params.getParameterAsInteger("heapsize",60000000)); this.setCleanupthreadinterval(params.getParameterAsInteger("cleanupthreadinterval",10)); this.setPriority(params.getParameterAsInteger( "threadpriority", Thread.currentThread().getPriority())); int percent = params.getParameterAsInteger("percent_to_free", 10); if ((this.getFreememory() < 1)) { throw new ConfigurationException("StoreJanitorImpl freememory parameter has to be greater then 1"); } if ((this.getHeapsize() < 1)) { throw new ConfigurationException("StoreJanitorImpl heapsize parameter has to be greater then 1"); } if ((this.getCleanupthreadinterval() < 1)) { throw new ConfigurationException("StoreJanitorImpl cleanupthreadinterval parameter has to be greater then 1"); } if ((this.getPriority() < 1)) { throw new ConfigurationException("StoreJanitorImpl threadpriority has to be greater then 1"); } if ((percent > 100 && percent < 1)) { throw new ConfigurationException("StoreJanitorImpl percent_to_free, has to be between 1 and 100"); } this.fraction = percent / 100.0; this.setStoreList(new ArrayList()); } public void start() { doRun = true; Thread checker = new Thread(this); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Intializing checker thread"); } checker.setPriority(this.getPriority()); checker.setDaemon(true); checker.setName("checker"); checker.start(); } public void stop() { doRun = false; } /** * The "checker" thread checks if memory is running low in the jvm. */ public void run() { while (doRun) { // amount of memory used is greater then heapsize if (this.memoryLow()) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Invoking garbage collection, total memory = " + this.getJVM().totalMemory() + ", free memory = " + this.getJVM().freeMemory()); } //this.freePhysicalMemory(); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Garbage collection complete, total memory = " + this.getJVM().totalMemory() + ", free memory = " + this.getJVM().freeMemory()); } synchronized (this) { if (this.memoryLow() && this.getStoreList().size() > 0) { this.freeMemory(); this.setIndex(this.getIndex() + 1); } } } try { Thread.sleep(this.cleanupthreadinterval * 1000); } catch (InterruptedException ignore) {} } } /** * Method to check if memory is running low in the JVM. * * @return true if memory is low */ private boolean memoryLow() { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("JVM total Memory: " + this.getJVM().totalMemory()); this.getLogger().debug("JVM free Memory: " + this.getJVM().freeMemory()); } if((this.getJVM().totalMemory() >= this.getHeapsize()) && (this.getJVM().freeMemory() < this.getFreememory())) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Memory is low = true"); } return true; } else { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Memory is low = false"); } return false; } } /** * This method register the stores * * @param store the store to be registered */ public void register(Store store) { this.getStoreList().add(store); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Registering store instance"); this.getLogger().debug("Size of StoreJanitor now:" + this.getStoreList().size()); } } /** * This method unregister the stores * * @param store the store to be unregistered */ public void unregister(Store store) { this.getStoreList().remove(store); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Unregister store instance"); this.getLogger().debug("Size of StoreJanitor now:" + this.getStoreList().size()); } } /** * This method return a java.util.Iterator of every registered stores * * <i>The iterators returned is fail-fast: if list is structurally * modified at any time after the iterator is created, in any way, the * iterator will throw a ConcurrentModificationException. Thus, in the * face of concurrent modification, the iterator fails quickly and * cleanly, rather than risking arbitrary, non-deterministic behavior at * an undetermined time in the future.</i> * * @return a java.util.Iterator */ public Iterator iterator() { return this.getStoreList().iterator(); } /** * Round Robin alghorithm for freeing the registerd caches. */ private void freeMemory() { Store store; try { //Determine elements in Store: if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("StoreList size=" + this.getStoreList().size()); this.getLogger().debug("Actual Index position: " + this.getIndex()); } if (this.getIndex() < this.getStoreList().size()) { if(this.getIndex() == -1) { this.setIndex(0); store = (Store)this.getStoreList().get(this.getIndex()); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Freeing Store: " + this.getIndex()); } //delete proportionate elements out of the cache as //configured. int limit = this.calcToFree(store); for (int i=0; i < limit; i++) { store.free(); } } else { store = (Store)this.getStoreList().get(this.getIndex()); if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Freeing Store: " + this.getIndex()); } //delete proportionate elements out of the cache as //configured. int limit = this.calcToFree(store); for (int i=0; i < limit; i++) { store.free(); } } } else { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Starting from the beginning"); } this.resetIndex(); this.setIndex(0); store = (Store)this.getStoreList().get(this.getIndex()); //delete proportionate elements out of the cache as //configured. int limit = this.calcToFree(store); for (int i=0; i < limit; i++) { store.free(); } } } catch(Exception e) { this.getLogger().error("Error in freeMemory()",e); } } /** * This method calculates the number of Elements to be freememory * out of the Cache. * * @param store the Store which was selected as victim * @return number of elements to be removed! */ private int calcToFree(Store store) { int cnt = store.size(); if (cnt < 0) { if (getLogger().isDebugEnabled()) { getLogger().debug("Unknown size of the store: " + store); } return 0; } return (int)(cnt * fraction); } /** * This method forces the garbage collector private void freePhysicalMemory() { this.getJVM().runFinalization(); this.getJVM().gc(); } */ private int getFreememory() { return freememory; } private void setFreememory(int _freememory) { this.freememory = _freememory; } private int getHeapsize() { return this.heapsize; } private void setHeapsize(int _heapsize) { this.heapsize = _heapsize; } private int getCleanupthreadinterval() { return this.cleanupthreadinterval; } private void setCleanupthreadinterval(int _cleanupthreadinterval) { this.cleanupthreadinterval = _cleanupthreadinterval; } private int getPriority() { return this.priority; } private void setPriority(int _priority) { this.priority = _priority; } private Runtime getJVM() { return this.jvm; } private void setJVM(Runtime _runtime) { this.jvm = _runtime; } private ArrayList getStoreList() { return this.storelist; } private void setStoreList(ArrayList _storelist) { this.storelist = _storelist; } private void setIndex(int _index) { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Setting index=" + _index); } this.index = _index; } private int getIndex() { return this.index; } private void resetIndex() { if (this.getLogger().isDebugEnabled()) { this.getLogger().debug("Reseting index"); } this.index = -1; } }