You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2009/11/09 23:16:28 UTC

svn commit: r834257 - in /commons/proper/jexl/trunk/src/test/java/org/apache/commons/jexl: ClassCreator.java ClassCreatorTest.java

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