Package org.apache.excalibur.store.impl

Source Code of org.apache.excalibur.store.impl.StoreJanitorImpl

/*
* Copyright 2002-2004 The Apache Software Foundation
* Licensed  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.excalibur.store.impl;

import java.util.ArrayList;
import java.util.Iterator;

import org.apache.avalon.framework.activity.Startable;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.avalon.framework.parameters.ParameterException;
import org.apache.avalon.framework.parameters.Parameterizable;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.avalon.framework.thread.ThreadSafe;
import org.apache.excalibur.store.Store;
import org.apache.excalibur.store.StoreJanitor;

/**
* 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.
*
* @avalon.component
* @avalon.service type=StoreJanitor
* @x-avalon.info name=store-janitor
* @x-avalon.lifestyle type=singleton
*
* @author <a href="mailto:dev@avalon.apache.org">Avalon Development Team</a>
* @version CVS $Id: StoreJanitorImpl.java,v 1.4 2004/02/28 11:47:31 cziegeler Exp $
*/
public class StoreJanitorImpl
extends AbstractLogEnabled
implements StoreJanitor,
           Parameterizable,
           ThreadSafe,
           Runnable,
           Startable
{

    private boolean doRun = false;

    // Configuration parameters
    private int minFreeMemory = -1;
    private int maxHeapSize = -1;
    private int threadInterval = -1;
    private int minThreadInterval = 500;
    private boolean adaptiveThreadInterval = false;
    private int priority = -1;
    private double fraction;

    private Runtime jvm;
    private ArrayList storelist;
    private int index = -1;
    /** Should the gc be called on low memory? */
    protected boolean invokeGC = false;
   
    /**
     * Initialize the StoreJanitorImpl.
     * A few options can be used :
     * <UL>
     <LI><B>freememory</B>: How many bytes shall be always free in the JVM (Default: 1mb)</LI>
     <LI><B>heapsize</B>: Maximum possible size of the JVM memory consumption (Default: 64mb)</LI>
     <LI><B>cleanupthreadinterval</B>: How often (sec) shall run the cleanup thread (Default: 10s)</LI>
     <LI><B>adaptivethreadinterval</B> (experimental): Enable adaptive algorithm to determine thread interval
     *      (Default: false) When true, <code>cleanupthreadinterval</code> defines the maximum cleanup interval.
     *      Cleanup interval then is determined based on the memory fill rate: the faster memory is filled in,
     *      and the less free memory is left, the shorter is the cleanup time.</LI>
     <LI><B>threadpriority</B>: priority of the thread (1-10). (Default: 10)</LI>
     <LI><B>percent_to_free</B>: What fraction of the store to free when memory is low (1-100). (Default: 10%)</LI>
     <LI><B>invokegc</B>: Invoke the gc on low memory first (true|false; default: false)</LI>
     * </UL>
     *
     * @param params the Configuration of the application
     * @exception ParameterException
     */
    public void parameterize(Parameters params) throws ParameterException
    {
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Configure StoreJanitorImpl");
        }
        setJVM(Runtime.getRuntime());

        setMinFreeMemory(params.getParameterAsInteger("freememory", 1024 * 1024));
        setMaxHeapSize(params.getParameterAsInteger("heapsize", 60 * 1024 * 1024));
        // Parameter value is in seconds, converted to millis
        setThreadInterval(params.getParameterAsInteger("cleanupthreadinterval", 10) * 1000);
        setAdaptiveThreadInterval(params.getParameterAsBoolean("adaptivethreadinterval", false));
        setPriority(params.getParameterAsInteger("threadpriority",
                                                 Thread.currentThread().getPriority()));
        int percent = params.getParameterAsInteger("percent_to_free", 10);
        this.invokeGC = params.getParameterAsBoolean("invokegc", this.invokeGC);
       
        if (getMinFreeMemory() < 1)
        {
            throw new ParameterException("StoreJanitorImpl freememory parameter has to be greater then 1");
        }
        if (getMaxHeapSize() < 1)
        {
            throw new ParameterException("StoreJanitorImpl heapsize parameter has to be greater then 1");
        }
        if (getThreadInterval() < 1)
        {
            throw new ParameterException("StoreJanitorImpl cleanupthreadinterval parameter has to be greater then 1");
        }
        if (getPriority() < 1 || getPriority() > 10)
        {
            throw new ParameterException("StoreJanitorImpl threadpriority has to be between 1 and 10");
        }
        if (percent > 100 && percent < 1)
        {
            throw new ParameterException("StoreJanitorImpl percent_to_free, has to be between 1 and 100");
        }

        this.fraction = percent / 100.0D;
        setStoreList(new ArrayList());
       
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug("minimum free memory=" + this.getMinFreeMemory());
            getLogger().debug("heapsize=" + this.getMaxHeapSize());
            getLogger().debug("thread interval=" + this.getThreadInterval());
            getLogger().debug("priority=" + this.getPriority());
            getLogger().debug("percent=" + percent);
            getLogger().debug("invoke gc=" + this.invokeGC);
        }
    }

    public void start()
    {
        doRun = true;
        Thread checker = new Thread(this);
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Intializing checker thread");
        }
        checker.setPriority(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()
    {
        boolean firstRun = true;
        long inUse = memoryInUse(); // Amount of memory in use before sleep()
        long interval = Long.MAX_VALUE; // Sleep time in ms
        long maxRateOfChange = 1; // Used memory change rate in bytes per second

        while (doRun) {
            if (getAdaptiveThreadInterval())
            {
                // Monitor the rate of change of heap in use.
                long change = memoryInUse() - inUse;
                long rateOfChange = longDiv(change * 1000, interval); // bps.
                if (maxRateOfChange < rateOfChange)
                {
                    maxRateOfChange = (maxRateOfChange + rateOfChange) / 2;
                }
                if (getLogger().isDebugEnabled()) {
                    getLogger().debug("Waking after " + interval + "ms, in use change "
                                      + change + "b to " + memoryInUse() + "b, rate "
                                      + rateOfChange + "b/sec, max rate " + maxRateOfChange + "b/sec");
                }
            }

            // Amount of memory used is greater than heapsize
            if (memoryLow())
            {
                if ( this.invokeGC )
                {
                    this.freePhysicalMemory();
                }

                synchronized (this)
                {
                    if (!this.invokeGC
                        || (memoryLow() && getStoreList().size() > 0))
                    {
                           
                        freeMemory();
                        setIndex(getIndex() + 1);
                    }
                }
            }

            if (getAdaptiveThreadInterval())
            {
                // Calculate sleep interval based on the change rate and free memory left
                interval = minTimeToFill(maxRateOfChange) * 1000 / 2;
                if (interval > this.threadInterval)
                {
                    interval = this.threadInterval;
                }
                else if (interval < this.minThreadInterval)
                {
                    interval = this.minThreadInterval;
                }
                inUse = memoryInUse();
            }
            else
            {
                interval = this.threadInterval;
            }
            if (getLogger().isDebugEnabled())
            {
                getLogger().debug("Sleeping for " + interval + "ms");
            }

            // Sleep
            try
            {
                Thread.sleep(interval);
            }
            catch (InterruptedException ignore) {}

            // Ignore change in memory during the first run (startup)
            if (firstRun)
            {
                firstRun = false;
                inUse = memoryInUse();
            }
        }
    }

    /**
     * Method to check if memory is running low in the JVM.
     *
     * @return true if memory is low
     */
    private boolean memoryLow()
    {
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("JVM Memory total: " + getJVM().totalMemory()
                              + ", free: " + getJVM().freeMemory());
        }

        if ((getJVM().totalMemory() >= getMaxHeapSize())
                && (getJVM().freeMemory() < getMinFreeMemory()))
        {
            if (getLogger().isDebugEnabled())
            {
                getLogger().debug("Memory is low!");
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Calculate the JVM memory in use now.
     *
     * @return memory in use.
     */
    private long memoryInUse()
    {
        return jvm.totalMemory() - jvm.freeMemory();
    }

    /**
     * Calculate amount of time needed to fill all free memory with given
     * fill rate.
     *
     * @param rate memory fill rate in time per bytes
     * @return amount of time to fill all the memory with given fill rate
     */
    private long minTimeToFill(long rate)
    {
        return longDiv(jvm.freeMemory(), rate);
    }

    private long longDiv(long top, long bottom)
    {
        try
        {
            return top / bottom;
        }
        catch (Exception e)
        {
            return top > 0 ? Long.MAX_VALUE : Long.MIN_VALUE;
        }
    }

    /**
     * This method register the stores
     *
     * @param store the store to be registered
     */
    public synchronized void register(Store store)
    {
        getStoreList().add(store);
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Registered store instance " + store + ". Stores now: "
                              + getStoreList().size());
        }
    }

    /**
     * This method unregister the stores
     *
     * @param store the store to be unregistered
     */
    public synchronized void unregister(Store store)
    {
        getStoreList().remove(store);
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Unregistered store instance " + store + ". Stores now: "
                              + 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 getStoreList().iterator();
    }

    /**
     * Round Robin alghorithm for freeing the registered caches.
     */
    private void freeMemory()
    {
        // TODO: Alternative to RR might be to free same fraction from every storage.
        try
        {
            // Determine the store.
            if (getIndex() < getStoreList().size())
            {
                if (getIndex() == -1)
                {
                    setIndex(0);
                }
            }
            else
            {
                // Store list changed (one or more store has been removed).
                if (getLogger().isDebugEnabled())
                {
                    getLogger().debug("Restarting from the beginning");
                }
                setIndex(0);
            }

            // Delete proportionate elements out of the store as configured.
            Store store = (Store)getStoreList().get(getIndex());
            int limit = calcToFree(store);
            if (getLogger().isDebugEnabled())
            {
                getLogger().debug("Freeing " + limit + " items from store N " + getIndex());
            }
            for (int i=0; i < limit; i++)
            {
                try
                {
                    store.free();
                }
                catch (OutOfMemoryError e)
                {
                    getLogger().error("OutOfMemoryError in freeMemory()");
                }
            }
        }
        catch (Exception e)
        {
            getLogger().error("Error in freeMemory()", e);
        }
        catch (OutOfMemoryError e)
        {
            getLogger().error("OutOfMemoryError in freeMemory()");
        }
    }

    /**
     * This method claculates 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;
        }
        final int res = (int)(cnt * fraction);
        if ( getLogger().isDebugEnabled() )
        {
            getLogger().debug("Calculating size for store " + store + " with size " + cnt + " : " + res);
        }
        return res;
    }

    /**
     * This method forces the garbage collector
     */
    private void freePhysicalMemory()
    {
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Invoking garbage collection. Memory total: "
                              + getJVM().totalMemory() + ", free: "
                              + getJVM().freeMemory());
        }

        getJVM().runFinalization();
        getJVM().gc();

        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Garbage collection complete. Memory total: "
                              + getJVM().totalMemory() + ", free: "
                              + getJVM().freeMemory());
        }
    }
    

    private int getMinFreeMemory()
    {
        return this.minFreeMemory;
    }

    private void setMinFreeMemory(int _freememory)
    {
        this.minFreeMemory = _freememory;
    }

    private int getMaxHeapSize()
    {
        return this.maxHeapSize;
    }

    private void setMaxHeapSize(int _heapsize)
    {
        this.maxHeapSize = _heapsize;
    }

    private int getPriority()
    {
        return this.priority;
    }

    private void setPriority(int _priority)
    {
        this.priority = _priority;
    }

    private int getThreadInterval()
    {
        return this.threadInterval;
    }

    private void setThreadInterval(int _threadInterval)
    {
        this.threadInterval = _threadInterval;
    }

    private boolean getAdaptiveThreadInterval()
    {
        return this.adaptiveThreadInterval;
    }

    private void setAdaptiveThreadInterval(boolean _adaptiveThreadInterval)
    {
        this.adaptiveThreadInterval = _adaptiveThreadInterval;
    }

    private Runtime getJVM()
    {
        return this.jvm;
    }

    private void setJVM(Runtime _jvm)
    {
        this.jvm = _jvm;
    }

    private ArrayList getStoreList()
    {
        return this.storelist;
    }

    private void setStoreList(ArrayList _storelist)
    {
        this.storelist = _storelist;
    }

    private void setIndex(int _index)
    {
        if (getLogger().isDebugEnabled())
        {
            getLogger().debug("Setting index=" + _index);
        }
        this.index = _index;
    }

    private int getIndex()
    {
        return this.index;
    }
}
TOP

Related Classes of org.apache.excalibur.store.impl.StoreJanitorImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.