Author: henrib Date: Mon Nov 9 22:16:28 2009 New Revision: 834257 URL: http://svn.apache.org/viewvc?rev=834257&view=rev Log: Fix for JEXL-61; all caches get wrapped by SoftReference, allowing them to be GCed under memory pressure.
Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreator.java (with props) commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreatorTest.java (with props) Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreator.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreator.java?rev=834257&view=auto ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreator.java (added) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreator.java Mon Nov 9 22:16:28 2009 @@ -0,0 +1,123 @@ +/* + * Copyright 2009 henri. + * + * Licensed 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. + * under the License. + */ +package org.apache.commons.jexl; + +import java.io.File; +import java.io.FileWriter; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; + +/** + * Helper class to test GC / reference interactions. + * Dynamically creates a class by compiling generated source Java code and + * load it through a dedicated class loader. + */ +public class ClassCreator { + private final File base; + private File packageDir = null; + private int seed = 0; + private String className = null; + private String sourceName = null; + private ClassLoader loader = null; + + public ClassCreator(File theBase) throws Exception { + base = theBase; + } + + public void clear() { + seed = 0; + packageDir = null; + className = null; + sourceName = null; + packageDir = null; + loader = null; + } + + public void setSeed(int s) { + seed = s; + className = "foo" + s; + sourceName = className + ".java"; + packageDir = new File(base, seed + "/org/apache/commons/jexl/generated"); + packageDir.mkdirs(); + loader = null; + } + + public String getClassName() { + return "org.apache.commons.jexl.generated." + className; + } + + public Class<?> getClassInstance() throws Exception { + return getClassLoader().loadClass("org.apache.commons.jexl.generated." + className); + } + + public ClassLoader getClassLoader() throws Exception { + if (loader == null) { + URL classpath = (new File(base, Integer.toString(seed))).toURI().toURL(); + loader = new URLClassLoader(new URL[]{classpath}, null); + } + return loader; + } + + public Class<?> createClass() throws Exception { + // generate, compile & validate + generate(); + Class<?> clazz = compile(); + if (clazz == null) { + throw new Exception("failed to compile foo" + seed); + } + Object v = validate(clazz); + if (v instanceof Integer && ((Integer) v).intValue() == seed) { + return clazz; + } + throw new Exception("failed to validate foo" + seed); + } + + void generate() throws Exception { + FileWriter aWriter = new FileWriter(new File(packageDir, sourceName), false); + aWriter.write("package org.apache.commons.jexl.generated;"); + aWriter.write("public class " + className + "{\n"); + aWriter.write("private int value ="); + aWriter.write(Integer.toString(seed)); + aWriter.write(";\n"); + aWriter.write(" public void setValue(int v) {"); + aWriter.write(" value = v;"); + aWriter.write(" }\n"); + aWriter.write(" public int getValue() {"); + aWriter.write(" return value;"); + aWriter.write(" }\n"); + aWriter.write(" }\n"); + aWriter.flush(); + aWriter.close(); + } + + Class<?> compile() throws Exception { + String[] source = {packageDir.getPath() + "/" + sourceName}; + if (com.sun.tools.javac.Main.compile(source) >= 0) { + return getClassLoader().loadClass("org.apache.commons.jexl.generated." + className); + } + return null; + } + + Object validate(Class<?> clazz) throws Exception { + Class<?> params[] = {}; + Object paramsObj[] = {}; + Object iClass = clazz.newInstance(); + Method thisMethod = clazz.getDeclaredMethod("getValue", params); + return thisMethod.invoke(iClass, paramsObj); + } +} Propchange: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreator.java ------------------------------------------------------------------------------ svn:eol-style = native Added: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreatorTest.java URL: http://svn.apache.org/viewvc/commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreatorTest.java?rev=834257&view=auto ============================================================================== --- commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreatorTest.java (added) +++ commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreatorTest.java Mon Nov 9 22:16:28 2009 @@ -0,0 +1,186 @@ +/* + * Copyright 2009 henri. + * + * Licensed 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. + * under the License. + */ +package org.apache.commons.jexl; + +import java.io.File; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Basic check on automated class creation + */ +public class ClassCreatorTest extends JexlTestCase { + static final Log logger = LogFactory.getLog(JexlTestCase.class); + static final int LOOPS = 8; + private File base = null; + + @Override + public void setUp() throws Exception { + base = new File(System.getProperty("java.io.tmpdir") + + File.pathSeparator + + "jexl" + + System.currentTimeMillis()); + } + + @Override + public void tearDown() throws Exception { + deleteDirectory(base); + } + + private void deleteDirectory(File dir) { + if (dir.isDirectory()) { + for (File file : dir.listFiles()) { + if (file.isFile()) { + file.delete(); + } + } + } + dir.delete(); + } + + // A space hog class + static final int MEGA = 1024 * 1024; + public class BigObject { + private final byte[] space = new byte[MEGA]; + private final int id; + + public BigObject(int id) { + this.id = id; + } + + public int getId() { + return id; + } + } + + // A soft reference on class + static final class ClassReference extends WeakReference<Class<?>> { + ClassReference(Class<?> clazz, ReferenceQueue<Object> queue) { + super(clazz, queue); + } + } + // A weak reference on instance + static final class InstanceReference extends SoftReference<Object> { + InstanceReference(Object obj, ReferenceQueue<Object> queue) { + super(obj, queue); + } + } + + public void testOne() throws Exception { + ClassCreator cctor = new ClassCreator(base); + cctor.setSeed(1); + Class<?> foo1 = cctor.createClass(); + assertEquals("foo1", foo1.getSimpleName()); + cctor.clear(); + } + + public void testMany() throws Exception { + int pass = 0; + int gced = -1; + ReferenceQueue<Object> queue = new ReferenceQueue<Object>(); + List<Reference<?>> stuff = new ArrayList<Reference<?>>(); + // keeping a reference on methods prevent classes from being GCed +// List<Object> mm = new ArrayList<Object>(); + JexlEngine jexl = new JexlEngine(); + jexl.setCache(512); + Expression expr = jexl.createExpression("foo.value"); + Expression newx = jexl.createExpression("foo = new(clazz)"); + JexlContext context = JexlHelper.createContext(); + + ClassCreator cctor = new ClassCreator(base); + for (int i = 0; i < LOOPS && gced < 0; ++i) { + cctor.setSeed(i); + Class<?> clazz; + if (pass ==0) { + clazz = cctor.createClass(); + } else { + clazz = cctor.getClassInstance(); + if (clazz == null) { + assertEquals(i, gced); + break; + } + } + // this code verifies the assumption that holding a strong reference to a method prevents + // its owning class from being GCed +// Method m = clazz.getDeclaredMethod("getValue", new Class<?>[0]); +// mm.add(m); + // we should not be able to create foox since it is unknown to the Jexl classloader + context.getVars().put("clazz", cctor.getClassName()); + context.getVars().put("foo", null); + Object z = newx.evaluate(context); + assertNull(z); + // check with the class itself + context.getVars().put("clazz", clazz); + z = newx.evaluate(context); + assertNotNull(clazz + ": class " + i + " could not be instantiated on pass " + pass, z); + assertEquals(new Integer(i), expr.evaluate(context)); + // with the proper class loader, attempt to create an instance from the class name + jexl.setClassLoader(cctor.getClassLoader()); + z = newx.evaluate(context); + assertTrue(z.getClass().equals(clazz)); + assertEquals(new Integer(i), expr.evaluate(context)); + cctor.clear(); + jexl.setClassLoader(null); + + // on pass 0, attempt to force GC to run and collect generated classes + if (pass == 0) { + // add a weak reference on the class + stuff.add(new ClassReference(clazz, queue)); + // add a soft reference on an instance + stuff.add(new InstanceReference(clazz.newInstance(), queue)); + + // attempt to force GC: + // while we still have a MB free, create & store big objects + for (int b = 0; b < 64 && Runtime.getRuntime().freeMemory() > MEGA; ++b) { + BigObject big = new BigObject(b); + stuff.add(new InstanceReference(big, queue)); + } + // hint it... + System.gc(); + // let's see if some weak refs got collected + boolean qr = false; + while (queue.poll() != null) { + Reference<?> ref = queue.remove(1); + if (ref instanceof ClassReference) { + gced = i; + qr = true; + } + } + if (qr) { + //logger.warn("may have GCed class around " + i); + pass = 1; + i = 0; + } + } + } + + if (gced < 0) { + logger.warn("unable to force GC"); + //assertTrue(gced > 0); + } + } + + static public void main(String[] args) throws Exception { + (new ClassCreatorTest()).runTest("testMany"); + } +} Propchange: commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl/ClassCreatorTest.java ------------------------------------------------------------------------------ svn:eol-style = native