Index: oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java (revision 1452217) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeState.java (working copy) @@ -152,6 +152,10 @@ initialized = true; } } + if (initialized) { + // refresh cache to force re-calculation of weight (OAK-643) + cache.refresh(revision + path); + } if (initialized && !PathUtils.denotesRoot(path)) { // OAK-591: check if we can re-use a previous revision // by looking up the node state by hash or id (if available) @@ -344,6 +348,46 @@ return path; } + /** + * @return the approximate memory usage of this node state. + */ + synchronized int getMemory() { + // base memory footprint is roughly 64 bytes + int memory = 64; + // path String + memory += 12 + path.length() * 2; + // revision String + memory += 12 + revision.length() * 2; + // optional hash String + if (hash != null) { + memory += 12 + hash.length() * 2; + } + // optional id String + if (id != null && !id.equals(hash)) { + memory += 12 + id.length() * 2; + } + // rough approximation for properties + if (properties != null) { + for (Map.Entry entry : properties.entrySet()) { + // name + memory += 12 + entry.getKey().length() * 2; + PropertyState propState = entry.getValue(); + if (propState.getType() != Type.BINARY + && propState.getType() != Type.BINARIES) { + // assume binaries go into blob store + for (int i = 0; i < propState.count(); i++) { + memory += propState.size(i); + } + } + } + } + // rough approximation for child nodes + if (childNames != null) { + memory += childNames.size() * 150; + } + return memory; + } + //------------------------------------------------------------< private >--- private boolean hasChanges(String journal) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeStore.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeStore.java (revision 1452217) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/kernel/KernelNodeStore.java (working copy) @@ -25,6 +25,10 @@ import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; +import com.google.common.cache.Weigher; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.SettableFuture; + import org.apache.jackrabbit.mk.api.MicroKernel; import org.apache.jackrabbit.mk.api.MicroKernelException; import org.apache.jackrabbit.oak.spi.commit.EmptyObserver; @@ -52,18 +56,34 @@ private volatile Observer observer = EmptyObserver.INSTANCE; private final LoadingCache cache = - CacheBuilder.newBuilder().maximumSize(10000).build( - new CacheLoader() { - @Override - public KernelNodeState load(String key) { - int slash = key.indexOf('/'); - String revision = key.substring(0, slash); - String path = key.substring(slash); - return new KernelNodeState( - kernel, path, revision, cache); - } - }); + CacheBuilder.newBuilder().maximumWeight(16 * 1024 * 1024).weigher( + new Weigher() { + @Override + public int weigh(String key, KernelNodeState state) { + return state.getMemory(); + } + }).build(new CacheLoader() { + @Override + public KernelNodeState load(String key) { + int slash = key.indexOf('/'); + String revision = key.substring(0, slash); + String path = key.substring(slash); + return new KernelNodeState(kernel, path, revision, cache); + } + @Override + public ListenableFuture reload(String key, + KernelNodeState oldValue) + throws Exception { + // LoadingCache.reload() is only used to re-calculate the + // memory usage on KernelNodeState.init(). Therefore + // we simply return the old value as is (OAK-643) + SettableFuture future = SettableFuture.create(); + future.set(oldValue); + return future; + } + }); + /** * State of the current root node. */