Bladeren bron

HADOOP-1017. Cache constructors. Contributed by Ron Bodkin.

git-svn-id: https://svn.apache.org/repos/asf/lucene/hadoop/trunk@510188 13f79535-47bb-0310-9956-ffa450edef68
Doug Cutting 18 jaren geleden
bovenliggende
commit
439b36a385

+ 3 - 0
CHANGES.txt

@@ -78,6 +78,9 @@ Trunk (unreleased changes)
     namenode and jobtracker with include and exclude files.  (Wendy
     Chien via cutting)
 
+24. HADOOP-1017.  Cache constructors, for improved performance.
+    (Ron Bodkin via cutting)
+
 
 Release 0.11.2 - 2007-02-16
 

+ 24 - 2
src/java/org/apache/hadoop/util/ReflectionUtils.java

@@ -21,6 +21,8 @@ package org.apache.hadoop.util;
 import java.lang.reflect.Constructor;
 import java.io.*;
 import java.lang.management.*;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 import org.apache.commons.logging.Log;
 import org.apache.hadoop.conf.*;
@@ -33,6 +35,12 @@ import org.apache.hadoop.mapred.*;
 public class ReflectionUtils {
     
     private static final Class[] emptyArray = new Class[]{};
+    /** 
+     * Cache of constructors for each class. Pins the classes so they
+     * can't be garbage collected until ReflectionUtils can be collected.
+     */
+    private static final Map<Class<?>, Constructor<?>> CONSTRUCTOR_CACHE = 
+        new ConcurrentHashMap<Class<?>, Constructor<?>>();
 
     /**
      * Check and set 'configuration' if necessary.
@@ -61,8 +69,12 @@ public class ReflectionUtils {
     public static Object newInstance(Class theClass, Configuration conf) {
         Object result;
         try {
-            Constructor meth = theClass.getDeclaredConstructor(emptyArray);
-            meth.setAccessible(true);
+            Constructor meth = CONSTRUCTOR_CACHE.get(theClass);
+            if (meth == null) {
+              meth = theClass.getDeclaredConstructor(emptyArray);
+              meth.setAccessible(true);
+              CONSTRUCTOR_CACHE.put(theClass, meth);
+            }
             result = meth.newInstance();
         } catch (Exception e) {
             throw new RuntimeException(e);
@@ -152,4 +164,14 @@ public class ReflectionUtils {
         }
       }
     }
+
+    // methods to support testing
+    static void clearCache() {
+      CONSTRUCTOR_CACHE.clear();
+    }
+    
+    static int getCacheSize() {
+      return CONSTRUCTOR_CACHE.size();
+    }
+
 }

+ 91 - 0
src/test/org/apache/hadoop/util/TestReflectionUtils.java

@@ -0,0 +1,91 @@
+package org.apache.hadoop.util;
+
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.HashMap;
+
+import junit.framework.TestCase;
+
+public class TestReflectionUtils extends TestCase {
+
+    private static Class toConstruct[] = { String.class, TestReflectionUtils.class, HashMap.class };
+    private Throwable failure = null;
+
+    public void setUp() {
+      ReflectionUtils.clearCache();
+    }
+    
+    public void testCache() throws Exception {
+      assertEquals(0, cacheSize());
+      doTestCache();
+      assertEquals(toConstruct.length, cacheSize());
+      ReflectionUtils.clearCache();
+      assertEquals(0, cacheSize());
+    }
+    
+    
+    private void doTestCache() {
+      for (int i=0; i<toConstruct.length; i++) {
+          Class cl = toConstruct[i];
+          Object x = ReflectionUtils.newInstance(cl, null);
+          Object y = ReflectionUtils.newInstance(cl, null);
+          assertEquals(cl, x.getClass());
+          assertEquals(cl, y.getClass());
+      }
+    }
+    
+    public void testThreadSafe() throws Exception {
+      Thread[] th = new Thread[32];
+      for (int i=0; i<th.length; i++) {
+          th[i] = new Thread() {
+            public void run() {
+            try {
+              doTestCache();
+            } catch (Throwable t) {
+              failure = t;
+            }
+          }
+        };
+        th[i].start();
+      }
+      for (int i=0; i<th.length; i++) {
+        th[i].join();
+      }
+      if (failure != null) {
+        failure.printStackTrace();
+        fail(failure.getMessage());
+      }
+    }
+    
+    private int cacheSize() throws Exception {
+      return ReflectionUtils.getCacheSize();
+    }
+    
+    public void testCantCreate() {
+      try {
+        ReflectionUtils.newInstance(NoDefaultCtor.class, null);
+        fail("invalid call should fail");
+      } catch (RuntimeException rte) {
+        assertEquals(NoSuchMethodException.class, rte.getCause().getClass());
+      }
+    }
+    
+    public void testCacheDoesntLeak() throws Exception {
+      int iterations=9999; // very fast, but a bit less reliable - bigger numbers force GC
+      for (int i=0; i<iterations; i++) {
+        URLClassLoader loader = new URLClassLoader(new URL[0], getClass().getClassLoader());
+        Class cl = Class.forName("org.apache.hadoop.util.TestReflectionUtils$LoadedInChild", false, loader);
+        Object o = ReflectionUtils.newInstance(cl, null);
+        assertEquals(cl, o.getClass());
+      }
+      System.gc();
+      assertTrue(cacheSize()+" too big", cacheSize()<iterations);
+    }
+    
+    private static class LoadedInChild {
+    }
+    
+    public static class NoDefaultCtor {
+      public NoDefaultCtor(int x) {}
+    }
+}