You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2010/10/21 16:48:26 UTC

svn commit: r1026017 - in /jackrabbit/branches/2.0: ./ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/

Author: jukka
Date: Thu Oct 21 14:48:25 2010
New Revision: 1026017

URL: http://svn.apache.org/viewvc?rev=1026017&view=rev
Log:
2.0: Merged revisions 1025933 and 1025957 (JCR-2699)

Modified:
    jackrabbit/branches/2.0/   (props changed)
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/LRUNodeIdCache.java
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/PropertyState.java
    jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java

Propchange: jackrabbit/branches/2.0/
------------------------------------------------------------------------------
--- svn:mergeinfo (original)
+++ svn:mergeinfo Thu Oct 21 14:48:25 2010
@@ -1,6 +1,6 @@
 /jackrabbit/branches/1.5:794012,794100,794102
-/jackrabbit/branches/2.1:955309,955314,982266,982277,982505,998310
+/jackrabbit/branches/2.1:955309,955314,982266,982277,982505,998310,1025933,1025957
 /jackrabbit/sandbox/JCR-1456:774917-886178
 /jackrabbit/sandbox/JCR-2170:812417-816332
 /jackrabbit/sandbox/tripod-JCR-2209:795441-795863
-/jackrabbit/trunk:891595,891629,892253,892263,894150-894151,896408,896513,896532,896857,896870,896876,896908,896940,896942-896943,896969,896977,897071,897836,897842,897858,897935,897983,897992-897993,897996,898002,898042,898267,898325,898540,898677,898699,898701,898715,898872,899102,899181,899391,899393-899394,899583,899594,899643,900305,900310,900314,900453,900702,900736,900762-900763,900767,900782,901095,901122,901139,901144,901170,901176,901191,901193,901196,901216,901228,901285,902058,902062,926324,928888,936668,955222,955229,955307,955852,965539,996810
+/jackrabbit/trunk:891595,891629,892253,892263,894150-894151,896408,896513,896532,896857,896870,896876,896908,896940,896942-896943,896969,896977,897071,897836,897842,897858,897935,897983,897992-897993,897996,898002,898042,898267,898325,898540,898677,898699,898701,898715,898872,899102,899181,899391,899393-899394,899583,899594,899643,900305,900310,900314,900453,900702,900736,900762-900763,900767,900782,901095,901122,901139,901144,901170,901176,901191,901193,901196,901216,901228,901285,902058,902062,926324,928888,936668,955222,955229,955307,955852,965539,996810,1001707,1002065-1002066,1002084,1002101-1002102

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/BundleCache.java Thu Oct 21 14:48:25 2010
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.core.persistence.util;
 
-import org.apache.commons.collections.map.LinkedMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 import org.apache.jackrabbit.core.id.NodeId;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
@@ -54,15 +56,29 @@ public class BundleCache {
     /**
      * a map of the cache entries
      */
-    private LinkedMap bundles = new LinkedMap();
+    private final LinkedHashMap<NodeId, NodePropBundle> bundles;
 
     /**
      * Creates a new BundleCache
      *
      * @param maxSize the maximum size of this cache in bytes.
      */
+    @SuppressWarnings("serial")
     public BundleCache(long maxSize) {
         this.maxSize = maxSize;
+        this.bundles = new LinkedHashMap<NodeId, NodePropBundle>(
+                (int) maxSize / 1024, 0.75f, true /* access-ordered */) {
+            @Override
+            protected boolean removeEldestEntry(
+                    Map.Entry<NodeId, NodePropBundle> e) {
+                if (curSize > BundleCache.this.maxSize) {
+                    curSize -= e.getValue().getSize();
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        };
     }
 
     /**
@@ -90,11 +106,9 @@ public class BundleCache {
      * @param id the id of the bundle
      * @return the cached bundle or <code>null</code>
      */
-    public NodePropBundle get(NodeId id) {
-        Entry entry = (Entry) bundles.remove(id);
-        if (entry != null) {
-            // at end
-            bundles.put(id, entry);
+    public synchronized NodePropBundle get(NodeId id) {
+        NodePropBundle bundle = bundles.get(id);
+        if (bundle != null) {
             hits++;
         } else {
             misses++;
@@ -106,32 +120,21 @@ public class BundleCache {
             log.info("num=" + bundles.size() + " mem=" + c + "k max=" + m + "k avg=" + a
                     + " hits=" + hits + " miss=" + misses);
         }
-        return entry == null ? null : entry.bundle;
+        return bundle;
     }
 
     /**
-     * Puts a bunlde to the cache. If the new size of the cache exceeds the
-     * {@link #getMaxSize() max size} of the cache it will remove bundles from
-     * this cache until the limit is satisfied.
+     * Puts a bundle to the cache.
      *
      * @param bundle the bunlde to put to the cache
      */
-    public void put(NodePropBundle bundle) {
-        Entry entry = (Entry) bundles.remove(bundle.getId());
-        if (entry == null) {
-            entry = new Entry(bundle, bundle.getSize());
-        } else {
-            curSize -= entry.size;
-            entry.bundle = bundle;
-            entry.size = bundle.getSize();
-        }
-        bundles.put(bundle.getId(), entry);
-        curSize += entry.size;
-        // now limit size of cache
-        while (curSize > maxSize) {
-            entry = (Entry) bundles.remove(0);
-            curSize -= entry.size;
+    public synchronized void put(NodePropBundle bundle) {
+        NodePropBundle previous = bundles.get(bundle.getId());
+        if (previous != null) {
+            curSize -= previous.getSize();
         }
+        bundles.put(bundle.getId(), bundle);
+        curSize += bundle.getSize();
     }
 
     /**
@@ -141,7 +144,7 @@ public class BundleCache {
      * @return <code>true</code> if the bundle is cached;
      *         <code>false</code> otherwise.
      */
-    public boolean contains(NodeId id) {
+    public synchronized boolean contains(NodeId id) {
         return bundles.containsKey(id);
     }
 
@@ -152,51 +155,22 @@ public class BundleCache {
      * @return the previously cached bunlde or <code>null</code> of the bundle
      *         was not cached.
      */
-    public NodePropBundle remove(NodeId id) {
-        Entry entry = (Entry) bundles.remove(id);
-        if (entry != null) {
-            curSize -= entry.size;
-            return entry.bundle;
-        } else {
-            return null;
+    public synchronized NodePropBundle remove(NodeId id) {
+        NodePropBundle bundle = bundles.remove(id);
+        if (bundle != null) {
+            curSize -= bundle.getSize();
         }
+        return bundle;
     }
 
     /**
      * Clears this cache and removes all bundles.
      */
-    public void clear() {
+    public synchronized void clear() {
         bundles.clear();
         curSize = 0;
         hits = 0;
         misses = 0;
     }
 
-    /**
-     * Internal class that holds the bundles.
-     */
-    private static final class Entry {
-
-        /**
-         * the cached bundle
-         */
-        private NodePropBundle bundle;
-
-        /**
-         * the memory usage of the bundle in bytes
-         */
-        private long size;
-
-        /**
-         * Creates a new entry.
-         *
-         * @param bundle the bundle to cache
-         * @param size the size of the bundle
-         */
-        public Entry(NodePropBundle bundle, long size) {
-            this.bundle = bundle;
-            this.size = size;
-        }
-    }
-
 }

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/LRUNodeIdCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/LRUNodeIdCache.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/LRUNodeIdCache.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/LRUNodeIdCache.java Thu Oct 21 14:48:25 2010
@@ -16,7 +16,9 @@
  */
 package org.apache.jackrabbit.core.persistence.util;
 
-import org.apache.commons.collections.map.LinkedMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
 import org.apache.jackrabbit.core.id.NodeId;
 import org.slf4j.LoggerFactory;
 import org.slf4j.Logger;
@@ -34,7 +36,7 @@ public class LRUNodeIdCache {
     /**
      * The maximum number of ids to cache
      */
-    private long maxSize = 10240;
+    private static final int maxSize = 10240;
 
     /**
      * the number of cache hits
@@ -49,7 +51,14 @@ public class LRUNodeIdCache {
     /**
      * the map of cached ids
      */
-    private final LinkedMap missing = new LinkedMap();
+    @SuppressWarnings("serial")
+    private final LinkedHashMap<NodeId, NodeId> missing =
+        new LinkedHashMap<NodeId, NodeId>(maxSize * 2, 0.75f, true) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<NodeId, NodeId> e) {
+                return size() > maxSize;
+            }
+        };
 
     /**
      * Checks if the given id is contained in this cached.
@@ -58,31 +67,25 @@ public class LRUNodeIdCache {
      * @return <code>true</code> if the id is cached;
      *         <code>false</code> otherwise.
      */
-    public boolean contains(NodeId id) {
-        Object o = missing.remove(id);
-        if (o == null) {
-            misses++;
-        } else {
-            missing.put(id, id);
+    public synchronized boolean contains(NodeId id) {
+        boolean rv = missing.get(id) != null;
+        if (rv) {
             hits++;
+        } else {
+            misses++;
         }
         if (log.isInfoEnabled() && (hits + misses) % 10000 == 0) {
             log.info("num=" + missing.size() + "/" + maxSize + " hits=" + hits + " miss=" + misses);
         }
-        return o != null;
+        return rv;
     }
 
     /**
      * Puts the given id to this cache.
      * @param id the id to put.
      */
-    public void put(NodeId id) {
-        if (!missing.containsKey(id)) {
-            if (missing.size() == maxSize) {
-                missing.remove(0);
-            }
-            missing.put(id, id);
-        }
+    public synchronized void put(NodeId id) {
+        missing.put(id, id);
     }
 
     /**
@@ -91,14 +94,14 @@ public class LRUNodeIdCache {
      * @return <code>true</code> if the id was cached;
      *         <code>false</code> otherwise.
      */
-    public boolean remove(NodeId id) {
+    public synchronized boolean remove(NodeId id) {
         return missing.remove(id) != null;
     }
 
     /**
      * Clears this cache.
      */
-    public void clear() {
+    public synchronized void clear() {
         missing.clear();
     }
 

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemState.java Thu Oct 21 14:48:25 2010
@@ -421,4 +421,5 @@ public abstract class ItemState {
      * @return the approximate memory consumption of this state.
      */
     public abstract long calculateMemoryFootprint();
+
 }

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/MLRUItemStateCache.java Thu Oct 21 14:48:25 2010
@@ -16,7 +16,10 @@
  */
 package org.apache.jackrabbit.core.state;
 
-import java.util.Iterator;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
 
 import org.apache.commons.collections.map.LinkedMap;
 import org.apache.jackrabbit.core.id.ItemId;
@@ -41,16 +44,16 @@ public class MLRUItemStateCache implemen
     public static final int DEFAULT_MAX_MEM = 4 * 1024 * 1024;
 
     /** the amount of memory the entries use */
-    private long totalMem;
+    private volatile long totalMem;
 
     /** the maximum of memory the cache may use */
-    private long maxMem;
+    private volatile long maxMem;
 
     /** the number of writes */
-    private long numWrites;
+    private volatile long numWrites;
 
     /** the access count */
-    private long accessCount;
+    private volatile long accessCount;
 
     /** the cache access listeners */
     private CacheAccessListener accessListener;
@@ -58,7 +61,7 @@ public class MLRUItemStateCache implemen
     /**
      * A cache for <code>ItemState</code> instances
      */
-    private final LinkedMap cache = new LinkedMap();
+    private final Map<ItemId, Entry> cache;
 
     /**
      * Constructs a new, empty <code>ItemStateCache</code> with a maximum amount
@@ -74,8 +77,21 @@ public class MLRUItemStateCache implemen
      *
      * @param maxMem the maximum amount of memory this cache may use.
      */
+    @SuppressWarnings("serial")
     private MLRUItemStateCache(int maxMem) {
         this.maxMem = maxMem;
+        this.cache = new LinkedHashMap<ItemId, MLRUItemStateCache.Entry>(
+                maxMem / 1024, 0.75f, true /* access-ordered */) {
+            @Override
+            protected boolean removeEldestEntry(Map.Entry<ItemId, Entry> e) {
+                if (totalMem > MLRUItemStateCache.this.maxMem) {
+                    totalMem -= e.getValue().size;
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+        };
     }
 
     //-------------------------------------------------------< ItemStateCache >
@@ -94,10 +110,8 @@ public class MLRUItemStateCache implemen
     public ItemState retrieve(ItemId id) {
         touch();
         synchronized (cache) {
-            Entry entry = (Entry) cache.remove(id);
+            Entry entry = cache.get(id);
             if (entry != null) {
-                // 'touch' item, by adding at end of list
-                cache.put(id, entry);
                 return entry.state;
             } else {
                 return null;
@@ -110,7 +124,12 @@ public class MLRUItemStateCache implemen
      */
     public ItemState[] retrieveAll() {
         synchronized (cache) {
-            return (ItemState[]) cache.values().toArray(new ItemState[cache.size()]);
+            ItemState[] states = new ItemState[cache.size()];
+            int i = 0;
+            for (Entry entry : cache.values()) {
+                states[i++] = entry.state;
+            }
+            return states;
         }
     }
 
@@ -120,7 +139,7 @@ public class MLRUItemStateCache implemen
     public void update(ItemId id) {
         touch();
         synchronized (cache) {
-            Entry entry = (Entry) cache.get(id);
+            Entry entry = cache.get(id);
             if (entry != null) {
                 totalMem -= entry.size;
                 entry.recalc();
@@ -141,33 +160,21 @@ public class MLRUItemStateCache implemen
                 evict(id);
             }
             Entry entry = new Entry(state);
-            cache.put(id, entry);
             totalMem += entry.size;
-            shrinkIfRequired();
+            cache.put(id, entry);
             if (numWrites++ % 10000 == 0 && log.isDebugEnabled()) {
                 log.debug(this + " size=" + cache.size() + ", " + totalMem + "/" + maxMem);
             }
         }
     }
 
-    private void shrinkIfRequired() {
-        // remove items, if too many
-        synchronized (cache) {
-            while (totalMem > maxMem) {
-                ItemId id = (ItemId) cache.firstKey();
-                Entry entry = (Entry) cache.remove(id);
-                totalMem -= entry.size;
-            }
-        }
-    }
-
     /**
      * {@inheritDoc}
      */
     public void evict(ItemId id) {
         touch();
         synchronized (cache) {
-            Entry entry = (Entry) cache.remove(id);
+            Entry entry = cache.remove(id);
             if (entry != null) {
                 totalMem -= entry.size;
             }
@@ -220,15 +227,6 @@ public class MLRUItemStateCache implemen
      * {@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;
     }
 
@@ -247,7 +245,21 @@ public class MLRUItemStateCache implemen
     public void setMaxMemorySize(long size) {
         synchronized (cache) {
             this.maxMem = size;
-            shrinkIfRequired();
+
+            // remove items, if too many
+            if (totalMem > maxMem) {
+                totalMem = 0;
+                List<Map.Entry<ItemId, Entry>> entries =
+                    new ArrayList<Map.Entry<ItemId, Entry>>(cache.entrySet());
+                for (Map.Entry<ItemId, Entry> entry : entries) {
+                    long entrySize = entry.getValue().size;
+                    if (totalMem + entrySize > maxMem) {
+                        cache.remove(entry.getKey());
+                    } else {
+                        totalMem += entrySize;
+                    }
+                }
+            }
         }
     }
 

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/NodeState.java Thu Oct 21 14:48:25 2010
@@ -802,27 +802,26 @@ public class NodeState extends ItemState
     /**
      * {@inheritDoc}
      */
+    @Override
     public long calculateMemoryFootprint() {
         /*
         private Name nodeTypeName;
         private Set mixinTypeNames = Collections.EMPTY_SET;
         private NodeId id;
         private NodeId parentId;
-        private NodeDefId defId;
         private ChildNodeEntries childNodeEntries = new ChildNodeEntries();
-        private boolean sharedChildNodeEntries = false;
         private HashSet propertyNames = new HashSet();
-        private boolean sharedPropertyNames = false;
+        private boolean sharedSet = Set<NodeId>;
+        private boolean sharedSetRW = false;
+        private NodeStateListener listener = ...;
 
-        we assume an average Name localname of 30 chars.
-        NodeId = 8 + UUID(24) + hashcode(4) = 36
-        Name = 8 + hash(4) + string(38+2*len) + namespace(4) + localName(38+2*len) ~ 250
-        NodeDefId = 8 + id(4) = 12
-        ChildNodeEntries = 8 + n * (name(256) + index(4) + id(36) + hashentry(16)) ~ n*300
-        PropNames = 8 + n * ( name(250))
+        We assume only 16 bytes per name or node id,
+        as they are shared between states
+        ChildNodeEntries = 8 + n * (name(16) + index(4) + id(16) + hashentry(16)) ~ n*52
+        MixinTypeNames/PropNames = 8 + n * (name(16) + hashentry(16))
         */
-        return 350 + mixinTypeNames.size() * 250 + childNodeEntries.size() * 300
-                + propertyNames.size() * 250;
+        return 100 + mixinTypeNames.size() * 32 + childNodeEntries.size() * 52
+                + propertyNames.size() * 32;
     }
 
     /**

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/PropertyState.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/PropertyState.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/PropertyState.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/PropertyState.java Thu Oct 21 14:48:25 2010
@@ -196,20 +196,21 @@ public class PropertyState extends ItemS
     /**
      * {@inheritDoc}
      */
+    @Override
     public long calculateMemoryFootprint() {
         /*
         private PropertyId id;
         private InternalValue[] values;
         private int type;
         private boolean multiValued;
-        private PropDefId defId;
 
-        we assume an average Name localname of 30 chars.
-        PropertyId = 8 + nodeId(36) * name(250) + hash(4) ~ 300;
-        NodeDefId = 8 + id(4) = 12
+        We assume only 16 bytes per name or node id,
+        as they are shared between states
+        PropertyId = 8 + nodeId(16) + name(16) + hash(4) ~ 44;
         InternalValue = 8 + n * (values) ~ 8 + n*100;
         value=approx 100 bytes.
         */
-        return 350 + values.length * 100;
+        return 64 + values.length * 100;
     }
+
 }

Modified: jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java?rev=1026017&r1=1026016&r2=1026017&view=diff
==============================================================================
--- jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java (original)
+++ jackrabbit/branches/2.0/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/SharedItemStateManager.java Thu Oct 21 14:48:25 2010
@@ -257,9 +257,12 @@ public class SharedItemStateManager
         ISMLocking.ReadLock readLock = acquireReadLock(id);
         try {
             // check internal first
-            if (hasNonVirtualItemState(id)) {
-                return getNonVirtualItemState(id);
-            }
+            return getNonVirtualItemState(id);
+        } catch (NoSuchItemStateException e) {
+            // Fall through to virtual state providers. We can afford the
+            // exception-for-control-flow performance hit here, as almost
+            // all performance-critical content is non-virtual. With this
+            // catch we can avoid an extra hasNonVirtualItemState() call.
         } finally {
             readLock.release();
         }
@@ -1700,21 +1703,28 @@ public class SharedItemStateManager
      */
     private ItemState getNonVirtualItemState(ItemId id)
             throws NoSuchItemStateException, ItemStateException {
-
-        // check cache; synchronized to ensure an entry is not created twice.
-        synchronized (cache) {
-            ItemState state = cache.retrieve(id);
-            if (state == null) {
-                // not found in cache, load from persistent storage
-                state = loadItemState(id);
-                state.setStatus(ItemState.STATUS_EXISTING);
-                // put it in cache
-                cache.cache(state);
-                // set parent container
-                state.setContainer(this);
+        ItemState state = cache.retrieve(id);
+        if (state == null) {
+            // not found in cache, load from persistent storage
+            state = loadItemState(id);
+            state.setStatus(ItemState.STATUS_EXISTING);
+            synchronized (this) {
+                // Use a double check to ensure that the cache entry is
+                // not created twice. We don't synchronize the entire
+                // method to allow the first cache retrieval to proceed
+                // even when another thread is loading a new item state.
+                ItemState cachedState = cache.retrieve(id);
+                if (cachedState == null) {
+                    // put it in cache
+                    cache.cache(state);
+                    // set parent container
+                    state.setContainer(this);
+                } else {
+                    state = cachedState;
+                }
             }
-            return state;
         }
+        return state;
     }
 
     /**