Repository: tinkerpop Updated Branches: refs/heads/TINKERPOP-1612 2a601f6fd -> 6e33a3159 (forced update)
TINKERPOP-1560 Used ManagedConcurrentValueMap in GremlinGroovyClassLoader By configuring the ManagedConcurrentValueMap to have weak references, the GremlinGroovyClassLoader, and therefore the GremlinGroovyScriptEngine, are now able to "forget" classes that are no longer used. It was determined that the cache of these classes would grow indefinitely for each script passed to the GremlinGroovyScriptEngine, thus allowing the metaspace to continue to grow. It isn't really possible to write tests to verify that this change works, but I did test manually by watching memory usage in a profiler and could see that metaspace memory stayed stable and that classes were unloading from the classloader over time. Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/97265772 Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/97265772 Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/97265772 Branch: refs/heads/TINKERPOP-1612 Commit: 97265772db79618cd5de83bb45d22f3e2bb6be9e Parents: f84f454 Author: Stephen Mallette <sp...@genoprime.com> Authored: Fri Jan 20 11:54:45 2017 -0500 Committer: Stephen Mallette <sp...@genoprime.com> Committed: Mon Jan 23 07:45:26 2017 -0500 ---------------------------------------------------------------------- CHANGELOG.asciidoc | 1 + .../groovy/jsr223/GremlinGroovyClassLoader.java | 24 ++++++++- .../jsr223/ManagedConcurrentValueMap.java | 4 ++ .../GremlinGroovyScriptEngineIntegrateTest.java | 55 ++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/97265772/CHANGELOG.asciidoc ---------------------------------------------------------------------- diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index fb0f8da..5e06a1c 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -34,6 +34,7 @@ TinkerPop 3.2.4 (Release Date: NOT OFFICIALLY RELEASED YET) * SASL negotiation supports both a byte array and Base64 encoded bytes as a string for authentication to Gremlin Server. * Deprecated `TinkerIoRegistry` replacing it with the more consistently named `TinkerIoRegistryV1d0`. * Made error messaging more consistent during result iteration timeouts in Gremlin Server. +* Fixed a memory leak in the classloader for the `GremlinGroovyScriptEngine` where classes in the loader were not releasing from memory as a strong reference was always maintained. * `PathRetractionStrategy` does not add a `NoOpBarrierStep` to the end of local children as its wasted computation in 99% of traversals. * Fixed a bug in `AddVertexStartStep` where if a side-effect was being used in the parametrization, an NPE occurred. * Fixed a bug in `LazyBarrierStrategy` where `profile()` was deactivating it accidentally. http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/97265772/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyClassLoader.java ---------------------------------------------------------------------- diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyClassLoader.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyClassLoader.java index e987ce0..2bc122c 100644 --- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyClassLoader.java +++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/GremlinGroovyClassLoader.java @@ -20,19 +20,39 @@ package org.apache.tinkerpop.gremlin.groovy.jsr223; import groovy.lang.GroovyClassLoader; import org.codehaus.groovy.control.CompilerConfiguration; +import org.codehaus.groovy.util.ReferenceBundle; + +import java.util.Map; +import java.util.concurrent.ConcurrentMap; /** - * A {@code GroovyClassLoader} extension that provides access to the {@code removeClassCacheEntry(String)} method. + * A {@code GroovyClassLoader} extension that uses a {@link ManagedConcurrentValueMap} configured with soft references. + * In this way, the classloader will "forget" scripts allowing them to be garbage collected and thus prevent memory + * issues in JVM metaspace. * * @author Stephen Mallette (http://stephen.genoprime.com) */ class GremlinGroovyClassLoader extends GroovyClassLoader { + private final ManagedConcurrentValueMap<String, Class> classSoftCache; + public GremlinGroovyClassLoader(final ClassLoader parent, final CompilerConfiguration conf) { super(parent, conf); + classSoftCache = new ManagedConcurrentValueMap<>(ReferenceBundle.getSoftBundle()); } @Override protected void removeClassCacheEntry(final String name) { - super.removeClassCacheEntry(name); + this.classSoftCache.remove(name); + } + + @Override + protected void setClassCacheEntry(final Class cls) { + this.classSoftCache.put(cls.getName(), cls); + } + + @Override + protected Class getClassCacheEntry(final String name) { + if(null == name) return null; + return this.classSoftCache.get(name); } } http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/97265772/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ManagedConcurrentValueMap.java ---------------------------------------------------------------------- diff --git a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ManagedConcurrentValueMap.java b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ManagedConcurrentValueMap.java index dce056b..f1ee00d 100644 --- a/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ManagedConcurrentValueMap.java +++ b/gremlin-groovy/src/main/java/org/apache/tinkerpop/gremlin/groovy/jsr223/ManagedConcurrentValueMap.java @@ -76,6 +76,10 @@ class ManagedConcurrentValueMap<K, V> { internalMap.put(key, ref); } + public void remove(final K key) { + internalMap.remove(key); + } + /** * Clear the map. */ http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/97265772/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinGroovyScriptEngineIntegrateTest.java ---------------------------------------------------------------------- diff --git a/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinGroovyScriptEngineIntegrateTest.java b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinGroovyScriptEngineIntegrateTest.java new file mode 100644 index 0000000..5242d3b --- /dev/null +++ b/gremlin-groovy/src/test/java/org/apache/tinkerpop/gremlin/jsr223/GremlinGroovyScriptEngineIntegrateTest.java @@ -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.tinkerpop.gremlin.jsr223; + +import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine; +import org.javatuples.Pair; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.junit.Assert.assertEquals; + +/** + * @author Stephen Mallette (http://stephen.genoprime.com) + */ +public class GremlinGroovyScriptEngineIntegrateTest { + @Test + @Ignore("This is not a test that needs to run on build - it's more for profiling the GremlinGroovyScriptEngine") + public void shouldTest() throws Exception { + final Random r = new Random(); + final List<Pair<String, Integer>> scripts = new ArrayList<>(); + final GremlinGroovyScriptEngine engine = new GremlinGroovyScriptEngine(); + for (int ix = 0; ix < 1000000; ix++) { + final String script = "1 + " + ix; + final int output = (int) engine.eval(script); + assertEquals(1 + ix, output); + + if (ix % 1000 == 0) scripts.add(Pair.with(script, output)); + + if (ix % 25 == 0) { + final Pair<String,Integer> p = scripts.get(r.nextInt(scripts.size())); + assertEquals(p.getValue1().intValue(), (int) engine.eval(p.getValue0())); + } + } + } +}