jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From tri...@apache.org
Subject svn commit: r471800 - in /jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state: Cache.java CacheManager.java MLRUItemStateCache.java
Date Mon, 06 Nov 2006 17:43:57 GMT
Author: tripod
Date: Mon Nov  6 09:43:57 2006
New Revision: 471800

URL: http://svn.apache.org/viewvc?view=rev&rev=471800
Log:
JCR-619 CacheManager (Memory Management in Jackrabbit)

Added:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java
  (with props)
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java
  (with props)
Modified:
    jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java

Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java?view=auto&rev=471800
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java
(added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java
Mon Nov  6 09:43:57 2006
@@ -0,0 +1,55 @@
+/*
+ * 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.jackrabbit.core.state;
+
+/**
+ * A <code>Cache</code> object
+ * A cache must call <code>CacheManager.getInstance().add(this)</code>
+ * to take part in the dynamic memory distribution.
+ */
+public interface Cache {
+
+    /**
+     * Set the new memory limit.
+     * @param size the size in bytes
+     */
+    void setMaxMemorySize(long size);
+
+    /**
+     * Get the current limit.
+     * @return the size in bytes
+     */
+    long getMaxMemorySize();
+
+    /**
+     * Get the amount of used memory.
+     * @return the size in bytes
+     */
+    long getMemoryUsed();
+
+    /**
+     * Get the number of accesses (get or set) until resetAccessCount was called.
+     * @return the count
+     */
+
+    long getAccessCount();
+
+    /**
+     * Reset the access counter.
+     */
+    void resetAccessCount();
+}

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/Cache.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url rev

Added: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java?view=auto&rev=471800
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java
(added)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java
Mon Nov  6 09:43:57 2006
@@ -0,0 +1,237 @@
+/*
+ * 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.jackrabbit.core.state;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.WeakHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class manages the size of the caches used in Jackrabbit.
+ * The combined size of all caches must be limited to avoid out of memory problems.
+ * The available memory is dynamically distributed across the caches each second.
+ * This class tries to calculates the best cache sizes by comparing the access counts of
each cache,
+ * and the used memory. The idea is, the more a cache is accessed, the more memory it should
get,
+ * while the cache should not shrink too quickly. A minimum and maximum size per cache is
defined as well.
+ * After distributing the memory in this way, there might be some unused memory
+ * (if one or more caches did not use some of the allocated memory).
+ * This unused memory is distributed evenly across the full caches.
+ *
+ */
+public class CacheManager implements Runnable {
+
+    /** Logger instance */
+    private static Logger log = LoggerFactory.getLogger(CacheManager.class);
+
+    /** The single instance */
+    private static CacheManager instance = new CacheManager();
+
+    /** Amount of memory to distribute accross the caches */
+    private static final long MAX_MEMORY = 16 * 1024 * 1024;
+
+    /** Minimum size of a cache */
+    private static final long MIN_MEMORY_PER_CACHE = 128 * 1024;
+
+    /** Maximum memory per cache (unless, there is some unused memory) */
+    private static final long MAX_MEMORY_PER_CACHE = 4 * 1024 * 1024;
+    private WeakHashMap caches = new WeakHashMap();
+
+    /** Rebalance the caches each ... milliseconds */
+    private static final int SLEEP = 1000;
+
+    /** The size of a big object, to detect if a cache is full or not */
+    private static final int BIG_OBJECT_SIZE = 16 * 1024;
+
+    public static CacheManager getInstance() {
+        return instance;
+    }
+
+    /**
+     * Constructs a new cache manager. Singleton pattern, so the constructor is private.
+     * A daemon thread is started.
+     */
+    private CacheManager() {
+        Thread t = new Thread(this);
+        t.setDaemon(true);
+        t.setName(getClass().getName());
+        t.start();
+    }
+
+    /**
+     * Run the (daemon) thread.
+     */
+    public void run() {
+        while(true) {
+            try {
+                Thread.sleep(SLEEP);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+            try {
+                resizeAll();
+            } catch(Throwable e) {
+                log.error("Error resizing caches", e);
+            }
+        }
+    }
+
+    /**
+     * Re-calcualte the maximum memory for each cache, and set the new limits.
+     *
+     */
+    private synchronized void resizeAll() {
+        // get strong references
+        // entries in a weak hash map may disappear any time
+        // so can't use size() / keySet() directly
+        // only using the iterator guarantees that we don't get null references
+        ArrayList list = new ArrayList();
+        for(Iterator it = caches.keySet().iterator(); it.hasNext(); ) {
+            list.add(it.next());
+        }
+        if(list.size() == 0) {
+            // nothing to do
+            return;
+        }
+        CacheInfo[] infos = new CacheInfo[list.size()];
+        for(int i=0; i<list.size(); i++) {
+            infos[i] = new CacheInfo((Cache) list.get(i));
+        }
+        // calculate the total access count and memory used
+        long totalAccessCount = 0;
+        long totalMemoryUsed = 0;
+        for(int i=0; i<infos.length; i++) {
+            totalAccessCount += infos[i].getAccessCount();
+            totalMemoryUsed += infos[i].getMemoryUsed();
+        }
+        // try to distribute the memory based on the access count and memory used
+        // (higher numbers - more memory)
+        // and find out how many caches are full
+        // 50% is distributed according to access count, and 50% according to memory used
+        double memoryPerAccess = (double)MAX_MEMORY / 2. / Math.max(1., (double)totalAccessCount);
+        double memoryPerUsed = (double)MAX_MEMORY / 2. / Math.max(1., (double)totalMemoryUsed);
+        int fullCacheCount = 0;
+        for(int i=0; i<infos.length; i++) {
+            CacheInfo info = infos[i];
+            long mem = (long)(memoryPerAccess * info.getAccessCount());
+            mem += (long)(memoryPerUsed * info.getMemoryUsed());
+            mem = Math.min(mem, MAX_MEMORY_PER_CACHE);
+            if(info.wasFull()) {
+                fullCacheCount++;
+            } else {
+                mem = Math.min(mem, info.getMemoryUsed());
+            }
+            mem = Math.min(mem, MAX_MEMORY_PER_CACHE);
+            mem = Math.max(mem, MIN_MEMORY_PER_CACHE);
+            info.setMemory(mem);
+        }
+        // calculate the unused memory
+        long unusedMemory = MAX_MEMORY;
+        for(int i=0; i<infos.length; i++) {
+            unusedMemory -= infos[i].getMemory();
+        }
+        // distribute the remaining memory evenly across the full caches
+        if(unusedMemory > 0 && fullCacheCount > 0) {
+            for(int i=0; i<infos.length; i++) {
+                CacheInfo info = infos[i];
+                if(info.wasFull()) {
+                    info.setMemory(info.getMemory() + unusedMemory / fullCacheCount);
+                }
+            }
+        }
+        // set the new limit and reset the access counters
+        for(int i=0; i<infos.length; i++) {
+            CacheInfo info = infos[i];
+            Cache cache = info.getCache();
+            log.debug(cache + " now:" + cache.getMaxMemorySize() + " used:"+info.getMemoryUsed()
+ " access:"+info.getAccessCount() + " new:" + info.getMemory());
+            cache.setMaxMemorySize(info.getMemory());
+            cache.resetAccessCount();
+        }
+    }
+
+    /**
+     * Add a new cache to the list.
+     *
+     * @param cache the cache to add
+     */
+    public synchronized void add(Cache cache) {
+        caches.put(cache, null);
+        resizeAll();
+    }
+
+    /**
+     * Remove a cache. As this class only has a weak reference to each cache,
+     * calling this method is not strictly required.
+     *
+     * @param cache the cache to remove
+     */
+    public synchronized void remove(Cache cache) {
+        caches.remove(cache);
+        resizeAll();
+    }
+
+    /**
+     * Internal copy of the cache information
+     */
+    public static class CacheInfo {
+        private Cache cache;
+        private long accessCount;
+        private long memory;
+        private long memoryUsed;
+        private boolean wasFull;
+
+        CacheInfo(Cache cache) {
+            this.cache = cache;
+            // copy the data as this runs in a different thread
+            // the exact values are not important, but it is important that the values don't
change
+            this.memory = cache.getMaxMemorySize();
+            this.memoryUsed = cache.getMemoryUsed();
+            this.accessCount = cache.getAccessCount();
+            // if the memory used plus one large object is smaller than the allocated memory,
+            // then the memory was not fully used
+            wasFull = memoryUsed + BIG_OBJECT_SIZE >= memory;
+        }
+
+        boolean wasFull() {
+            return wasFull;
+        }
+
+        long getAccessCount() {
+            return accessCount;
+        }
+
+        long getMemoryUsed() {
+            return memoryUsed;
+        }
+
+        void setMemory(long mem) {
+            this.memory = mem;
+        }
+
+        long getMemory() {
+            return memory;
+        }
+
+        Cache getCache() {
+            return cache;
+        }
+
+    }
+
+}

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/CacheManager.java
------------------------------------------------------------------------------
    svn:keywords = author date id revision url rev

Modified: jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java?view=diff&rev=471800&r1=471799&r2=471800
==============================================================================
--- jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
(original)
+++ jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
Mon Nov  6 09:43:57 2006
@@ -34,7 +34,7 @@
  * cache uses a rough estimate of the memory consuption of the cache item
  * states for calculating the maximum number of entries.
  */
-public class MLRUItemStateCache implements ItemStateCache {
+public class MLRUItemStateCache implements ItemStateCache, Cache {
     /** Logger instance */
     private static Logger log = LoggerFactory.getLogger(LRUItemStateCache.class);
 
@@ -45,11 +45,14 @@
     private long totalMem;
 
     /** the maximum of memory the cache may use */
-    private final long maxMem;
+    private long maxMem;
 
     /** the number of writes */
     private long numWrites = 0;
 
+    /** the access count */
+    private long accessCount = 0;
+
     /**
      * A cache for <code>ItemState</code> instances
      */
@@ -71,6 +74,7 @@
      */
     public MLRUItemStateCache(int maxMem) {
         this.maxMem = maxMem;
+        CacheManager.getInstance().add(this);
     }
 
     //-------------------------------------------------------< ItemStateCache >
@@ -88,6 +92,7 @@
      */
     public ItemState retrieve(ItemId id) {
         synchronized (cache) {
+            touch();
             Entry entry = (Entry) cache.remove(id);
             if (entry != null) {
                 // 'touch' item, by adding at end of list
@@ -104,6 +109,7 @@
      */
     public void update(ItemId id) {
         synchronized (cache) {
+            touch();
             Entry entry = (Entry) cache.get(id);
             if (entry != null) {
                 totalMem -= entry.size;
@@ -118,6 +124,7 @@
      */
     public void cache(ItemState state) {
         synchronized (cache) {
+            touch();
             ItemId id = state.getId();
             if (cache.containsKey(id)) {
                 log.warn("overwriting cached entry " + id);
@@ -126,22 +133,27 @@
             Entry entry = new Entry(state);
             cache.put(id, entry);
             totalMem += entry.size;
-            // remove items, if too many
-            while (totalMem > maxMem) {
-                id = (ItemId) cache.firstKey();
-                evict(id);
-            }
+            shrinkIfRequired();
             if (numWrites++%10000 == 0 && log.isDebugEnabled()) {
                 log.info(this + " size=" + cache.size() + ", " + totalMem + "/" + maxMem);
             }
         }
     }
 
+    private void shrinkIfRequired() {
+        // remove items, if too many
+        while (totalMem > maxMem) {
+            ItemId id = (ItemId) cache.firstKey();
+            evict(id);
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
     public void evict(ItemId id) {
         synchronized (cache) {
+            touch();
             Entry entry = (Entry) cache.remove(id);
             if (entry != null) {
                 totalMem -= entry.size;
@@ -198,6 +210,59 @@
                 list.add(entry.state);
             }
             return list;
+        }
+    }
+
+    private void touch() {
+        accessCount++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getAccessCount() {
+        return accessCount;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getMaxMemorySize() {
+        return maxMem;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getMemoryUsed() {
+        synchronized (cache) {
+            totalMem = 0;
+            Iterator iter = cache.values().iterator();
+            while (iter.hasNext()) {
+                Entry entry = (Entry) iter.next();
+                entry.recalc();
+                totalMem += entry.size;
+            }
+        }
+        return totalMem;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void resetAccessCount() {
+        synchronized (cache) {
+            accessCount = 0;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void setMaxMemorySize(long size) {
+        synchronized (cache) {
+            this.maxMem = size;
+            shrinkIfRequired();
         }
     }
 



Mime
View raw message