You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by th...@apache.org on 2011/09/08 20:04:24 UTC

svn commit: r1166828 - in /jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk: index/PrefixIndex.java mem/MemoryKernelImpl.java mem/NodeImpl.java mem/NodeListSmall.java util/StringCache.java

Author: thomasm
Date: Thu Sep  8 18:04:24 2011
New Revision: 1166828

URL: http://svn.apache.org/viewvc?rev=1166828&view=rev
Log:
Conserve memory (StringCache, property value array instead of HashMap)

Added:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/StringCache.java
Modified:
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java
    jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeListSmall.java

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java?rev=1166828&r1=1166827&r2=1166828&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/index/PrefixIndex.java Thu Sep  8 18:04:24 2011
@@ -17,7 +17,6 @@
 package org.apache.jackrabbit.mk.index;
 
 import java.util.Iterator;
-import java.util.Map.Entry;
 import org.apache.jackrabbit.mk.json.JsopTokenizer;
 import org.apache.jackrabbit.mk.mem.NodeImpl;
 
@@ -39,9 +38,9 @@ public class PrefixIndex implements Inde
 
     public void addOrRemoveNode(NodeImpl node, boolean add) {
         String nodePath = node.getPath();
-        for (Entry<String, String> e : node.getProperties()) {
-            String propertyName = e.getKey();
-            String value = e.getValue();
+        for (int i = 0, size = node.getPropertyCount(); i < size; i++) {
+            String propertyName = node.getProperty(i);
+            String value = node.getPropertyValue(i);
             addOrRemoveProperty(nodePath, propertyName, value, add);
         }
     }

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java?rev=1166828&r1=1166827&r2=1166828&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/MemoryKernelImpl.java Thu Sep  8 18:04:24 2011
@@ -33,7 +33,6 @@ import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.Map.Entry;
 
 /*
 
@@ -121,8 +120,8 @@ public class MemoryKernelImpl implements
 
     private void applyConfig(NodeImpl head) {
         NodeImpl config = head.getNode("config");
-        for (Entry<String, String> e : config.getProperties()) {
-            nodeMap.setSetting(e.getKey(), e.getValue());
+        for (int i = 0, size = config.getPropertyCount(); i < size; i++) {
+            nodeMap.setSetting(config.getProperty(i), config.getPropertyValue(i));
         }
     }
 

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java?rev=1166828&r1=1166827&r2=1166828&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeImpl.java Thu Sep  8 18:04:24 2011
@@ -16,18 +16,14 @@
  */
 package org.apache.jackrabbit.mk.mem;
 
+import java.util.ArrayList;
+import java.util.Iterator;
 import org.apache.jackrabbit.mk.Constants;
 import org.apache.jackrabbit.mk.json.JsopBuilder;
 import org.apache.jackrabbit.mk.json.JsopTokenizer;
 import org.apache.jackrabbit.mk.util.ExceptionFactory;
 import org.apache.jackrabbit.mk.util.PathUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
+import org.apache.jackrabbit.mk.util.StringCache;
 
 /**
  * An in-memory node, including all child nodes.
@@ -36,7 +32,7 @@ public class NodeImpl {
 
     private final long revId;
     private final NodeMap map;
-    private HashMap<String, String> properties;
+    private String[] propertyValuePairs;
     private NodeList childNodes;
     private String path;
     private long id;
@@ -59,8 +55,10 @@ public class NodeImpl {
             return this;
         }
         NodeImpl clone = new NodeImpl(map, revId);
-        if (properties != null) {
-            clone.properties = new HashMap<String, String>(properties);
+        if (propertyValuePairs != null) {
+            String[] s = new String[propertyValuePairs.length];
+            System.arraycopy(propertyValuePairs, 0, s, 0, s.length);
+            clone.propertyValuePairs = s;
         }
         if (childNodes != null) {
             clone.childNodes = childNodes.createClone(map, revId);
@@ -179,18 +177,54 @@ public class NodeImpl {
     }
 
     public boolean hasProperty(String propertyName) {
-        return properties != null && properties.containsKey(propertyName);
+        return propertyValuePairs != null && search(propertyName, propertyValuePairs) >= 0;
+    }
+
+    /**
+     * Return the index of the key within the array of key-value pairs. If
+     * found, the method returns the index of the key, if not this method
+     * returns (- index - 2). See also Arrays.binarySearch.
+     *
+     * @param key the key
+     * @param pair the key-value pair
+     * @return the index
+     */
+    private static int search(String key, String[] pair) {
+        int low = 0;
+        int high = pair.length / 2 - 1;
+        while (low <= high) {
+            int mid = (low + high) >> 1;
+            String middle = pair[mid * 2];
+            int result = middle.compareTo(key);
+            if (result < 0) {
+                low = mid + 1;
+            } else if (result > 0) {
+                high = mid - 1;
+            } else {
+                return mid * 2;
+            }
+        }
+        // not found
+        return -(low * 2 + 2);
     }
 
     public String getProperty(String propertyName) {
-        return properties == null ? null : properties.get(propertyName);
+        if (propertyValuePairs == null) {
+            return null;
+        }
+        int index = search(propertyName, propertyValuePairs);
+        if (index < 0) {
+            return null;
+        }
+        return propertyValuePairs[index + 1];
     }
 
     void append(JsopBuilder json, int depth, long offset, int count, boolean childNodeCount) {
         json.object();
-        if (properties != null) {
-            for (Entry<String, String> e : properties.entrySet()) {
-                json.key(e.getKey()).encodedValue(e.getValue());
+        String[] pv = propertyValuePairs;
+        if (pv != null) {
+            for (int i = 0, size = pv.length; i < size; i += 2) {
+                json.key(pv[i]).encodedValue(pv[i + 1]);
             }
         }
         if (childNodes == null) {
@@ -266,17 +300,49 @@ public class NodeImpl {
     }
 
     void setProperty(String name, String value) {
-        if (properties == null) {
-            properties = new HashMap<String, String>();
+        propertyValuePairs = updatePair(propertyValuePairs, name, value);
+    }
+
+    private static String[] updatePair(String[] pairs, String key, String value) {
+        if (pairs == null) {
+            if (value == null) {
+                return null;
+            } else {
+                return new String[] { key, value };
+            }
         }
-        if (value == null) {
-            properties.remove(name);
-            if (properties.size() == 0) {
-                properties = null;
+        int index = search(key, pairs);
+        if (index < 0) {
+            if (value == null) {
+                return pairs;
             }
-        } else {
-            properties.put(name, value);
+            index = -index - 2;
+            String[] newPairs = new String[pairs.length + 2];
+            if (index > 0) {
+                System.arraycopy(pairs, 0, newPairs, 0, index);
+            }
+            int len = newPairs.length - index;
+            if (len > 2) {
+                System.arraycopy(pairs, index, newPairs, index + 2, len - 2);
+            }
+            pairs = newPairs;
+            pairs[index] = StringCache.cache(key);
+        } else if (value == null) {
+            if (pairs.length == 2) {
+                return null;
+            }
+            String[] newPairs = new String[pairs.length - 2];
+            if (index > 0) {
+                System.arraycopy(pairs, 0, newPairs, 0, index);
+            }
+            int len = newPairs.length - index;
+            if (len > 0) {
+                System.arraycopy(pairs, index + 2, newPairs, index, len);
+            }
+            return newPairs;
         }
+        pairs[index + 1] = StringCache.cache(value);
+        return pairs;
     }
 
     public static NodeImpl parse(NodeMap map, JsopTokenizer t, long revId) {
@@ -322,12 +388,16 @@ public class NodeImpl {
         return childNodes.getNames(0, maxCount);
     }
 
-    public Iterable<Entry<String, String>> getProperties() {
-        if (properties == null) {
-            Map<String, String> e = Collections.emptyMap();
-            return e.entrySet();
-        }
-        return properties.entrySet();
+    public int getPropertyCount() {
+        return propertyValuePairs == null ? 0 : propertyValuePairs.length / 2;
+    }
+
+    public String getProperty(int index) {
+        return propertyValuePairs[index + index];
+    }
+
+    public String getPropertyValue(int index) {
+        return propertyValuePairs[index + index + 1];
     }
 
     public String toString() {
@@ -343,9 +413,10 @@ public class NodeImpl {
         json.setLineLength(120);
         json.encodedValue(map.formatId(id)).appendTag("=");
         json.object();
-        if (properties != null && properties.size() > 0) {
-            for (Entry<String, String> e : properties.entrySet()) {
-                json.key(e.getKey()).encodedValue(e.getValue());
+        String[] pv = propertyValuePairs;
+        if (pv != null) {
+            for (int i = 0, size = pv.length; i < size; i += 2) {
+                json.key(pv[i]).encodedValue(pv[i + 1]);
             }
         }
         if (childNodes != null && childNodes.size() > 0) {

Modified: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeListSmall.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeListSmall.java?rev=1166828&r1=1166827&r2=1166828&view=diff
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeListSmall.java (original)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/mem/NodeListSmall.java Thu Sep  8 18:04:24 2011
@@ -21,6 +21,7 @@ import org.apache.jackrabbit.mk.json.Jso
 import org.apache.jackrabbit.mk.mem.NodeImpl.ChildVisitor;
 import org.apache.jackrabbit.mk.util.ExceptionFactory;
 import org.apache.jackrabbit.mk.util.IOUtils;
+import org.apache.jackrabbit.mk.util.StringCache;
 import org.apache.jackrabbit.mk.util.StringUtils;
 
 public class NodeListSmall implements NodeList {
@@ -90,6 +91,7 @@ public class NodeListSmall implements No
             throw ExceptionFactory.get("Node already exists: " + name);
         }
         index = -index - 1;
+        name = StringCache.cache(name);
         names = StringUtils.arrayInsert(names, size, name);
         children = StringUtils.arrayInsert(children, size, x);
         sort = StringUtils.arrayInsert(sort, index, size);

Added: jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/StringCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/StringCache.java?rev=1166828&view=auto
==============================================================================
--- jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/StringCache.java (added)
+++ jackrabbit/sandbox/microkernel/src/main/java/org/apache/jackrabbit/mk/util/StringCache.java Thu Sep  8 18:04:24 2011
@@ -0,0 +1,166 @@
+/*
+ * 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.mk.util;
+
+import java.lang.ref.SoftReference;
+
+/**
+ * A simple string cache.
+ */
+public class StringCache {
+
+    public static final boolean OBJECT_CACHE = getBooleanSetting("mk.objectCache", true);
+    public static final int OBJECT_CACHE_SIZE = IOUtils.nextPowerOf2(getIntSetting("mk.objectCacheSize", 1024));
+
+    private static SoftReference<String[]> softCache = new SoftReference<String[]>(null);
+
+    private StringCache() {
+        // utility class
+    }
+
+    private static boolean getBooleanSetting(String name, boolean defaultValue) {
+        String s = getProperty(name);
+        if (s != null) {
+            try {
+                return Boolean.valueOf(s).booleanValue();
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+        return defaultValue;
+    }
+
+    private static int getIntSetting(String name, int defaultValue) {
+        String s = getProperty(name);
+        if (s != null) {
+            try {
+                return Integer.decode(s).intValue();
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+        return defaultValue;
+    }
+
+    private static String getProperty(String name) {
+        try {
+            return System.getProperty(name);
+        } catch (Exception e) {
+            // SecurityException
+            // applets may not do that - ignore
+            return null;
+        }
+    }
+
+    private static String[] getCache() {
+        String[] cache;
+        // softCache can be null due to a Tomcat problem
+        // a workaround is disable the system property org.apache.
+        // catalina.loader.WebappClassLoader.ENABLE_CLEAR_REFERENCES
+        if (softCache != null) {
+            cache = softCache.get();
+            if (cache != null) {
+                return cache;
+            }
+        }
+        try {
+            cache = new String[OBJECT_CACHE_SIZE];
+        } catch (OutOfMemoryError e) {
+            return null;
+        }
+        softCache = new SoftReference<String[]>(cache);
+        return cache;
+    }
+
+    /**
+     * Get the string from the cache if possible. If the string has not been
+     * found, it is added to the cache. If there is such a string in the cache,
+     * that one is returned.
+     *
+     * @param s the original string
+     * @return a string with the same content, if possible from the cache
+     */
+    public static String cache(String s) {
+        if (!OBJECT_CACHE) {
+            return s;
+        }
+        if (s == null) {
+            return s;
+        } else if (s.length() == 0) {
+            return "";
+        }
+        int hash = s.hashCode();
+        String[] cache = getCache();
+        if (cache != null) {
+            int index = hash & (OBJECT_CACHE_SIZE - 1);
+            String cached = cache[index];
+            if (cached != null) {
+                if (s.equals(cached)) {
+                    return cached;
+                }
+            }
+            cache[index] = s;
+        }
+        return s;
+    }
+
+    /**
+     * Get a string from the cache, and if no such string has been found, create
+     * a new one with only this content. This solves out of memory problems if
+     * the string is a substring of another, large string. In Java, strings are
+     * shared, which could lead to memory problems. This avoid such problems.
+     *
+     * @param s the string
+     * @return a string that is guaranteed not be a substring of a large string
+     */
+    public static String fromCacheOrNew(String s) {
+        if (!OBJECT_CACHE) {
+            return s;
+        }
+        if (s == null) {
+            return s;
+        } else if (s.length() == 0) {
+            return "";
+        }
+        int hash = s.hashCode();
+        String[] cache = getCache();
+        int index = hash & (OBJECT_CACHE_SIZE - 1);
+        if (cache == null) {
+            return s;
+        }
+        String cached = cache[index];
+        if (cached != null) {
+            if (s.equals(cached)) {
+                return cached;
+            }
+        }
+        // create a new object that is not shared
+        // (to avoid out of memory if it is a substring of a big String)
+        // NOPMD
+        s = new String(s);
+        cache[index] = s;
+        return s;
+    }
+
+    /**
+     * Clear the cache. This method is used for testing.
+     */
+    public static void clearCache() {
+        softCache = new SoftReference<String[]>(null);
+    }
+
+}